mirror of
https://github.com/NixOS/nix
synced 2025-07-07 10:11:47 +02:00
Flake schemas
This applies upstream https://github.com/NixOS/nix/pull/8892.
This commit is contained in:
parent
51583851a2
commit
6406619c44
25 changed files with 702 additions and 819 deletions
|
@ -43,20 +43,6 @@ std::vector<std::string> InstallableFlake::getActualAttrPaths()
|
|||
return res;
|
||||
}
|
||||
|
||||
Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake)
|
||||
{
|
||||
auto vFlake = state.allocValue();
|
||||
|
||||
callFlake(state, lockedFlake, *vFlake);
|
||||
|
||||
auto aOutputs = vFlake->attrs()->get(state.symbols.create("outputs"));
|
||||
assert(aOutputs);
|
||||
|
||||
state.forceValue(*aOutputs->value, aOutputs->value->determinePos(noPos));
|
||||
|
||||
return aOutputs->value;
|
||||
}
|
||||
|
||||
static std::string showAttrPaths(const std::vector<std::string> & paths)
|
||||
{
|
||||
std::string s;
|
||||
|
|
|
@ -52,8 +52,6 @@ struct InstallableFlake : InstallableValue
|
|||
|
||||
std::vector<std::string> getActualAttrPaths();
|
||||
|
||||
Value * getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake);
|
||||
|
||||
DerivedPathsWithInfo toDerivedPaths() override;
|
||||
|
||||
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
|
||||
|
|
|
@ -444,11 +444,6 @@ ref<eval_cache::EvalCache> openEvalCache(
|
|||
: std::nullopt;
|
||||
auto rootLoader = [&state, lockedFlake]()
|
||||
{
|
||||
/* For testing whether the evaluation cache is
|
||||
complete. */
|
||||
if (getEnv("NIX_ALLOW_EVAL").value_or("1") == "0")
|
||||
throw Error("not everything is cached, but evaluation is not allowed");
|
||||
|
||||
auto vFlake = state.allocValue();
|
||||
flake::callFlake(state, *lockedFlake, *vFlake);
|
||||
|
||||
|
|
|
@ -368,6 +368,12 @@ Value * EvalCache::getRootValue()
|
|||
{
|
||||
if (!value) {
|
||||
debug("getting root value");
|
||||
|
||||
/* For testing whether the evaluation cache is
|
||||
complete. */
|
||||
if (getEnv("NIX_ALLOW_EVAL").value_or("1") == "0")
|
||||
throw Error("not everything is cached, but evaluation is not allowed");
|
||||
|
||||
value = allocRootValue(rootLoader());
|
||||
}
|
||||
return *value;
|
||||
|
|
|
@ -34,7 +34,11 @@ class EvalCache : public std::enable_shared_from_this<EvalCache>
|
|||
friend struct CachedEvalError;
|
||||
|
||||
std::shared_ptr<AttrDb> db;
|
||||
|
||||
public:
|
||||
EvalState & state;
|
||||
|
||||
private:
|
||||
typedef std::function<Value *()> RootLoader;
|
||||
RootLoader rootLoader;
|
||||
RootValue value;
|
||||
|
@ -89,7 +93,10 @@ class AttrCursor : public std::enable_shared_from_this<AttrCursor>
|
|||
friend class EvalCache;
|
||||
friend struct CachedEvalError;
|
||||
|
||||
public:
|
||||
ref<EvalCache> root;
|
||||
|
||||
private:
|
||||
typedef std::optional<std::pair<std::shared_ptr<AttrCursor>, Symbol>> Parent;
|
||||
Parent parent;
|
||||
RootValue _value;
|
||||
|
|
|
@ -204,7 +204,7 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
|||
return inputs;
|
||||
}
|
||||
|
||||
static Flake readFlake(
|
||||
Flake readFlake(
|
||||
EvalState & state,
|
||||
const FlakeRef & originalRef,
|
||||
const FlakeRef & resolvedRef,
|
||||
|
@ -336,19 +336,15 @@ static LockFile readLockFile(const SourcePath & lockFilePath)
|
|||
: LockFile();
|
||||
}
|
||||
|
||||
/* Compute an in-memory lock file for the specified top-level flake,
|
||||
and optionally write it to file, if the flake is writable. */
|
||||
LockedFlake lockFlake(
|
||||
EvalState & state,
|
||||
const FlakeRef & topRef,
|
||||
const LockFlags & lockFlags)
|
||||
const LockFlags & lockFlags,
|
||||
Flake flake,
|
||||
FlakeCache & flakeCache)
|
||||
{
|
||||
FlakeCache flakeCache;
|
||||
|
||||
auto useRegistries = lockFlags.useRegistries.value_or(flakeSettings.useRegistries);
|
||||
|
||||
auto flake = getFlake(state, topRef, useRegistries, flakeCache);
|
||||
|
||||
if (lockFlags.applyNixConfig) {
|
||||
flake.config.apply();
|
||||
state.store->setOptions();
|
||||
|
@ -738,6 +734,28 @@ LockedFlake lockFlake(
|
|||
}
|
||||
}
|
||||
|
||||
LockedFlake lockFlake(
|
||||
EvalState & state,
|
||||
const FlakeRef & topRef,
|
||||
const LockFlags & lockFlags)
|
||||
{
|
||||
FlakeCache flakeCache;
|
||||
|
||||
auto useRegistries = lockFlags.useRegistries.value_or(flakeSettings.useRegistries);
|
||||
|
||||
return lockFlake(state, topRef, lockFlags, getFlake(state, topRef, useRegistries, flakeCache), flakeCache);
|
||||
}
|
||||
|
||||
LockedFlake lockFlake(
|
||||
EvalState & state,
|
||||
const FlakeRef & topRef,
|
||||
const LockFlags & lockFlags,
|
||||
Flake flake)
|
||||
{
|
||||
FlakeCache flakeCache;
|
||||
return lockFlake(state, topRef, lockFlags, std::move(flake), flakeCache);
|
||||
}
|
||||
|
||||
void callFlake(EvalState & state,
|
||||
const LockedFlake & lockedFlake,
|
||||
Value & vRes)
|
||||
|
|
|
@ -193,11 +193,29 @@ struct LockFlags
|
|||
std::set<InputPath> inputUpdates;
|
||||
};
|
||||
|
||||
Flake readFlake(
|
||||
EvalState & state,
|
||||
const FlakeRef & originalRef,
|
||||
const FlakeRef & resolvedRef,
|
||||
const FlakeRef & lockedRef,
|
||||
const SourcePath & rootDir,
|
||||
const InputPath & lockRootPath);
|
||||
|
||||
/**
|
||||
* Compute an in-memory lock file for the specified top-level flake,
|
||||
* and optionally write it to file, if the flake is writable.
|
||||
*/
|
||||
LockedFlake lockFlake(
|
||||
EvalState & state,
|
||||
const FlakeRef & flakeRef,
|
||||
const LockFlags & lockFlags);
|
||||
|
||||
LockedFlake lockFlake(
|
||||
EvalState & state,
|
||||
const FlakeRef & topRef,
|
||||
const LockFlags & lockFlags,
|
||||
Flake flake);
|
||||
|
||||
void callFlake(
|
||||
EvalState & state,
|
||||
const LockedFlake & lockedFlake,
|
||||
|
|
43
src/nix/call-flake-schemas.nix
Normal file
43
src/nix/call-flake-schemas.nix
Normal file
|
@ -0,0 +1,43 @@
|
|||
/* The flake providing default schemas. */
|
||||
defaultSchemasFlake:
|
||||
|
||||
/* The flake whose contents we want to extract. */
|
||||
flake:
|
||||
|
||||
let
|
||||
|
||||
# Helper functions.
|
||||
|
||||
mapAttrsToList = f: attrs: map (name: f name attrs.${name}) (builtins.attrNames attrs);
|
||||
|
||||
in
|
||||
|
||||
rec {
|
||||
outputNames = builtins.attrNames flake.outputs;
|
||||
|
||||
allSchemas = (flake.outputs.schemas or defaultSchemasFlake.schemas) // schemaOverrides;
|
||||
|
||||
schemaOverrides = {}; # FIXME
|
||||
|
||||
schemas =
|
||||
builtins.listToAttrs (builtins.concatLists (mapAttrsToList
|
||||
(outputName: output:
|
||||
if allSchemas ? ${outputName} then
|
||||
[{ name = outputName; value = allSchemas.${outputName}; }]
|
||||
else
|
||||
[ ])
|
||||
flake.outputs));
|
||||
|
||||
inventory =
|
||||
builtins.mapAttrs
|
||||
(outputName: output:
|
||||
if schemas ? ${outputName} && schemas.${outputName}.version == 1
|
||||
then
|
||||
{ output = schemas.${outputName}.inventory output;
|
||||
inherit (schemas.${outputName}) doc;
|
||||
}
|
||||
else
|
||||
{ unknown = true; }
|
||||
)
|
||||
flake.outputs;
|
||||
}
|
|
@ -18,56 +18,20 @@ R""(
|
|||
# Description
|
||||
|
||||
This command verifies that the flake specified by flake reference
|
||||
*flake-url* can be evaluated successfully (as detailed below), and
|
||||
that the derivations specified by the flake's `checks` output can be
|
||||
built successfully.
|
||||
*flake-url* can be evaluated and built successfully according to its
|
||||
`schemas` flake output. For every flake output that has a schema
|
||||
definition, `nix flake check` uses the schema to extract the contents
|
||||
of the output. Then, for every item in the contents:
|
||||
|
||||
* It evaluates the elements of the `evalChecks` attribute set returned
|
||||
by the schema for that item, printing an error or warning for every
|
||||
check that fails to evaluate or that evaluates to `false`.
|
||||
|
||||
* It builds `derivation` attribute returned by the schema for that
|
||||
item, if the item has the `isFlakeCheck` attribute.
|
||||
|
||||
If the `keep-going` option is set to `true`, Nix will keep evaluating as much
|
||||
as it can and report the errors as it encounters them. Otherwise it will stop
|
||||
at the first error.
|
||||
|
||||
# Evaluation checks
|
||||
|
||||
The following flake output attributes must be derivations:
|
||||
|
||||
* `checks.`*system*`.`*name*
|
||||
* `defaultPackage.`*system*
|
||||
* `devShell.`*system*
|
||||
* `devShells.`*system*`.`*name*
|
||||
* `nixosConfigurations.`*name*`.config.system.build.toplevel`
|
||||
* `packages.`*system*`.`*name*
|
||||
|
||||
The following flake output attributes must be [app
|
||||
definitions](./nix3-run.md):
|
||||
|
||||
* `apps.`*system*`.`*name*
|
||||
* `defaultApp.`*system*
|
||||
|
||||
The following flake output attributes must be [template
|
||||
definitions](./nix3-flake-init.md):
|
||||
|
||||
* `defaultTemplate`
|
||||
* `templates.`*name*
|
||||
|
||||
The following flake output attributes must be *Nixpkgs overlays*:
|
||||
|
||||
* `overlay`
|
||||
* `overlays.`*name*
|
||||
|
||||
The following flake output attributes must be *NixOS modules*:
|
||||
|
||||
* `nixosModule`
|
||||
* `nixosModules.`*name*
|
||||
|
||||
The following flake output attributes must be
|
||||
[bundlers](./nix3-bundle.md):
|
||||
|
||||
* `bundlers.`*name*
|
||||
* `defaultBundler`
|
||||
|
||||
In addition, the `hydraJobs` output is evaluated in the same way as
|
||||
Hydra's `hydra-eval-jobs` (i.e. as a arbitrarily deeply nested
|
||||
attribute set of derivations). Similarly, the
|
||||
`legacyPackages`.*system* output is evaluated like `nix-env --query --available `.
|
||||
|
||||
)""
|
||||
|
|
221
src/nix/flake-schemas.cc
Normal file
221
src/nix/flake-schemas.cc
Normal file
|
@ -0,0 +1,221 @@
|
|||
#include "flake-schemas.hh"
|
||||
#include "eval-settings.hh"
|
||||
#include "fetch-to-store.hh"
|
||||
#include "memory-source-accessor.hh"
|
||||
|
||||
namespace nix::flake_schemas {
|
||||
|
||||
using namespace eval_cache;
|
||||
using namespace flake;
|
||||
|
||||
static LockedFlake getBuiltinDefaultSchemasFlake(EvalState & state)
|
||||
{
|
||||
auto accessor = make_ref<MemorySourceAccessor>();
|
||||
|
||||
accessor->setPathDisplay("«builtin-flake-schemas»");
|
||||
|
||||
accessor->addFile(
|
||||
CanonPath("flake.nix"),
|
||||
#include "builtin-flake-schemas.nix.gen.hh"
|
||||
);
|
||||
|
||||
// FIXME: remove this when we have lazy trees.
|
||||
auto storePath = fetchToStore(*state.store, {accessor}, FetchMode::Copy);
|
||||
state.allowPath(storePath);
|
||||
|
||||
// Construct a dummy flakeref.
|
||||
auto flakeRef = parseFlakeRef(
|
||||
fmt("tarball+https://builtin-flake-schemas?narHash=%s",
|
||||
state.store->queryPathInfo(storePath)->narHash.to_string(HashFormat::SRI, true)));
|
||||
|
||||
auto flake = readFlake(state, flakeRef, flakeRef, flakeRef, state.rootPath(state.store->toRealPath(storePath)), {});
|
||||
|
||||
return lockFlake(state, flakeRef, {}, flake);
|
||||
}
|
||||
|
||||
std::tuple<ref<EvalCache>, ref<eval_cache::AttrCursor>>
|
||||
call(EvalState & state, std::shared_ptr<flake::LockedFlake> lockedFlake, std::optional<FlakeRef> defaultSchemasFlake)
|
||||
{
|
||||
auto fingerprint = lockedFlake->getFingerprint(state.store);
|
||||
|
||||
std::string callFlakeSchemasNix =
|
||||
#include "call-flake-schemas.nix.gen.hh"
|
||||
;
|
||||
|
||||
auto lockedDefaultSchemasFlake =
|
||||
defaultSchemasFlake ? flake::lockFlake(state, *defaultSchemasFlake, {}) : getBuiltinDefaultSchemasFlake(state);
|
||||
auto lockedDefaultSchemasFlakeFingerprint = lockedDefaultSchemasFlake.getFingerprint(state.store);
|
||||
|
||||
std::optional<Fingerprint> fingerprint2;
|
||||
if (fingerprint && lockedDefaultSchemasFlakeFingerprint)
|
||||
fingerprint2 = hashString(
|
||||
HashAlgorithm::SHA256,
|
||||
fmt("app:%s:%s:%s",
|
||||
hashString(HashAlgorithm::SHA256, callFlakeSchemasNix).to_string(HashFormat::Base16, false),
|
||||
fingerprint->to_string(HashFormat::Base16, false),
|
||||
lockedDefaultSchemasFlakeFingerprint->to_string(HashFormat::Base16, false)));
|
||||
|
||||
// FIXME: merge with openEvalCache().
|
||||
auto cache = make_ref<EvalCache>(
|
||||
evalSettings.useEvalCache && evalSettings.pureEval ? fingerprint2 : std::nullopt,
|
||||
state,
|
||||
[&state, lockedFlake, callFlakeSchemasNix, lockedDefaultSchemasFlake]() {
|
||||
auto vCallFlakeSchemas = state.allocValue();
|
||||
state.eval(
|
||||
state.parseExprFromString(callFlakeSchemasNix, state.rootPath(CanonPath::root)), *vCallFlakeSchemas);
|
||||
|
||||
auto vFlake = state.allocValue();
|
||||
flake::callFlake(state, *lockedFlake, *vFlake);
|
||||
|
||||
auto vDefaultSchemasFlake = state.allocValue();
|
||||
if (vFlake->type() == nAttrs && vFlake->attrs()->get(state.symbols.create("schemas")))
|
||||
vDefaultSchemasFlake->mkNull();
|
||||
else
|
||||
flake::callFlake(state, lockedDefaultSchemasFlake, *vDefaultSchemasFlake);
|
||||
|
||||
auto vRes = state.allocValue();
|
||||
Value * args[] = {vDefaultSchemasFlake, vFlake};
|
||||
state.callFunction(*vCallFlakeSchemas, 2, args, *vRes, noPos);
|
||||
|
||||
return vRes;
|
||||
});
|
||||
|
||||
return {cache, cache->getRoot()->getAttr("inventory")};
|
||||
}
|
||||
|
||||
/* Derive the flake output attribute path from the cursor used to
|
||||
traverse the inventory. We do this so we don't have to maintain a
|
||||
separate attrpath for that. */
|
||||
std::vector<Symbol> toAttrPath(ref<AttrCursor> cursor)
|
||||
{
|
||||
auto attrPath = cursor->getAttrPath();
|
||||
std::vector<Symbol> res;
|
||||
auto i = attrPath.begin();
|
||||
assert(i != attrPath.end());
|
||||
++i; // skip "inventory"
|
||||
assert(i != attrPath.end());
|
||||
res.push_back(*i++); // copy output name
|
||||
if (i != attrPath.end())
|
||||
++i; // skip "outputs"
|
||||
while (i != attrPath.end()) {
|
||||
++i; // skip "children"
|
||||
if (i != attrPath.end())
|
||||
res.push_back(*i++);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string toAttrPathStr(ref<AttrCursor> cursor)
|
||||
{
|
||||
return concatStringsSep(".", cursor->root->state.symbols.resolve(toAttrPath(cursor)));
|
||||
}
|
||||
|
||||
void forEachOutput(
|
||||
ref<AttrCursor> inventory,
|
||||
std::function<void(Symbol outputName, std::shared_ptr<AttrCursor> output, const std::string & doc, bool isLast)> f)
|
||||
{
|
||||
// FIXME: handle non-IFD outputs first.
|
||||
// evalSettings.enableImportFromDerivation.setDefault(false);
|
||||
|
||||
auto outputNames = inventory->getAttrs();
|
||||
for (const auto & [i, outputName] : enumerate(outputNames)) {
|
||||
auto output = inventory->getAttr(outputName);
|
||||
try {
|
||||
auto isUnknown = (bool) output->maybeGetAttr("unknown");
|
||||
Activity act(*logger, lvlInfo, actUnknown, fmt("evaluating '%s'", toAttrPathStr(output)));
|
||||
f(outputName,
|
||||
isUnknown ? std::shared_ptr<AttrCursor>() : output->getAttr("output"),
|
||||
isUnknown ? "" : output->getAttr("doc")->getString(),
|
||||
i + 1 == outputNames.size());
|
||||
} catch (Error & e) {
|
||||
e.addTrace(nullptr, "while evaluating the flake output '%s':", toAttrPathStr(output));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visit(
|
||||
std::optional<std::string> system,
|
||||
ref<AttrCursor> node,
|
||||
std::function<void(ref<AttrCursor> leaf)> visitLeaf,
|
||||
std::function<void(std::function<void(ForEachChild)>)> visitNonLeaf,
|
||||
std::function<void(ref<AttrCursor> node, const std::vector<std::string> & systems)> visitFiltered)
|
||||
{
|
||||
Activity act(*logger, lvlInfo, actUnknown, fmt("evaluating '%s'", toAttrPathStr(node)));
|
||||
|
||||
/* Apply the system type filter. */
|
||||
if (system) {
|
||||
if (auto forSystems = node->maybeGetAttr("forSystems")) {
|
||||
auto systems = forSystems->getListOfStrings();
|
||||
if (std::find(systems.begin(), systems.end(), system) == systems.end()) {
|
||||
visitFiltered(node, systems);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto children = node->maybeGetAttr("children")) {
|
||||
visitNonLeaf([&](ForEachChild f) {
|
||||
auto attrNames = children->getAttrs();
|
||||
for (const auto & [i, attrName] : enumerate(attrNames)) {
|
||||
try {
|
||||
f(attrName, children->getAttr(attrName), i + 1 == attrNames.size());
|
||||
} catch (Error & e) {
|
||||
// FIXME: make it a flake schema attribute whether to ignore evaluation errors.
|
||||
if (node->root->state.symbols[toAttrPath(node)[0]] != "legacyPackages") {
|
||||
e.addTrace(nullptr, "while evaluating the flake output attribute '%s':", toAttrPathStr(node));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
else
|
||||
visitLeaf(ref(node));
|
||||
}
|
||||
|
||||
std::optional<std::string> what(ref<AttrCursor> leaf)
|
||||
{
|
||||
if (auto what = leaf->maybeGetAttr("what"))
|
||||
return what->getString();
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> shortDescription(ref<AttrCursor> leaf)
|
||||
{
|
||||
if (auto what = leaf->maybeGetAttr("shortDescription")) {
|
||||
auto s = trim(what->getString());
|
||||
if (s != "")
|
||||
return s;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::shared_ptr<AttrCursor> derivation(ref<AttrCursor> leaf)
|
||||
{
|
||||
return leaf->maybeGetAttr("derivation");
|
||||
}
|
||||
|
||||
MixFlakeSchemas::MixFlakeSchemas()
|
||||
{
|
||||
addFlag(
|
||||
{.longName = "default-flake-schemas",
|
||||
.description = "The URL of the flake providing default flake schema definitions.",
|
||||
.labels = {"flake-ref"},
|
||||
.handler = {&defaultFlakeSchemas},
|
||||
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
|
||||
completeFlakeRef(completions, getStore(), prefix);
|
||||
}}});
|
||||
}
|
||||
|
||||
std::optional<FlakeRef> MixFlakeSchemas::getDefaultFlakeSchemas()
|
||||
{
|
||||
if (!defaultFlakeSchemas)
|
||||
return std::nullopt;
|
||||
else
|
||||
return parseFlakeRef(*defaultFlakeSchemas, absPath("."));
|
||||
}
|
||||
|
||||
}
|
45
src/nix/flake-schemas.hh
Normal file
45
src/nix/flake-schemas.hh
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include "eval-cache.hh"
|
||||
#include "flake/flake.hh"
|
||||
#include "command.hh"
|
||||
|
||||
namespace nix::flake_schemas {
|
||||
|
||||
using namespace eval_cache;
|
||||
|
||||
std::tuple<ref<eval_cache::EvalCache>, ref<AttrCursor>>
|
||||
call(EvalState & state, std::shared_ptr<flake::LockedFlake> lockedFlake, std::optional<FlakeRef> defaultSchemasFlake);
|
||||
|
||||
std::vector<Symbol> toAttrPath(ref<AttrCursor> cursor);
|
||||
|
||||
std::string toAttrPathStr(ref<AttrCursor> cursor);
|
||||
|
||||
void forEachOutput(
|
||||
ref<AttrCursor> inventory,
|
||||
std::function<void(Symbol outputName, std::shared_ptr<AttrCursor> output, const std::string & doc, bool isLast)> f);
|
||||
|
||||
typedef std::function<void(Symbol attrName, ref<AttrCursor> attr, bool isLast)> ForEachChild;
|
||||
|
||||
void visit(
|
||||
std::optional<std::string> system,
|
||||
ref<AttrCursor> node,
|
||||
std::function<void(ref<AttrCursor> leaf)> visitLeaf,
|
||||
std::function<void(std::function<void(ForEachChild)>)> visitNonLeaf,
|
||||
std::function<void(ref<AttrCursor> node, const std::vector<std::string> & systems)> visitFiltered);
|
||||
|
||||
std::optional<std::string> what(ref<AttrCursor> leaf);
|
||||
|
||||
std::optional<std::string> shortDescription(ref<AttrCursor> leaf);
|
||||
|
||||
std::shared_ptr<AttrCursor> derivation(ref<AttrCursor> leaf);
|
||||
|
||||
/* Some helper functions for processing flake schema output. */
|
||||
struct MixFlakeSchemas : virtual Args, virtual StoreCommand
|
||||
{
|
||||
std::optional<std::string> defaultFlakeSchemas;
|
||||
|
||||
MixFlakeSchemas();
|
||||
|
||||
std::optional<FlakeRef> getDefaultFlakeSchemas();
|
||||
};
|
||||
|
||||
}
|
907
src/nix/flake.cc
907
src/nix/flake.cc
|
@ -17,6 +17,7 @@
|
|||
#include "eval-cache.hh"
|
||||
#include "markdown.hh"
|
||||
#include "users.hh"
|
||||
#include "flake-schemas.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <queue>
|
||||
|
@ -164,31 +165,6 @@ struct CmdFlakeLock : FlakeCommand
|
|||
}
|
||||
};
|
||||
|
||||
static void enumerateOutputs(EvalState & state, Value & vFlake,
|
||||
std::function<void(const std::string & name, Value & vProvide, const PosIdx pos)> callback)
|
||||
{
|
||||
auto pos = vFlake.determinePos(noPos);
|
||||
state.forceAttrs(vFlake, pos, "while evaluating a flake to get its outputs");
|
||||
|
||||
auto aOutputs = vFlake.attrs()->get(state.symbols.create("outputs"));
|
||||
assert(aOutputs);
|
||||
|
||||
state.forceAttrs(*aOutputs->value, pos, "while evaluating the outputs of a flake");
|
||||
|
||||
auto sHydraJobs = state.symbols.create("hydraJobs");
|
||||
|
||||
/* Hack: ensure that hydraJobs is evaluated before anything
|
||||
else. This way we can disable IFD for hydraJobs and then enable
|
||||
it for other outputs. */
|
||||
if (auto attr = aOutputs->value->attrs()->get(sHydraJobs))
|
||||
callback(state.symbols[attr->name], *attr->value, attr->pos);
|
||||
|
||||
for (auto & attr : *aOutputs->value->attrs()) {
|
||||
if (attr.name != sHydraJobs)
|
||||
callback(state.symbols[attr.name], *attr.value, attr.pos);
|
||||
}
|
||||
}
|
||||
|
||||
struct CmdFlakeMetadata : FlakeCommand, MixJSON
|
||||
{
|
||||
std::string description() override
|
||||
|
@ -319,7 +295,7 @@ struct CmdFlakeInfo : CmdFlakeMetadata
|
|||
}
|
||||
};
|
||||
|
||||
struct CmdFlakeCheck : FlakeCommand
|
||||
struct CmdFlakeCheck : FlakeCommand, flake_schemas::MixFlakeSchemas
|
||||
{
|
||||
bool build = true;
|
||||
bool checkAllSystems = false;
|
||||
|
@ -360,16 +336,26 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
auto state = getEvalState();
|
||||
|
||||
lockFlags.applyNixConfig = true;
|
||||
auto flake = lockFlake();
|
||||
auto flake = std::make_shared<LockedFlake>(lockFlake());
|
||||
auto localSystem = std::string(settings.thisSystem.get());
|
||||
|
||||
auto [cache, inventory] = flake_schemas::call(*state, flake, getDefaultFlakeSchemas());
|
||||
|
||||
std::vector<DerivedPath> drvPaths;
|
||||
|
||||
std::set<std::string> uncheckedOutputs;
|
||||
std::set<std::string> omittedSystems;
|
||||
|
||||
std::function<void(ref<eval_cache::AttrCursor> node)> visit;
|
||||
|
||||
bool hasErrors = false;
|
||||
|
||||
auto reportError = [&](const Error & e) {
|
||||
try {
|
||||
throw e;
|
||||
} catch (Error & e) {
|
||||
if (settings.keepGoing) {
|
||||
ignoreException();
|
||||
logError({.msg = e.info().msg});
|
||||
hasErrors = true;
|
||||
}
|
||||
else
|
||||
|
@ -377,428 +363,70 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
}
|
||||
};
|
||||
|
||||
std::set<std::string> omittedSystems;
|
||||
|
||||
// FIXME: rewrite to use EvalCache.
|
||||
|
||||
auto resolve = [&] (PosIdx p) {
|
||||
return state->positions[p];
|
||||
};
|
||||
|
||||
auto argHasName = [&] (Symbol arg, std::string_view expected) {
|
||||
std::string_view name = state->symbols[arg];
|
||||
return
|
||||
name == expected
|
||||
|| name == "_"
|
||||
|| (hasPrefix(name, "_") && name.substr(1) == expected);
|
||||
};
|
||||
|
||||
auto checkSystemName = [&](const std::string & system, const PosIdx pos) {
|
||||
// FIXME: what's the format of "system"?
|
||||
if (system.find('-') == std::string::npos)
|
||||
reportError(Error("'%s' is not a valid system type, at %s", system, resolve(pos)));
|
||||
};
|
||||
|
||||
auto checkSystemType = [&](const std::string & system, const PosIdx pos) {
|
||||
if (!checkAllSystems && system != localSystem) {
|
||||
omittedSystems.insert(system);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
auto checkDerivation = [&](const std::string & attrPath, Value & v, const PosIdx pos) -> std::optional<StorePath> {
|
||||
try {
|
||||
Activity act(*logger, lvlInfo, actUnknown,
|
||||
fmt("checking derivation %s", attrPath));
|
||||
auto packageInfo = getDerivation(*state, v, false);
|
||||
if (!packageInfo)
|
||||
throw Error("flake attribute '%s' is not a derivation", attrPath);
|
||||
else {
|
||||
// FIXME: check meta attributes
|
||||
auto storePath = packageInfo->queryDrvPath();
|
||||
if (storePath) {
|
||||
logger->log(lvlInfo,
|
||||
fmt("derivation evaluated to %s",
|
||||
store->printStorePath(storePath.value())));
|
||||
}
|
||||
return storePath;
|
||||
}
|
||||
} catch (Error & e) {
|
||||
e.addTrace(resolve(pos), HintFmt("while checking the derivation '%s'", attrPath));
|
||||
reportError(e);
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
std::vector<DerivedPath> drvPaths;
|
||||
|
||||
auto checkApp = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
|
||||
try {
|
||||
#if 0
|
||||
// FIXME
|
||||
auto app = App(*state, v);
|
||||
for (auto & i : app.context) {
|
||||
auto [drvPathS, outputName] = NixStringContextElem::parse(i);
|
||||
store->parseStorePath(drvPathS);
|
||||
}
|
||||
#endif
|
||||
} catch (Error & e) {
|
||||
e.addTrace(resolve(pos), HintFmt("while checking the app definition '%s'", attrPath));
|
||||
reportError(e);
|
||||
}
|
||||
};
|
||||
|
||||
auto checkOverlay = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
|
||||
try {
|
||||
Activity act(*logger, lvlInfo, actUnknown,
|
||||
fmt("checking overlay '%s'", attrPath));
|
||||
state->forceValue(v, pos);
|
||||
if (!v.isLambda()) {
|
||||
throw Error("overlay is not a function, but %s instead", showType(v));
|
||||
}
|
||||
if (v.payload.lambda.fun->hasFormals()
|
||||
|| !argHasName(v.payload.lambda.fun->arg, "final"))
|
||||
throw Error("overlay does not take an argument named 'final'");
|
||||
// FIXME: if we have a 'nixpkgs' input, use it to
|
||||
// evaluate the overlay.
|
||||
} catch (Error & e) {
|
||||
e.addTrace(resolve(pos), HintFmt("while checking the overlay '%s'", attrPath));
|
||||
reportError(e);
|
||||
}
|
||||
};
|
||||
|
||||
auto checkModule = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
|
||||
try {
|
||||
Activity act(*logger, lvlInfo, actUnknown,
|
||||
fmt("checking NixOS module '%s'", attrPath));
|
||||
state->forceValue(v, pos);
|
||||
} catch (Error & e) {
|
||||
e.addTrace(resolve(pos), HintFmt("while checking the NixOS module '%s'", attrPath));
|
||||
reportError(e);
|
||||
}
|
||||
};
|
||||
|
||||
std::function<void(const std::string & attrPath, Value & v, const PosIdx pos)> checkHydraJobs;
|
||||
|
||||
checkHydraJobs = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
|
||||
try {
|
||||
Activity act(*logger, lvlInfo, actUnknown,
|
||||
fmt("checking Hydra job '%s'", attrPath));
|
||||
state->forceAttrs(v, pos, "");
|
||||
|
||||
if (state->isDerivation(v))
|
||||
throw Error("jobset should not be a derivation at top-level");
|
||||
|
||||
for (auto & attr : *v.attrs()) {
|
||||
state->forceAttrs(*attr.value, attr.pos, "");
|
||||
auto attrPath2 = concatStrings(attrPath, ".", state->symbols[attr.name]);
|
||||
if (state->isDerivation(*attr.value)) {
|
||||
Activity act(*logger, lvlInfo, actUnknown,
|
||||
fmt("checking Hydra job '%s'", attrPath2));
|
||||
checkDerivation(attrPath2, *attr.value, attr.pos);
|
||||
} else
|
||||
checkHydraJobs(attrPath2, *attr.value, attr.pos);
|
||||
}
|
||||
|
||||
} catch (Error & e) {
|
||||
e.addTrace(resolve(pos), HintFmt("while checking the Hydra jobset '%s'", attrPath));
|
||||
reportError(e);
|
||||
}
|
||||
};
|
||||
|
||||
auto checkNixOSConfiguration = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
|
||||
try {
|
||||
Activity act(*logger, lvlInfo, actUnknown,
|
||||
fmt("checking NixOS configuration '%s'", attrPath));
|
||||
Bindings & bindings(*state->allocBindings(0));
|
||||
auto vToplevel = findAlongAttrPath(*state, "config.system.build.toplevel", bindings, v).first;
|
||||
state->forceValue(*vToplevel, pos);
|
||||
if (!state->isDerivation(*vToplevel))
|
||||
throw Error("attribute 'config.system.build.toplevel' is not a derivation");
|
||||
} catch (Error & e) {
|
||||
e.addTrace(resolve(pos), HintFmt("while checking the NixOS configuration '%s'", attrPath));
|
||||
reportError(e);
|
||||
}
|
||||
};
|
||||
|
||||
auto checkTemplate = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
|
||||
try {
|
||||
Activity act(*logger, lvlInfo, actUnknown,
|
||||
fmt("checking template '%s'", attrPath));
|
||||
|
||||
state->forceAttrs(v, pos, "");
|
||||
|
||||
if (auto attr = v.attrs()->get(state->symbols.create("path"))) {
|
||||
if (attr->name == state->symbols.create("path")) {
|
||||
NixStringContext context;
|
||||
auto path = state->coerceToPath(attr->pos, *attr->value, context, "");
|
||||
if (!path.pathExists())
|
||||
throw Error("template '%s' refers to a non-existent path '%s'", attrPath, path);
|
||||
// TODO: recursively check the flake in 'path'.
|
||||
}
|
||||
} else
|
||||
throw Error("template '%s' lacks attribute 'path'", attrPath);
|
||||
|
||||
if (auto attr = v.attrs()->get(state->symbols.create("description")))
|
||||
state->forceStringNoCtx(*attr->value, attr->pos, "");
|
||||
else
|
||||
throw Error("template '%s' lacks attribute 'description'", attrPath);
|
||||
|
||||
for (auto & attr : *v.attrs()) {
|
||||
std::string_view name(state->symbols[attr.name]);
|
||||
if (name != "path" && name != "description" && name != "welcomeText")
|
||||
throw Error("template '%s' has unsupported attribute '%s'", attrPath, name);
|
||||
}
|
||||
} catch (Error & e) {
|
||||
e.addTrace(resolve(pos), HintFmt("while checking the template '%s'", attrPath));
|
||||
reportError(e);
|
||||
}
|
||||
};
|
||||
|
||||
auto checkBundler = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
|
||||
try {
|
||||
Activity act(*logger, lvlInfo, actUnknown,
|
||||
fmt("checking bundler '%s'", attrPath));
|
||||
state->forceValue(v, pos);
|
||||
if (!v.isLambda())
|
||||
throw Error("bundler must be a function");
|
||||
// TODO: check types of inputs/outputs?
|
||||
} catch (Error & e) {
|
||||
e.addTrace(resolve(pos), HintFmt("while checking the template '%s'", attrPath));
|
||||
reportError(e);
|
||||
}
|
||||
};
|
||||
|
||||
visit = [&](ref<eval_cache::AttrCursor> node)
|
||||
{
|
||||
Activity act(*logger, lvlInfo, actUnknown, "evaluating flake");
|
||||
flake_schemas::visit(
|
||||
checkAllSystems ? std::optional<std::string>() : localSystem,
|
||||
node,
|
||||
|
||||
auto vFlake = state->allocValue();
|
||||
flake::callFlake(*state, flake, *vFlake);
|
||||
|
||||
enumerateOutputs(*state,
|
||||
*vFlake,
|
||||
[&](const std::string & name, Value & vOutput, const PosIdx pos) {
|
||||
Activity act(*logger, lvlInfo, actUnknown,
|
||||
fmt("checking flake output '%s'", name));
|
||||
|
||||
try {
|
||||
evalSettings.enableImportFromDerivation.setDefault(name != "hydraJobs");
|
||||
|
||||
state->forceValue(vOutput, pos);
|
||||
|
||||
std::string_view replacement =
|
||||
name == "defaultPackage" ? "packages.<system>.default" :
|
||||
name == "defaultApp" ? "apps.<system>.default" :
|
||||
name == "defaultTemplate" ? "templates.default" :
|
||||
name == "defaultBundler" ? "bundlers.<system>.default" :
|
||||
name == "overlay" ? "overlays.default" :
|
||||
name == "devShell" ? "devShells.<system>.default" :
|
||||
name == "nixosModule" ? "nixosModules.default" :
|
||||
"";
|
||||
if (replacement != "")
|
||||
warn("flake output attribute '%s' is deprecated; use '%s' instead", name, replacement);
|
||||
|
||||
if (name == "checks") {
|
||||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs()) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos)) {
|
||||
state->forceAttrs(*attr.value, attr.pos, "");
|
||||
for (auto & attr2 : *attr.value->attrs()) {
|
||||
auto drvPath = checkDerivation(
|
||||
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
||||
*attr2.value, attr2.pos);
|
||||
if (drvPath && attr_name == settings.thisSystem.get()) {
|
||||
drvPaths.push_back(DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePathRef(*drvPath),
|
||||
.outputs = OutputsSpec::All { },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[&](ref<eval_cache::AttrCursor> leaf)
|
||||
{
|
||||
if (auto evalChecks = leaf->maybeGetAttr("evalChecks")) {
|
||||
auto checkNames = evalChecks->getAttrs();
|
||||
for (auto & checkName : checkNames) {
|
||||
// FIXME: update activity
|
||||
auto cursor = evalChecks->getAttr(checkName);
|
||||
auto b = cursor->getBool();
|
||||
if (!b)
|
||||
reportError(Error("Evaluation check '%s' failed.", flake_schemas::toAttrPathStr(cursor)));
|
||||
}
|
||||
|
||||
else if (name == "formatter") {
|
||||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs()) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos)) {
|
||||
checkApp(
|
||||
fmt("%s.%s", name, attr_name),
|
||||
*attr.value, attr.pos);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
else if (name == "packages" || name == "devShells") {
|
||||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs()) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos)) {
|
||||
state->forceAttrs(*attr.value, attr.pos, "");
|
||||
for (auto & attr2 : *attr.value->attrs())
|
||||
checkDerivation(
|
||||
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
||||
*attr2.value, attr2.pos);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
else if (name == "apps") {
|
||||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs()) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos)) {
|
||||
state->forceAttrs(*attr.value, attr.pos, "");
|
||||
for (auto & attr2 : *attr.value->attrs())
|
||||
checkApp(
|
||||
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
||||
*attr2.value, attr2.pos);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
else if (name == "defaultPackage" || name == "devShell") {
|
||||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs()) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos)) {
|
||||
checkDerivation(
|
||||
fmt("%s.%s", name, attr_name),
|
||||
*attr.value, attr.pos);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
else if (name == "defaultApp") {
|
||||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs()) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos) ) {
|
||||
checkApp(
|
||||
fmt("%s.%s", name, attr_name),
|
||||
*attr.value, attr.pos);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
else if (name == "legacyPackages") {
|
||||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs()) {
|
||||
checkSystemName(state->symbols[attr.name], attr.pos);
|
||||
checkSystemType(state->symbols[attr.name], attr.pos);
|
||||
// FIXME: do getDerivations?
|
||||
}
|
||||
}
|
||||
|
||||
else if (name == "overlay")
|
||||
checkOverlay(name, vOutput, pos);
|
||||
|
||||
else if (name == "overlays") {
|
||||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs())
|
||||
checkOverlay(fmt("%s.%s", name, state->symbols[attr.name]),
|
||||
*attr.value, attr.pos);
|
||||
}
|
||||
|
||||
else if (name == "nixosModule")
|
||||
checkModule(name, vOutput, pos);
|
||||
|
||||
else if (name == "nixosModules") {
|
||||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs())
|
||||
checkModule(fmt("%s.%s", name, state->symbols[attr.name]),
|
||||
*attr.value, attr.pos);
|
||||
}
|
||||
|
||||
else if (name == "nixosConfigurations") {
|
||||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs())
|
||||
checkNixOSConfiguration(fmt("%s.%s", name, state->symbols[attr.name]),
|
||||
*attr.value, attr.pos);
|
||||
}
|
||||
|
||||
else if (name == "hydraJobs")
|
||||
checkHydraJobs(name, vOutput, pos);
|
||||
|
||||
else if (name == "defaultTemplate")
|
||||
checkTemplate(name, vOutput, pos);
|
||||
|
||||
else if (name == "templates") {
|
||||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs())
|
||||
checkTemplate(fmt("%s.%s", name, state->symbols[attr.name]),
|
||||
*attr.value, attr.pos);
|
||||
}
|
||||
|
||||
else if (name == "defaultBundler") {
|
||||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs()) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos)) {
|
||||
checkBundler(
|
||||
fmt("%s.%s", name, attr_name),
|
||||
*attr.value, attr.pos);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
else if (name == "bundlers") {
|
||||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs()) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos)) {
|
||||
state->forceAttrs(*attr.value, attr.pos, "");
|
||||
for (auto & attr2 : *attr.value->attrs()) {
|
||||
checkBundler(
|
||||
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
||||
*attr2.value, attr2.pos);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
else if (
|
||||
name == "lib"
|
||||
|| name == "darwinConfigurations"
|
||||
|| name == "darwinModules"
|
||||
|| name == "flakeModule"
|
||||
|| name == "flakeModules"
|
||||
|| name == "herculesCI"
|
||||
|| name == "homeConfigurations"
|
||||
|| name == "homeModule"
|
||||
|| name == "homeModules"
|
||||
|| name == "nixopsConfigurations"
|
||||
)
|
||||
// Known but unchecked community attribute
|
||||
;
|
||||
|
||||
else
|
||||
warn("unknown flake output '%s'", name);
|
||||
|
||||
} catch (Error & e) {
|
||||
e.addTrace(resolve(pos), HintFmt("while checking flake output '%s'", name));
|
||||
reportError(e);
|
||||
}
|
||||
|
||||
if (auto drv = flake_schemas::derivation(leaf)) {
|
||||
if (auto isFlakeCheck = leaf->maybeGetAttr("isFlakeCheck")) {
|
||||
if (isFlakeCheck->getBool()) {
|
||||
auto drvPath = drv->forceDerivation();
|
||||
drvPaths.push_back(DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePathRef(drvPath),
|
||||
.outputs = OutputsSpec::All { },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
[&](std::function<void(flake_schemas::ForEachChild)> forEachChild)
|
||||
{
|
||||
forEachChild([&](Symbol attrName, ref<eval_cache::AttrCursor> node, bool isLast)
|
||||
{
|
||||
visit(node);
|
||||
});
|
||||
},
|
||||
|
||||
[&](ref<eval_cache::AttrCursor> node, const std::vector<std::string> & systems) {
|
||||
for (auto & s : systems)
|
||||
omittedSystems.insert(s);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
flake_schemas::forEachOutput(inventory, [&](Symbol outputName, std::shared_ptr<eval_cache::AttrCursor> output, const std::string & doc, bool isLast)
|
||||
{
|
||||
if (output) {
|
||||
visit(ref(output));
|
||||
} else
|
||||
uncheckedOutputs.insert(state->symbols[outputName]);
|
||||
});
|
||||
|
||||
if (!uncheckedOutputs.empty())
|
||||
warn("The following flake outputs are unchecked: %s.",
|
||||
concatStringsSep(", ", uncheckedOutputs)); // FIXME: quote
|
||||
|
||||
if (build && !drvPaths.empty()) {
|
||||
Activity act(*logger, lvlInfo, actUnknown,
|
||||
fmt("running %d flake checks", drvPaths.size()));
|
||||
store->buildPaths(drvPaths);
|
||||
}
|
||||
|
||||
if (hasErrors)
|
||||
throw Error("some errors were encountered during the evaluation");
|
||||
|
||||
|
@ -808,7 +436,7 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
"Use '--all-systems' to check all.",
|
||||
concatStringsSep(", ", omittedSystems)
|
||||
);
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1092,7 +720,7 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun
|
|||
}
|
||||
};
|
||||
|
||||
struct CmdFlakeShow : FlakeCommand, MixJSON
|
||||
struct CmdFlakeShow : FlakeCommand, MixJSON, flake_schemas::MixFlakeSchemas
|
||||
{
|
||||
bool showLegacy = false;
|
||||
bool showAllSystems = false;
|
||||
|
@ -1125,267 +753,158 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
|
||||
void run(nix::ref<nix::Store> store) override
|
||||
{
|
||||
evalSettings.enableImportFromDerivation.setDefault(false);
|
||||
|
||||
auto state = getEvalState();
|
||||
auto flake = std::make_shared<LockedFlake>(lockFlake());
|
||||
auto localSystem = std::string(settings.thisSystem.get());
|
||||
|
||||
std::function<bool(
|
||||
eval_cache::AttrCursor & visitor,
|
||||
const std::vector<Symbol> &attrPath,
|
||||
const Symbol &attr)> hasContent;
|
||||
auto [cache, inventory] = flake_schemas::call(*state, flake, getDefaultFlakeSchemas());
|
||||
|
||||
// For frameworks it's important that structures are as lazy as possible
|
||||
// to prevent infinite recursions, performance issues and errors that
|
||||
// aren't related to the thing to evaluate. As a consequence, they have
|
||||
// to emit more attributes than strictly (sic) necessary.
|
||||
// However, these attributes with empty values are not useful to the user
|
||||
// so we omit them.
|
||||
hasContent = [&](
|
||||
eval_cache::AttrCursor & visitor,
|
||||
const std::vector<Symbol> &attrPath,
|
||||
const Symbol &attr) -> bool
|
||||
{
|
||||
auto attrPath2(attrPath);
|
||||
attrPath2.push_back(attr);
|
||||
auto attrPathS = state->symbols.resolve(attrPath2);
|
||||
const auto & attrName = state->symbols[attr];
|
||||
if (json) {
|
||||
std::function<void(ref<eval_cache::AttrCursor> node, nlohmann::json & obj)> visit;
|
||||
|
||||
auto visitor2 = visitor.getAttr(attrName);
|
||||
visit = [&](ref<eval_cache::AttrCursor> node, nlohmann::json & obj)
|
||||
{
|
||||
flake_schemas::visit(
|
||||
showAllSystems ? std::optional<std::string>() : localSystem,
|
||||
node,
|
||||
|
||||
try {
|
||||
if ((attrPathS[0] == "apps"
|
||||
|| attrPathS[0] == "checks"
|
||||
|| attrPathS[0] == "devShells"
|
||||
|| attrPathS[0] == "legacyPackages"
|
||||
|| attrPathS[0] == "packages")
|
||||
&& (attrPathS.size() == 1 || attrPathS.size() == 2)) {
|
||||
for (const auto &subAttr : visitor2->getAttrs()) {
|
||||
if (hasContent(*visitor2, attrPath2, subAttr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
[&](ref<eval_cache::AttrCursor> leaf)
|
||||
{
|
||||
obj.emplace("leaf", true);
|
||||
|
||||
if (auto what = flake_schemas::what(leaf))
|
||||
obj.emplace("what", what);
|
||||
|
||||
if (auto shortDescription = flake_schemas::shortDescription(leaf))
|
||||
obj.emplace("shortDescription", shortDescription);
|
||||
|
||||
if (auto drv = flake_schemas::derivation(leaf))
|
||||
obj.emplace("derivationName", drv->getAttr(state->sName)->getString());
|
||||
|
||||
// FIXME: add more stuff
|
||||
},
|
||||
|
||||
[&](std::function<void(flake_schemas::ForEachChild)> forEachChild)
|
||||
{
|
||||
auto children = nlohmann::json::object();
|
||||
forEachChild([&](Symbol attrName, ref<eval_cache::AttrCursor> node, bool isLast)
|
||||
{
|
||||
auto j = nlohmann::json::object();
|
||||
try {
|
||||
visit(node, j);
|
||||
} catch (EvalError & e) {
|
||||
// FIXME: make it a flake schema attribute whether to ignore evaluation errors.
|
||||
if (node->root->state.symbols[flake_schemas::toAttrPath(node)[0]] == "legacyPackages")
|
||||
j.emplace("failed", true);
|
||||
else
|
||||
throw;
|
||||
}
|
||||
children.emplace(state->symbols[attrName], std::move(j));
|
||||
});
|
||||
obj.emplace("children", std::move(children));
|
||||
},
|
||||
|
||||
[&](ref<eval_cache::AttrCursor> node, const std::vector<std::string> & systems)
|
||||
{
|
||||
obj.emplace("filtered", true);
|
||||
});
|
||||
};
|
||||
|
||||
auto res = nlohmann::json::object();
|
||||
|
||||
flake_schemas::forEachOutput(inventory, [&](Symbol outputName, std::shared_ptr<eval_cache::AttrCursor> output, const std::string & doc, bool isLast)
|
||||
{
|
||||
auto j = nlohmann::json::object();
|
||||
|
||||
if (!showLegacy && state->symbols[outputName] == "legacyPackages") {
|
||||
j.emplace("skipped", true);
|
||||
} else if (output) {
|
||||
j.emplace("doc", doc);
|
||||
auto j2 = nlohmann::json::object();
|
||||
visit(ref(output), j2);
|
||||
j.emplace("output", std::move(j2));
|
||||
} else
|
||||
j.emplace("unknown", true);
|
||||
|
||||
res.emplace(state->symbols[outputName], j);
|
||||
});
|
||||
|
||||
logger->cout("%s", res.dump());
|
||||
}
|
||||
|
||||
else {
|
||||
logger->cout(ANSI_BOLD "%s" ANSI_NORMAL, flake->flake.lockedRef);
|
||||
|
||||
std::function<void(
|
||||
ref<eval_cache::AttrCursor> node,
|
||||
const std::string & headerPrefix,
|
||||
const std::string & prevPrefix)> visit;
|
||||
|
||||
visit = [&](
|
||||
ref<eval_cache::AttrCursor> node,
|
||||
const std::string & headerPrefix,
|
||||
const std::string & prevPrefix)
|
||||
{
|
||||
flake_schemas::visit(
|
||||
showAllSystems ? std::optional<std::string>() : localSystem,
|
||||
node,
|
||||
|
||||
[&](ref<eval_cache::AttrCursor> leaf)
|
||||
{
|
||||
auto s = headerPrefix;
|
||||
|
||||
if (auto what = flake_schemas::what(leaf))
|
||||
s += fmt(": %s", *what);
|
||||
|
||||
if (auto drv = flake_schemas::derivation(leaf))
|
||||
s += fmt(ANSI_ITALIC " [%s]" ANSI_NORMAL, drv->getAttr(state->sName)->getString());
|
||||
|
||||
logger->cout(s);
|
||||
},
|
||||
|
||||
[&](std::function<void(flake_schemas::ForEachChild)> forEachChild)
|
||||
{
|
||||
logger->cout(headerPrefix);
|
||||
forEachChild([&](Symbol attrName, ref<eval_cache::AttrCursor> node, bool isLast)
|
||||
{
|
||||
visit(node,
|
||||
fmt(ANSI_GREEN "%s%s" ANSI_NORMAL ANSI_BOLD "%s" ANSI_NORMAL, prevPrefix,
|
||||
isLast ? treeLast : treeConn, state->symbols[attrName]),
|
||||
prevPrefix + (isLast ? treeNull : treeLine));
|
||||
});
|
||||
},
|
||||
|
||||
[&](ref<eval_cache::AttrCursor> node, const std::vector<std::string> & systems)
|
||||
{
|
||||
logger->cout(fmt("%s " ANSI_WARNING "omitted" ANSI_NORMAL " (use '--all-systems' to show)", headerPrefix));
|
||||
});
|
||||
};
|
||||
|
||||
flake_schemas::forEachOutput(inventory, [&](Symbol outputName, std::shared_ptr<eval_cache::AttrCursor> output, const std::string & doc, bool isLast)
|
||||
{
|
||||
auto headerPrefix = fmt(
|
||||
ANSI_GREEN "%s" ANSI_NORMAL ANSI_BOLD "%s" ANSI_NORMAL,
|
||||
isLast ? treeLast : treeConn, state->symbols[outputName]);
|
||||
|
||||
if (!showLegacy && state->symbols[outputName] == "legacyPackages") {
|
||||
logger->cout(headerPrefix);
|
||||
logger->cout(
|
||||
ANSI_GREEN "%s" "%s" ANSI_NORMAL ANSI_ITALIC "%s" ANSI_NORMAL,
|
||||
isLast ? treeNull : treeLine,
|
||||
treeLast,
|
||||
"(skipped; use '--legacy' to show)");
|
||||
} else if (output) {
|
||||
visit(ref(output), headerPrefix, isLast ? treeNull : treeLine);
|
||||
} else {
|
||||
logger->cout(headerPrefix);
|
||||
logger->cout(
|
||||
ANSI_GREEN "%s" "%s" ANSI_NORMAL ANSI_ITALIC "%s" ANSI_NORMAL,
|
||||
isLast ? treeNull : treeLine,
|
||||
treeLast,
|
||||
"(unknown flake output)");
|
||||
}
|
||||
|
||||
if ((attrPathS.size() == 1)
|
||||
&& (attrPathS[0] == "formatter"
|
||||
|| attrPathS[0] == "nixosConfigurations"
|
||||
|| attrPathS[0] == "nixosModules"
|
||||
|| attrPathS[0] == "overlays"
|
||||
)) {
|
||||
for (const auto &subAttr : visitor2->getAttrs()) {
|
||||
if (hasContent(*visitor2, attrPath2, subAttr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we don't recognize it, it's probably content
|
||||
return true;
|
||||
} catch (EvalError & e) {
|
||||
// Some attrs may contain errors, e.g. legacyPackages of
|
||||
// nixpkgs. We still want to recurse into it, instead of
|
||||
// skipping it at all.
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
std::function<nlohmann::json(
|
||||
eval_cache::AttrCursor & visitor,
|
||||
const std::vector<Symbol> & attrPath,
|
||||
const std::string & headerPrefix,
|
||||
const std::string & nextPrefix)> visit;
|
||||
|
||||
visit = [&](
|
||||
eval_cache::AttrCursor & visitor,
|
||||
const std::vector<Symbol> & attrPath,
|
||||
const std::string & headerPrefix,
|
||||
const std::string & nextPrefix)
|
||||
-> nlohmann::json
|
||||
{
|
||||
auto j = nlohmann::json::object();
|
||||
|
||||
auto attrPathS = state->symbols.resolve(attrPath);
|
||||
|
||||
Activity act(*logger, lvlInfo, actUnknown,
|
||||
fmt("evaluating '%s'", concatStringsSep(".", attrPathS)));
|
||||
|
||||
try {
|
||||
auto recurse = [&]()
|
||||
{
|
||||
if (!json)
|
||||
logger->cout("%s", headerPrefix);
|
||||
std::vector<Symbol> attrs;
|
||||
for (const auto &attr : visitor.getAttrs()) {
|
||||
if (hasContent(visitor, attrPath, attr))
|
||||
attrs.push_back(attr);
|
||||
}
|
||||
|
||||
for (const auto & [i, attr] : enumerate(attrs)) {
|
||||
const auto & attrName = state->symbols[attr];
|
||||
bool last = i + 1 == attrs.size();
|
||||
auto visitor2 = visitor.getAttr(attrName);
|
||||
auto attrPath2(attrPath);
|
||||
attrPath2.push_back(attr);
|
||||
auto j2 = visit(*visitor2, attrPath2,
|
||||
fmt(ANSI_GREEN "%s%s" ANSI_NORMAL ANSI_BOLD "%s" ANSI_NORMAL, nextPrefix, last ? treeLast : treeConn, attrName),
|
||||
nextPrefix + (last ? treeNull : treeLine));
|
||||
if (json) j.emplace(attrName, std::move(j2));
|
||||
}
|
||||
};
|
||||
|
||||
auto showDerivation = [&]()
|
||||
{
|
||||
auto name = visitor.getAttr(state->sName)->getString();
|
||||
if (json) {
|
||||
std::optional<std::string> description;
|
||||
if (auto aMeta = visitor.maybeGetAttr(state->sMeta)) {
|
||||
if (auto aDescription = aMeta->maybeGetAttr(state->sDescription))
|
||||
description = aDescription->getString();
|
||||
}
|
||||
j.emplace("type", "derivation");
|
||||
j.emplace("name", name);
|
||||
if (description)
|
||||
j.emplace("description", *description);
|
||||
} else {
|
||||
logger->cout("%s: %s '%s'",
|
||||
headerPrefix,
|
||||
attrPath.size() == 2 && attrPathS[0] == "devShell" ? "development environment" :
|
||||
attrPath.size() >= 2 && attrPathS[0] == "devShells" ? "development environment" :
|
||||
attrPath.size() == 3 && attrPathS[0] == "checks" ? "derivation" :
|
||||
attrPath.size() >= 1 && attrPathS[0] == "hydraJobs" ? "derivation" :
|
||||
"package",
|
||||
name);
|
||||
}
|
||||
};
|
||||
|
||||
if (attrPath.size() == 0
|
||||
|| (attrPath.size() == 1 && (
|
||||
attrPathS[0] == "defaultPackage"
|
||||
|| attrPathS[0] == "devShell"
|
||||
|| attrPathS[0] == "formatter"
|
||||
|| attrPathS[0] == "nixosConfigurations"
|
||||
|| attrPathS[0] == "nixosModules"
|
||||
|| attrPathS[0] == "defaultApp"
|
||||
|| attrPathS[0] == "templates"
|
||||
|| attrPathS[0] == "overlays"))
|
||||
|| ((attrPath.size() == 1 || attrPath.size() == 2)
|
||||
&& (attrPathS[0] == "checks"
|
||||
|| attrPathS[0] == "packages"
|
||||
|| attrPathS[0] == "devShells"
|
||||
|| attrPathS[0] == "apps"))
|
||||
)
|
||||
{
|
||||
recurse();
|
||||
}
|
||||
|
||||
else if (
|
||||
(attrPath.size() == 2 && (attrPathS[0] == "defaultPackage" || attrPathS[0] == "devShell" || attrPathS[0] == "formatter"))
|
||||
|| (attrPath.size() == 3 && (attrPathS[0] == "checks" || attrPathS[0] == "packages" || attrPathS[0] == "devShells"))
|
||||
)
|
||||
{
|
||||
if (!showAllSystems && std::string(attrPathS[1]) != localSystem) {
|
||||
if (!json)
|
||||
logger->cout(fmt("%s " ANSI_WARNING "omitted" ANSI_NORMAL " (use '--all-systems' to show)", headerPrefix));
|
||||
else {
|
||||
logger->warn(fmt("%s omitted (use '--all-systems' to show)", concatStringsSep(".", attrPathS)));
|
||||
}
|
||||
} else {
|
||||
if (visitor.isDerivation())
|
||||
showDerivation();
|
||||
else
|
||||
throw Error("expected a derivation");
|
||||
}
|
||||
}
|
||||
|
||||
else if (attrPath.size() > 0 && attrPathS[0] == "hydraJobs") {
|
||||
if (visitor.isDerivation())
|
||||
showDerivation();
|
||||
else
|
||||
recurse();
|
||||
}
|
||||
|
||||
else if (attrPath.size() > 0 && attrPathS[0] == "legacyPackages") {
|
||||
if (attrPath.size() == 1)
|
||||
recurse();
|
||||
else if (!showLegacy){
|
||||
if (!json)
|
||||
logger->cout(fmt("%s " ANSI_WARNING "omitted" ANSI_NORMAL " (use '--legacy' to show)", headerPrefix));
|
||||
else {
|
||||
logger->warn(fmt("%s omitted (use '--legacy' to show)", concatStringsSep(".", attrPathS)));
|
||||
}
|
||||
} else if (!showAllSystems && std::string(attrPathS[1]) != localSystem) {
|
||||
if (!json)
|
||||
logger->cout(fmt("%s " ANSI_WARNING "omitted" ANSI_NORMAL " (use '--all-systems' to show)", headerPrefix));
|
||||
else {
|
||||
logger->warn(fmt("%s omitted (use '--all-systems' to show)", concatStringsSep(".", attrPathS)));
|
||||
}
|
||||
} else {
|
||||
if (visitor.isDerivation())
|
||||
showDerivation();
|
||||
else if (attrPath.size() <= 2)
|
||||
// FIXME: handle recurseIntoAttrs
|
||||
recurse();
|
||||
}
|
||||
}
|
||||
|
||||
else if (
|
||||
(attrPath.size() == 2 && attrPathS[0] == "defaultApp") ||
|
||||
(attrPath.size() == 3 && attrPathS[0] == "apps"))
|
||||
{
|
||||
auto aType = visitor.maybeGetAttr("type");
|
||||
if (!aType || aType->getString() != "app")
|
||||
state->error<EvalError>("not an app definition").debugThrow();
|
||||
if (json) {
|
||||
j.emplace("type", "app");
|
||||
} else {
|
||||
logger->cout("%s: app", headerPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
else if (
|
||||
(attrPath.size() == 1 && attrPathS[0] == "defaultTemplate") ||
|
||||
(attrPath.size() == 2 && attrPathS[0] == "templates"))
|
||||
{
|
||||
auto description = visitor.getAttr("description")->getString();
|
||||
if (json) {
|
||||
j.emplace("type", "template");
|
||||
j.emplace("description", description);
|
||||
} else {
|
||||
logger->cout("%s: template: " ANSI_BOLD "%s" ANSI_NORMAL, headerPrefix, description);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
auto [type, description] =
|
||||
(attrPath.size() == 1 && attrPathS[0] == "overlay")
|
||||
|| (attrPath.size() == 2 && attrPathS[0] == "overlays") ? std::make_pair("nixpkgs-overlay", "Nixpkgs overlay") :
|
||||
attrPath.size() == 2 && attrPathS[0] == "nixosConfigurations" ? std::make_pair("nixos-configuration", "NixOS configuration") :
|
||||
(attrPath.size() == 1 && attrPathS[0] == "nixosModule")
|
||||
|| (attrPath.size() == 2 && attrPathS[0] == "nixosModules") ? std::make_pair("nixos-module", "NixOS module") :
|
||||
std::make_pair("unknown", "unknown");
|
||||
if (json) {
|
||||
j.emplace("type", type);
|
||||
} else {
|
||||
logger->cout("%s: " ANSI_WARNING "%s" ANSI_NORMAL, headerPrefix, description);
|
||||
}
|
||||
}
|
||||
} catch (EvalError & e) {
|
||||
if (!(attrPath.size() > 0 && attrPathS[0] == "legacyPackages"))
|
||||
throw;
|
||||
}
|
||||
|
||||
return j;
|
||||
};
|
||||
|
||||
auto cache = openEvalCache(*state, flake);
|
||||
|
||||
auto j = visit(*cache->getRoot(), {}, fmt(ANSI_BOLD "%s" ANSI_NORMAL, flake->flake.lockedRef), "");
|
||||
if (json)
|
||||
logger->cout("%s", j.dump());
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -55,3 +55,9 @@ $(d)/main.cc: \
|
|||
$(d)/profile.cc: $(d)/profile.md
|
||||
|
||||
$(d)/profile.md: $(d)/profiles.md.gen.hh
|
||||
|
||||
src/nix/flake.cc: src/nix/call-flake-schemas.nix.gen.hh src/nix/builtin-flake-schemas.nix.gen.hh
|
||||
|
||||
src/nix/builtin-flake-schemas.nix: $(default_flake_schemas)/flake.nix
|
||||
$(trace-gen) cp $^ $@
|
||||
@chmod +w $@
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue