Skip to content
Merged
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
22 changes: 18 additions & 4 deletions doc/manual/rl-next/json-format-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,30 @@ This is the legacy format, preserved for backwards compatibility:

- String-based hash values (e.g., `"narHash": "sha256:FePFYIlM..."`)
- String-based content addresses (e.g., `"ca": "fixed:r:sha256:1abc..."`)
- Full store paths in references (e.g., `"/nix/store/abc...-foo"`)
- Full store paths for map keys and references (e.g., `"/nix/store/abc...-foo"`)
- Now includes `"storeDir"` field at the top level

### Version 2 (`--json-format 2`)

The new structured format with the following changes:
The new structured format follows the [JSON guidelines](@docroot@/development/json-guideline.md) with the following changes:

- **Store path base names in references**:
- **Nested structure with top-level metadata**:

References use store path base names (e.g., `"abc...-foo"`) instead of full paths.
The output is now wrapped in an object with `version`, `storeDir`, and `info` fields:

```json
{
"version": 2,
"storeDir": "/nix/store",
"info": { ... }
}
```

The map from store bath base names to store object info is nested under the `info` field.

- **Store path base names instead of full paths**:

Map keys and references use store path base names (e.g., `"abc...-foo"`) instead of full absolute store paths.
Combined with `storeDir`, the full path can be reconstructed.

