blob: 1e526da7de653f5e53dd18a29672c60077212602 [file] [log] [blame] [edit]
# Copyright 2025 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Validates the contents of an IDK directory.
Currently, this is limited to validating the schema.
Usage:
python3 ./build/sdk/generate_idk/validate_idk.py \
--idk-directory $(fx get-build-dir)/sdk/exported/fuchsia_idk \
--schema-directory build/sdk/meta \
--json-validator-path $(fx get-build-dir)/host_x64/json_validator_valico
"""
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
# Disallow bytecode generation to avoid polluting the Bazel execroot with .pyc
# files that can end up in the generated TreeArtifact, resulting in issues when
# dependent actions try to read it.
sys.dont_write_bytecode = True
# Atom types that are treated as type "data".
_VALID_DATA_ATOM_TYPES = [
# LINT.IfChange
"component_manifest",
"config",
# LINT.ThenChange(//build/sdk/sdk_atom.gni)
]
def get_idk_manifest_type_file_for_atom_type(type: str) -> str:
"""Returns the type string used in the IDK manifest for the atom `type`.
Args:
type: The atom type as used in an individual atom manifest.
Returns:
The corresponding type used in the IDK manifest.
"""
if type in _VALID_DATA_ATOM_TYPES:
return "data"
else:
return type
class AtomSchemaValidator:
"""Validates atom metadata against JSON schemas."""
def __init__(self, schema_directory: Path, json_validator_path: Path):
"""Initializes the object.
Args:
schema_directory: Path to the directory containing the schema files
for IDK contents.
json_validator_path: Path to an executable that validates JSON
against a schema.
"""
self.schema_directory = schema_directory
self.json_validator_path = json_validator_path
def _get_schema_filename_for_atom_type(self, type: str) -> str:
"""Returns the filename of the schema file for the atom `type`.
Args:
type: The atom type as used in an individual atom manifest.
Returns:
The filename for the schema file used to validate `type`.
"""
manifest_type = get_idk_manifest_type_file_for_atom_type(type)
return f"{manifest_type}.json"
def validate(self, file_path: Path, type: str) -> int:
"""Validates that `file_path` complies with the schema for the `type`.
Deps on the schema files is covered by the collection's deps on
# "//build/sdk/meta".
Args:
file_path: The metadata file to validate.
type: The file's atom type as used in an individual atom manifest.
Returns:
0 if successful and non-zero otherwise.
"""
schema_filename = self._get_schema_filename_for_atom_type(type)
schema_file = self.schema_directory / schema_filename
ret = subprocess.run([self.json_validator_path, schema_file, file_path])
if ret.returncode != 0:
print(
f"ERROR: Metadata schema validation failed for '{os.path.abspath(file_path)}' of type '{type}' using schema '{schema_file}'."
)
return ret.returncode
return 0
class IdkValidator:
"""Validates the contents of an IDK.
Currently, this is limited to validating the schema.
"""
def __init__(
self, idk_dir: Path, schema_directory: Path, json_validator_path: Path
):
"""Initializes the object.
Args:
idk_dir: Root directory of the IDK to be verified. Must contain a
file at `meta/manifest.json` referencing the manifests for all
atoms.
schema_directory: Path to the directory containing the schema files
for IDK contents.
json_validator_path: Path to an executable that validates JSON
against a schema.
"""
self._idk_dir: Path = idk_dir
self._atom_schema_validator = AtomSchemaValidator(
schema_directory, json_validator_path
)
def validate_schema(self) -> int:
"""Reads the IDK manifest and validates the schema of all manifests.
Returns:
0 if successful and non-zero otherwise.
"""
_idk_manifest_path = self._idk_dir / "meta/manifest.json"
with (_idk_manifest_path).open() as f:
idk_manifest = json.load(f)
# Do some basic checking.
assert idk_manifest["arch"]["host"] != ""
assert len(idk_manifest["parts"]) > 0
assert (
len(idk_manifest["arch"]["target"]) > 0
and len(idk_manifest["arch"]["target"]) <= 3
)
assert idk_manifest["root"] == ".."
assert idk_manifest["schema_version"] == "1" # There is only one.
result = self._atom_schema_validator.validate(
_idk_manifest_path, "manifest"
)
if result != 0:
return result
atoms = idk_manifest["parts"]
for atom in atoms:
assert atom["stable"] == True or atom["stable"] == False
meta_file_path = self._idk_dir / atom["meta"]
atom_type = atom["type"]
result = self._atom_schema_validator.validate(
meta_file_path, atom_type
)
if result != 0:
return result
return 0
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--idk-directory",
help="Root directory of the IDK to be verified. Must contain a file at `meta/manifest.json` referencing the manifests for all atoms.",
type=Path,
required=True,
)
parser.add_argument(
"--schema-directory",
type=Path,
help="Path containing the metadata schema files",
required=True,
)
parser.add_argument(
"--json-validator-path",
type=Path,
help="Path containing the metadata schema files",
required=True,
)
parser.add_argument(
"--stamp-file",
type=Path,
help="Stamp file to write on success.",
required=False,
)
args = parser.parse_args()
idk_validator = IdkValidator(
args.idk_directory, args.schema_directory, args.json_validator_path
)
result = idk_validator.validate_schema()
if result != 0:
return result
if args.stamp_file:
args.stamp_file.parent.mkdir(parents=True, exist_ok=True)
args.stamp_file.write_text("OK")
return 0
if __name__ == "__main__":
sys.exit(main())