1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-12-11 11:31:03 +01:00

Introduce --json-format for nix path-info

As discussed today at great length in the Nix meeting, we don't want to
break the format, but we also don't want to impede the improvement of
JSON formats. The solution is to add a new flag for control the output
format.

Note that prior to the release, we may want to replace `--json
--json-format N` with `--json=N`, but this is being left for a separate
PR, as we don't yet have `=` support for CLI flags.
This commit is contained in:
John Ericson 2025-12-03 17:07:00 -05:00
parent 69920f9557
commit 1ad13a1423
36 changed files with 464 additions and 132 deletions

View file

@ -25,7 +25,8 @@ struct UnkeyedNarInfo : virtual UnkeyedValidPathInfo
// TODO libc++ 16 (used by darwin) missing `std::optional::operator <=>`, can't do yet
// auto operator <=>(const NarInfo &) const = default;
nlohmann::json toJSON(const StoreDirConfig * store, bool includeImpureInfo) const override;
nlohmann::json
toJSON(const StoreDirConfig * store, bool includeImpureInfo, PathInfoJsonFormat format) const override;
static UnkeyedNarInfo fromJSON(const StoreDirConfig * store, const nlohmann::json & json);
};

View file

@ -14,6 +14,22 @@ namespace nix {
class Store;
struct StoreDirConfig;
/**
* JSON format version for path info output.
*/
enum class PathInfoJsonFormat {
/// Legacy format with string hashes and full store paths
V1 = 1,
/// New format with structured hashes and store path base names
V2 = 2,
};
/**
* Convert an integer version number to PathInfoJsonFormat.
* Throws Error if the version is not supported.
*/
PathInfoJsonFormat parsePathInfoJsonFormat(uint64_t version);
struct SubstitutablePathInfo
{
std::optional<StorePath> deriver;
@ -114,10 +130,16 @@ struct UnkeyedValidPathInfo
virtual ~UnkeyedValidPathInfo() {}
/**
* @param store If non-null, store paths are rendered as full paths.
* If null, store paths are rendered as base names.
* @param includeImpureInfo If true, variable elements such as the
* registration time are included.
* registration time are included.
* @param format JSON format version. Version 1 uses string hashes and
* string content addresses. Version 2 uses structured
* hashes and structured content addresses.
*/
virtual nlohmann::json toJSON(const StoreDirConfig * store, bool includeImpureInfo) const;
virtual nlohmann::json
toJSON(const StoreDirConfig * store, bool includeImpureInfo, PathInfoJsonFormat format) const;
static UnkeyedValidPathInfo fromJSON(const StoreDirConfig * store, const nlohmann::json & json);
};
@ -196,5 +218,6 @@ using ValidPathInfos = std::map<StorePath, ValidPathInfo>;
} // namespace nix
JSON_IMPL(nix::PathInfoJsonFormat)
JSON_IMPL(nix::UnkeyedValidPathInfo)
JSON_IMPL(nix::ValidPathInfo)

View file

