Skip to content

Commit

Permalink
Make nix path-info --json return an object not array
Browse files Browse the repository at this point in the history
Before it returned a list of JSON objects with store object information,
including the path in each object. Now, it maps the paths to JSON
objects with the metadata sans path.

This matches how `nix derivation show` works.

In the process of doing this, shuffle around the `ValidPathInfo` JSON
rendering logic into what I hope is a better state:

`Store::pathInfoToJSON` was a rather baroque functions, being full of
parameters to support both parsed derivations and `nix path-info`. The
common core of each, a simple `UnkeyedValidPathInfo::toJSON` function,
is factored out, but the rest of the logic is just duplicated and then
specialized to its use-case (at which point it is no longer that
duplicated).

This keeps the human oriented CLI logic (which is currently stable) and
the core domain logic (export reference graphs with structured attrs,
which is stable), separate, which I think is better.

Quite hillariously, none of our existing tests caught this change to
`path-info --json` though they did use it. So just new tests need to be
added.
  • Loading branch information
Ericson2314 committed Oct 25, 2023
1 parent 5ac87a7 commit e85635e
Show file tree
Hide file tree
Showing 12 changed files with 257 additions and 125 deletions.
33 changes: 33 additions & 0 deletions doc/manual/src/release-notes/rl-next.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,36 @@
- `nix-shell` shebang lines now support single-quoted arguments.

- `builtins.fetchTree` is now marked as stable.

- [`nix path-info --json`](@docroot@/command-ref/new-cli/nix3-path-info.md)
(experimental) now returns a JSON map rather than JSON list.
The path field of each object has instead become the key in th outer map, since it is unique.

- Old way:

```json
[
{
path = "/nix/store/8fv91097mbh5049i9rglc73dx6kjg3qk-bash-5.2-p15",
// ...
},
path = "/nix/store/wffw7l0alvs3iw94cbgi1gmmbmw99sqb-home-manager-path",
// ...
}
]
```

- New way

```json
{
"/nix/store/8fv91097mbh5049i9rglc73dx6kjg3qk-bash-5.2-p15": {
// ...
},
"/nix/store/wffw7l0alvs3iw94cbgi1gmmbmw99sqb-home-manager-path": {
// ...
}
}
```

This makes it match `nix derivation show`, which also maps store paths to information.
21 changes: 21 additions & 0 deletions src/libstore/nar-info.cc
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,25 @@ std::string NarInfo::to_string(const Store & store) const
return res;
}

nlohmann::json NarInfo::toJSON(
const Store & store,
bool includeImpureInfo,
HashFormat hashFormat) const
{
using nlohmann::json;

auto jsonObject = ValidPathInfo::toJSON(store, includeImpureInfo, hashFormat);

if (includeImpureInfo) {
if (!url.empty())
jsonObject["url"] = url;
if (fileHash)
jsonObject["downloadHash"] = fileHash->to_string(hashFormat, true);
if (fileSize)
jsonObject["downloadSize"] = fileSize;
}

return jsonObject;
}

}
8 changes: 8 additions & 0 deletions src/libstore/nar-info.hh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ struct NarInfo : ValidPathInfo
NarInfo(const Store & store, const std::string & s, const std::string & whence);

std::string to_string(const Store & store) const;

nlohmann::json toJSON(
const Store & store,
bool includeImpureInfo,
HashFormat hashFormat) const override;
static NarInfo fromJSON(
const Store & store,
const nlohmann::json & json);
};

}
37 changes: 35 additions & 2 deletions src/libstore/parsed-derivations.cc
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,39 @@ bool ParsedDerivation::useUidRange() const

static std::regex shVarName("[A-Za-z_][A-Za-z0-9_]*");