- **Structured `ca` field**:
Expand Down
19 changes: 16 additions & 3 deletions src/nix/path-info.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,22 @@ pathInfoToJSON(Store & store, const StorePathSet & storePaths, bool showClosureS
{
json::object_t jsonAllObjects = json::object();

auto makeKey = [&](const StorePath & path) {
return format == PathInfoJsonFormat::V1 ? store.printStorePath(path) : std::string(path.to_string());
};

for (auto & storePath : storePaths) {
json jsonObject;

std::string key = store.printStorePath(storePath);
std::string key = makeKey(storePath);

try {
auto info = store.queryPathInfo(storePath);

// `storePath` has the representation `<hash>-x` rather than
// `<hash>-<name>` in case of binary-cache stores & `--all` because we don't
// know the name yet until we've read the NAR info.
key = store.printStorePath(info->path);
key = makeKey(info->path);

jsonObject = info->toJSON(format == PathInfoJsonFormat::V1 ? &store : nullptr, true, format);

Expand Down Expand Up @@ -87,7 +91,16 @@ pathInfoToJSON(Store & store, const StorePathSet & storePaths, bool showClosureS

jsonAllObjects[key] = std::move(jsonObject);
}
return jsonAllObjects;

if (format == PathInfoJsonFormat::V1) {
return jsonAllObjects;
} else {
return {
{"version", format},
{"storeDir", store.storeDir},
{"info", std::move(jsonAllObjects)},
};
}
}

struct CmdPathInfo : StorePathsCommand, MixJSON
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/binary-cache.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ outPath=$(nix-build dependencies.nix --no-out-link)

nix copy --to "file://$cacheDir" "$outPath"

readarray -t paths < <(nix path-info --all --json --json-format 2 --store "file://$cacheDir" | jq 'keys|sort|.[]' -r)
readarray -t paths < <(nix path-info --all --json --json-format 2 --store "file://$cacheDir" | jq '.info|keys|sort|.[]' -r)
[[ "${#paths[@]}" -eq 3 ]]
for path in "${paths[@]}"; do
[[ "$path" =~ -dependencies-input-0$ ]] \
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/fixed.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ nix-build fixed.nix -A bad --no-out-link && fail "should fail"
# a side-effect.
[[ -e $path ]]
nix path-info --json --json-format 2 "$path" | jq -e \
'.[].ca == {
'.info.[].ca == {
method: "flat",
hash: {
algorithm: "md5",
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/git-hashing/simple-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ try2 () {
nix path-info --json --json-format 2 "$path" | jq -e \
--arg algo "$hashAlgo" \
--arg hash "$hashFromGit" \
'.[].ca == {
'.info.[].ca == {
method: "git",
hash: {
algorithm: $algo,
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/impure-derivations.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ path1_stuff=$(echo "$json" | jq -r .[].outputs.stuff)
[[ $(< "$path1"/n) = 0 ]]
[[ $(< "$path1_stuff"/bla) = 0 ]]

nix path-info --json --json-format 2 "$path1" | jq -e '.[].ca | .method == "nar" and .hash.algorithm == "sha256"'
nix path-info --json --json-format 2 "$path1" | jq -e '.info.[].ca | .method == "nar" and .hash.algorithm == "sha256"'

path2=$(nix build -L --no-link --json --file ./impure-derivations.nix impure | jq -r .[].outputs.out)
[[ $(< "$path2"/n) = 1 ]]
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/nix-profile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ printf 4.0 > "$flake1Dir"/version
printf Utrecht > "$flake1Dir"/who
nix profile add "$flake1Dir"
[[ $("$TEST_HOME"/.nix-profile/bin/hello) = "Hello Utrecht" ]]
nix path-info --json --json-format 2 "$(realpath "$TEST_HOME"/.nix-profile/bin/hello)" | jq -e '.[].ca | .method == "nar" and .hash.algorithm == "sha256"'
nix path-info --json --json-format 2 "$(realpath "$TEST_HOME"/.nix-profile/bin/hello)" | jq -e '.info.[].ca | .method == "nar" and .hash.algorithm == "sha256"'

# Override the outputs.
nix profile remove simple flake1
Expand Down
22 changes: 15 additions & 7 deletions tests/functional/path-info.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,44 @@ source common.sh

echo foo > "$TEST_ROOT"/foo
foo=$(nix store add-file "$TEST_ROOT"/foo)
fooBase=$(basename "$foo")

echo bar > "$TEST_ROOT"/bar
bar=$(nix store add-file "$TEST_ROOT"/bar)
barBase=$(basename "$bar")

echo baz > "$TEST_ROOT"/baz
baz=$(nix store add-file "$TEST_ROOT"/baz)
bazBase=$(basename "$baz")
nix-store --delete "$baz"

diff --unified --color=always \
<(nix path-info --json --json-format 2 "$foo" "$bar" "$baz" |
jq --sort-keys 'map_values(.narHash)') \
jq --sort-keys '.info | map_values(.narHash)') \
<(jq --sort-keys <<-EOF
{
"$foo": {
"$fooBase": {
"algorithm": "sha256",
"format": "base16",
"hash": "42fb4031b525feebe2f8b08e6e6a8e86f34e6a91dd036ada888e311b9cc8e690"
},
"$bar": {
"$barBase": {
"algorithm": "sha256",
"format": "base16",
"hash": "f5f8581aef5fab17100b629cf35aa1d91328d5070b054068f14fa93e7fa3b614"
},
"$baz": null
"$bazBase": null
}
EOF
)

# Test that storeDir is returned in the JSON output
# Test that storeDir is returned in the JSON output in individual store objects
nix path-info --json --json-format 2 "$foo" | jq -e \
--arg foo "$foo" \
--arg fooBase "$fooBase" \
--arg storeDir "${NIX_STORE_DIR:-/nix/store}" \
'.[$foo].storeDir == $storeDir'
'.info[$fooBase].storeDir == $storeDir'

# And also at the top -evel
echo | nix path-info --json --json-format 2 --stdin | jq -e \
--arg storeDir "${NIX_STORE_DIR:-/nix/store}" \
'.storeDir == $storeDir'
22 changes: 11 additions & 11 deletions tests/functional/signing.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ outPath=$(nix-build dependencies.nix --no-out-link --secret-key-files "$TEST_ROO

# Verify that the path got signed.
info=$(nix path-info --json --json-format 2 "$outPath")
echo "$info" | jq -e '.[] | .ultimate == true'
echo "$info" | jq -e '.info.[] | .ultimate == true'
TODO_NixOS # looks like an actual bug? Following line fails on NixOS:
echo "$info" | jq -e '.[] | .signatures.[] | select(startswith("cache1.example.org"))'
echo "$info" | jq -e '.[] | .signatures.[] | select(startswith("cache2.example.org"))'
echo "$info" | jq -e '.info.[] | .signatures.[] | select(startswith("cache1.example.org"))'
echo "$info" | jq -e '.info.[] | .signatures.[] | select(startswith("cache2.example.org"))'

# Test "nix store verify".
nix store verify -r "$outPath"
Expand All @@ -40,8 +40,8 @@ nix store verify -r "$outPath"

# Verify that the path did not get signed but does have the ultimate bit.
info=$(nix path-info --json --json-format 2 "$outPath2")
echo "$info" | jq -e '.[] | .ultimate == true'
echo "$info" | jq -e '.[] | .signatures == []'
echo "$info" | jq -e '.info.[] | .ultimate == true'
echo "$info" | jq -e '.info.[] | .signatures == []'

# Test "nix store verify".
nix store verify -r "$outPath2"
Expand All @@ -58,7 +58,7 @@ nix store verify -r "$outPath2" --sigs-needed 1 --trusted-public-keys "$pk1"
# Build something content-addressed.
outPathCA=$(IMPURE_VAR1=foo IMPURE_VAR2=bar nix-build ./fixed.nix -A good.0 --no-out-link)

nix path-info --json --json-format 2 "$outPathCA" | jq -e '.[].ca | .method == "flat" and .hash.algorithm == "md5"'
nix path-info --json --json-format 2 "$outPathCA" | jq -e '.info.[].ca | .method == "flat" and .hash.algorithm == "md5"'

# Content-addressed paths don't need signatures, so they verify
# regardless of --sigs-needed.
Expand All @@ -74,15 +74,15 @@ nix copy --to file://"$cacheDir" "$outPath2"

# Verify that signatures got copied.
info=$(nix path-info --store file://"$cacheDir" --json --json-format 2 "$outPath2")
echo "$info" | jq -e '.[] | .ultimate == false'
echo "$info" | jq -e '.[] | .signatures.[] | select(startswith("cache1.example.org"))'
echo "$info" | expect 4 jq -e '.[] | .signatures.[] | select(startswith("cache2.example.org"))'
echo "$info" | jq -e '.info.[] | .ultimate == false'
echo "$info" | jq -e '.info.[] | .signatures.[] | select(startswith("cache1.example.org"))'
echo "$info" | expect 4 jq -e '.info.[] | .signatures.[] | select(startswith("cache2.example.org"))'

# Verify that adding a signature to a path in a binary cache works.
nix store sign --store file://"$cacheDir" --key-file "$TEST_ROOT"/sk2 "$outPath2"
info=$(nix path-info --store file://"$cacheDir" --json --json-format 2 "$outPath2")
echo "$info" | jq -e '.[] | .signatures.[] | select(startswith("cache1.example.org"))'
echo "$info" | jq -e '.[] | .signatures.[] | select(startswith("cache2.example.org"))'
echo "$info" | jq -e '.info.[] | .signatures.[] | select(startswith("cache1.example.org"))'
echo "$info" | jq -e '.info.[] | .signatures.[] | select(startswith("cache2.example.org"))'

# Copying to a diverted store should fail due to a lack of signatures by trusted keys.
chmod -R u+w "$TEST_ROOT"/store0 || true
Expand Down
Loading