@ -132,19 +132,24 @@ std::string NarInfo::to_string(const StoreDirConfig & store) const
return res;
}
nlohmann::json UnkeyedNarInfo::toJSON(const StoreDirConfig * store, bool includeImpureInfo) const
nlohmann::json
UnkeyedNarInfo::toJSON(const StoreDirConfig * store, bool includeImpureInfo, PathInfoJsonFormat format) const
{
using nlohmann::json;
auto jsonObject = UnkeyedValidPathInfo::toJSON(store, includeImpureInfo);
auto jsonObject = UnkeyedValidPathInfo::toJSON(store, includeImpureInfo, format);
if (includeImpureInfo) {
if (!url.empty())
jsonObject["url"] = url;
if (!compression.empty())
jsonObject["compression"] = compression;
if (fileHash)
jsonObject["downloadHash"] = *fileHash;
if (fileHash) {
if (format == PathInfoJsonFormat::V1)
jsonObject["downloadHash"] = fileHash->to_string(HashFormat::SRI, true);
else
jsonObject["downloadHash"] = *fileHash;
}
if (fileSize)
jsonObject["downloadSize"] = fileSize;
}
@ -154,20 +159,26 @@ nlohmann::json UnkeyedNarInfo::toJSON(const StoreDirConfig * store, bool include
UnkeyedNarInfo UnkeyedNarInfo::fromJSON(const StoreDirConfig * store, const nlohmann::json & json)
{
using nlohmann::detail::value_t;
UnkeyedNarInfo res{UnkeyedValidPathInfo::fromJSON(store, json)};
auto & obj = getObject(json);
PathInfoJsonFormat format = PathInfoJsonFormat::V1;
if (auto * version = optionalValueAt(obj, "version"))
format = *version;
if (auto * url = get(obj, "url"))
res.url = getString(*url);
if (auto * compression = get(obj, "compression"))
res.compression = getString(*compression);
if (auto * downloadHash = get(obj, "downloadHash"))
res.fileHash = *downloadHash;
if (auto * downloadHash = get(obj, "downloadHash")) {
if (format == PathInfoJsonFormat::V1)
res.fileHash = Hash::parseSRI(getString(*downloadHash));
else
res.fileHash = *downloadHash;
}
if (auto * downloadSize = get(obj, "downloadSize"))
res.fileSize = getUnsigned(*downloadSize);
@ -188,7 +199,7 @@ UnkeyedNarInfo adl_serializer<UnkeyedNarInfo>::from_json(const json & json)
void adl_serializer<UnkeyedNarInfo>::to_json(json & json, const UnkeyedNarInfo & c)
{
json = c.toJSON(nullptr, true);
json = c.toJSON(nullptr, true, PathInfoJsonFormat::V2);
}
} // namespace nlohmann

View file

@ -8,6 +8,18 @@
namespace nix {
PathInfoJsonFormat parsePathInfoJsonFormat(uint64_t version)
{
switch (version) {
case 1:
return PathInfoJsonFormat::V1;
case 2:
return PathInfoJsonFormat::V2;
default:
throw Error("unsupported path info JSON format version %d; supported versions are 1 and 2", version);
}
}
GENERATE_CMP_EXT(
,
std::weak_ordering,
@ -149,31 +161,45 @@ ValidPathInfo ValidPathInfo::makeFromCA(
return res;
}
nlohmann::json UnkeyedValidPathInfo::toJSON(const StoreDirConfig * store, bool includeImpureInfo) const
nlohmann::json
UnkeyedValidPathInfo::toJSON(const StoreDirConfig * store, bool includeImpureInfo, PathInfoJsonFormat format) const
{
using nlohmann::json;
if (format == PathInfoJsonFormat::V1)
assert(store);
auto jsonObject = json::object();
jsonObject["version"] = 2;
jsonObject["version"] = format;
jsonObject["narHash"] = format == PathInfoJsonFormat::V1
? static_cast<json>(narHash.to_string(HashFormat::SRI, true))
: static_cast<json>(narHash);
jsonObject["narHash"] = narHash;
jsonObject["narSize"] = narSize;
{
auto & jsonRefs = jsonObject["references"] = json::array();
for (auto & ref : references)
jsonRefs.emplace_back(store ? static_cast<json>(store->printStorePath(ref)) : static_cast<json>(ref));
jsonRefs.emplace_back(
format == PathInfoJsonFormat::V1 ? static_cast<json>(store->printStorePath(ref))
: static_cast<json>(ref));
}
jsonObject["ca"] = ca;
if (format == PathInfoJsonFormat::V1)
jsonObject["ca"] = ca ? static_cast<json>(renderContentAddress(*ca)) : static_cast<json>(nullptr);
else
jsonObject["ca"] = ca;
if (includeImpureInfo) {
jsonObject["deriver"] = deriver ? (store ? static_cast<json>(std::optional{store->printStorePath(*deriver)})
: static_cast<json>(std::optional{*deriver}))
: static_cast<json>(std::optional<StorePath>{});
jsonObject["registrationTime"] = registrationTime ? (std::optional{registrationTime}) : std::nullopt;
if (format == PathInfoJsonFormat::V1) {
jsonObject["deriver"] =
deriver ? static_cast<json>(store->printStorePath(*deriver)) : static_cast<json>(nullptr);
} else {
jsonObject["deriver"] = deriver;
}
jsonObject["registrationTime"] = registrationTime ? std::optional{registrationTime} : std::nullopt;
jsonObject["ultimate"] = ultimate;
@ -193,34 +219,51 @@ UnkeyedValidPathInfo UnkeyedValidPathInfo::fromJSON(const StoreDirConfig * store
auto & json = getObject(_json);
{
auto version = getUnsigned(valueAt(json, "version"));
if (version != 2)
throw Error("Unsupported path info JSON format version %d, only version 2 is currently supported", version);
}
PathInfoJsonFormat format = PathInfoJsonFormat::V1;
if (auto * version = optionalValueAt(json, "version"))
format = *version;
if (format == PathInfoJsonFormat::V1)
assert(store);
if (format == PathInfoJsonFormat::V1)
res.narHash = Hash::parseSRI(getString(valueAt(json, "narHash")));
else
res.narHash = valueAt(json, "narHash");
res.narHash = valueAt(json, "narHash");
res.narSize = getUnsigned(valueAt(json, "narSize"));
try {
auto references = getStringList(valueAt(json, "references"));
auto & references = getArray(valueAt(json, "references"));
for (auto & input : references)
res.references.insert(store ? store->parseStorePath(getString(input)) : static_cast<StorePath>(input));
res.references.insert(
format == PathInfoJsonFormat::V1 ? store->parseStorePath(getString(input))
: static_cast<StorePath>(input));
} catch (Error & e) {
e.addTrace({}, "while reading key 'references'");
throw;
}
try {
res.ca = ptrToOwned<ContentAddress>(getNullable(valueAt(json, "ca")));
if (format == PathInfoJsonFormat::V1) {
if (auto * rawCa = getNullable(valueAt(json, "ca")))
res.ca = ContentAddress::parse(getString(*rawCa));
} else {
res.ca = ptrToOwned<ContentAddress>(getNullable(valueAt(json, "ca")));
}
} catch (Error & e) {
e.addTrace({}, "while reading key 'ca'");
throw;
}
if (auto * rawDeriver0 = optionalValueAt(json, "deriver"))
if (auto * rawDeriver = getNullable(*rawDeriver0))
res.deriver = store ? store->parseStorePath(getString(*rawDeriver)) : static_cast<StorePath>(*rawDeriver);
if (auto * rawDeriver0 = optionalValueAt(json, "deriver")) {
if (format == PathInfoJsonFormat::V1) {
if (auto * rawDeriver = getNullable(*rawDeriver0))
res.deriver = store->parseStorePath(getString(*rawDeriver));
} else {
res.deriver = ptrToOwned<StorePath>(getNullable(*rawDeriver0));
}
}
if (auto * rawRegistrationTime0 = optionalValueAt(json, "registrationTime"))
if (auto * rawRegistrationTime = getNullable(*rawRegistrationTime0))
@ -241,6 +284,16 @@ namespace nlohmann {
using namespace nix;
PathInfoJsonFormat adl_serializer<PathInfoJsonFormat>::from_json(const json & json)
{
return parsePathInfoJsonFormat(getUnsigned(json));
}
void adl_serializer<PathInfoJsonFormat>::to_json(json & json, const PathInfoJsonFormat & format)
{
json = static_cast<int>(format);
}
UnkeyedValidPathInfo adl_serializer<UnkeyedValidPathInfo>::from_json(const json & json)
{
return UnkeyedValidPathInfo::fromJSON(nullptr, json);
@ -248,7 +301,7 @@ UnkeyedValidPathInfo adl_serializer<UnkeyedValidPathInfo>::from_json(const json
void adl_serializer<UnkeyedValidPathInfo>::to_json(json & json, const UnkeyedValidPathInfo & c)
{
json = c.toJSON(nullptr, true);
json = c.toJSON(nullptr, true, PathInfoJsonFormat::V2);
}
ValidPathInfo adl_serializer<ValidPathInfo>::from_json(const json & json0)