/**
* Write a JSON representation of store object metadata, such as the
* hash and the references.
*/
static nlohmann::json pathInfoToJSON(
Store & store,
const StorePathSet & storePaths)
{
nlohmann::json::array_t jsonList = nlohmann::json::array();

for (auto & storePath : storePaths) {
auto info = store.queryPathInfo(storePath);

auto & jsonPath = jsonList.emplace_back(
info->toJSON(store, false, HashFormat::Base32));

// Add the path to the object whose metadata we are including.
jsonPath["path"] = store.printStorePath(storePath);

jsonPath["closureSize"] = ({
uint64_t totalNarSize = 0;
StorePathSet closure;
store.computeFSClosure(info->path, closure, false, false);
for (auto & p : closure) {
auto info = store.queryPathInfo(p);
totalNarSize += info->narSize;
}
totalNarSize;
});
}
return jsonList;
}

std::optional<nlohmann::json> ParsedDerivation::prepareStructuredAttrs(Store & store, const StorePathSet & inputPaths)
{
auto structuredAttrs = getStructuredAttrs();
Expand All @@ -152,8 +185,8 @@ std::optional<nlohmann::json> ParsedDerivation::prepareStructuredAttrs(Store & s
StorePathSet storePaths;
for (auto & p : *i)
storePaths.insert(store.parseStorePath(p.get<std::string>()));
json[i.key()] = store.pathInfoToJSON(
store.exportReferences(storePaths, inputPaths), false, true);
json[i.key()] = pathInfoToJSON(store,
store.exportReferences(storePaths, inputPaths));
}
}

Expand Down
44 changes: 44 additions & 0 deletions src/libstore/path-info.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include <nlohmann/json.hpp>

#include "path-info.hh"
#include "store-api.hh"

Expand Down Expand Up @@ -144,4 +146,46 @@ ValidPathInfo::ValidPathInfo(
}, std::move(ca).raw);
}


nlohmann::json UnkeyedValidPathInfo::toJSON(
const Store & store,
bool includeImpureInfo,
HashFormat hashFormat) const
{
using nlohmann::json;

auto jsonObject = json::object();

jsonObject["valid"] = true;
jsonObject["narHash"] = narHash.to_string(hashFormat, true);
jsonObject["narSize"] = narSize;

{
auto& jsonRefs = (jsonObject["references"] = json::array());
for (auto & ref : references)
jsonRefs.emplace_back(store.printStorePath(ref));
}

if (ca)
jsonObject["ca"] = renderContentAddress(ca);

if (includeImpureInfo) {
if (deriver)
jsonObject["deriver"] = store.printStorePath(*deriver);

if (registrationTime)
jsonObject["registrationTime"] = registrationTime;

if (ultimate)
jsonObject["ultimate"] = ultimate;

if (!sigs.empty()) {
for (auto & sig : sigs)
jsonObject["signatures"].push_back(sig);
}
}

return jsonObject;
}

}
12 changes: 12 additions & 0 deletions src/libstore/path-info.hh
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ struct UnkeyedValidPathInfo
DECLARE_CMP(UnkeyedValidPathInfo);

virtual ~UnkeyedValidPathInfo() { }

/**
* @param includeImpureInfo If true, variable elements such as the
* registration time are included.
*/
virtual nlohmann::json toJSON(
const Store & store,
bool includeImpureInfo,
HashFormat hashFormat) const;
static UnkeyedValidPathInfo fromJSON(
const Store & store,
const nlohmann::json & json);
};

