nix flake show: Ignore empty attrsets

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.
This commit is contained in:
Robert Hensing 2023-01-31 18:16:31 +01:00
parent c9b9260f34
commit 60d48eda23
2 changed files with 88 additions and 1 deletions

View file

@ -1002,6 +1002,61 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
auto flake = std::make_shared<LockedFlake>(lockFlake()); auto flake = std::make_shared<LockedFlake>(lockFlake());
auto localSystem = std::string(settings.thisSystem.get()); auto localSystem = std::string(settings.thisSystem.get());
std::function<bool(
eval_cache::AttrCursor & visitor,
const std::vector<Symbol> &attrPath,
const Symbol &attr)> hasContent;
// 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];
auto visitor2 = visitor.getAttr(attrName);
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;
}
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;
};
std::function<nlohmann::json( std::function<nlohmann::json(
eval_cache::AttrCursor & visitor, eval_cache::AttrCursor & visitor,
const std::vector<Symbol> & attrPath, const std::vector<Symbol> & attrPath,
@ -1027,7 +1082,12 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
{ {
if (!json) if (!json)
logger->cout("%s", headerPrefix); logger->cout("%s", headerPrefix);
auto attrs = visitor.getAttrs(); 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)) { for (const auto & [i, attr] : enumerate(attrs)) {
const auto & attrName = state->symbols[attr]; const auto & attrName = state->symbols[attr];
bool last = i + 1 == attrs.size(); bool last = i + 1 == attrs.size();

View file

@ -37,3 +37,30 @@ in
assert show_output.legacyPackages.${builtins.currentSystem}.hello.name == "simple"; assert show_output.legacyPackages.${builtins.currentSystem}.hello.name == "simple";
true true
' '
# Test that attributes are only reported when they have actual content
cat >flake.nix <<EOF
{
description = "Bla bla";
outputs = inputs: rec {
apps.$system = { };
checks.$system = { };
devShells.$system = { };
legacyPackages.$system = { };
packages.$system = { };
packages.someOtherSystem = { };
formatter = { };
nixosConfigurations = { };
nixosModules = { };
};
}
EOF
nix flake show --json --all-systems > show-output.json
nix eval --impure --expr '
let show_output = builtins.fromJSON (builtins.readFile ./show-output.json);
in
assert show_output == { };
true
'