struct ValidPathInfo : UnkeyedValidPathInfo {
Expand Down
90 changes: 0 additions & 90 deletions src/libstore/store-api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -949,96 +949,6 @@ StorePathSet Store::exportReferences(const StorePathSet & storePaths, const Stor
return paths;
}

json Store::pathInfoToJSON(const StorePathSet & storePaths,
bool includeImpureInfo, bool showClosureSize,
HashFormat hashFormat,
AllowInvalidFlag allowInvalid)
{
json::array_t jsonList = json::array();

for (auto & storePath : storePaths) {
auto& jsonPath = jsonList.emplace_back(json::object());

try {
auto info = queryPathInfo(storePath);

jsonPath["path"] = printStorePath(info->path);
jsonPath["valid"] = true;
jsonPath["narHash"] = info->narHash.to_string(hashFormat, true);
jsonPath["narSize"] = info->narSize;

{
auto& jsonRefs = (jsonPath["references"] = json::array());
for (auto & ref : info->references)
jsonRefs.emplace_back(printStorePath(ref));
}

if (info->ca)
jsonPath["ca"] = renderContentAddress(info->ca);

std::pair<uint64_t, uint64_t> closureSizes;

if (showClosureSize) {
closureSizes = getClosureSize(info->path);
jsonPath["closureSize"] = closureSizes.first;
}

if (includeImpureInfo) {

if (info->deriver)
jsonPath["deriver"] = printStorePath(*info->deriver);

if (info->registrationTime)
jsonPath["registrationTime"] = info->registrationTime;

if (info->ultimate)
jsonPath["ultimate"] = info->ultimate;

if (!info->sigs.empty()) {
for (auto & sig : info->sigs)
jsonPath["signatures"].push_back(sig);
}

auto narInfo = std::dynamic_pointer_cast<const NarInfo>(
std::shared_ptr<const ValidPathInfo>(info));

if (narInfo) {
if (!narInfo->url.empty())
jsonPath["url"] = narInfo->url;
if (narInfo->fileHash)
jsonPath["downloadHash"] = narInfo->fileHash->to_string(hashFormat, true);
if (narInfo->fileSize)
jsonPath["downloadSize"] = narInfo->fileSize;
if (showClosureSize)
jsonPath["closureDownloadSize"] = closureSizes.second;
}
}

} catch (InvalidPath &) {
jsonPath["path"] = printStorePath(storePath);
jsonPath["valid"] = false;
}
}
return jsonList;
}


std::pair<uint64_t, uint64_t> Store::getClosureSize(const StorePath & storePath)
{
uint64_t totalNarSize = 0, totalDownloadSize = 0;
StorePathSet closure;
computeFSClosure(storePath, closure, false, false);
for (auto & p : closure) {
auto info = queryPathInfo(p);
totalNarSize += info->narSize;
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(
std::shared_ptr<const ValidPathInfo>(info));
if (narInfo)
totalDownloadSize += narInfo->fileSize;
}
return {totalNarSize, totalDownloadSize};
}


const Store::Stats & Store::getStats()
{
Expand Down
23 changes: 0 additions & 23 deletions src/libstore/store-api.hh
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ typedef std::map<std::string, StorePath> OutputPathMap;

enum CheckSigsFlag : bool { NoCheckSigs = false, CheckSigs = true };
enum SubstituteFlag : bool { NoSubstitute = false, Substitute = true };
enum AllowInvalidFlag : bool { DisallowInvalid = false, AllowInvalid = true };

/**
* Magic header of exportPath() output (obsolete).
Expand Down Expand Up @@ -665,28 +664,6 @@ public:
std::string makeValidityRegistration(const StorePathSet & paths,
bool showDerivers, bool showHash);

/**
* Write a JSON representation of store path metadata, such as the
* hash and the references.
*
* @param includeImpureInfo If true, variable elements such as the
* registration time are included.
*
* @param showClosureSize If true, the closure size of each path is
* included.
*/
nlohmann::json pathInfoToJSON(const StorePathSet & storePaths,
bool includeImpureInfo, bool showClosureSize,
HashFormat hashFormat = HashFormat::Base32,
AllowInvalidFlag allowInvalid = DisallowInvalid);

/**
* @return the size of the closure of the specified path, that is,
* the sum of the size of the NAR serialisation of each path in the
* closure.
*/
std::pair<uint64_t, uint64_t> getClosureSize(const StorePath & storePath);

/**
* Optimise the disk space usage of the Nix store by hard-linking files
* with the same contents.
Expand Down

0 comments on commit e85635e

Please sign in to comment.