Skip to content
Open
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
2 changes: 1 addition & 1 deletion docs/reference-manual/native-image/FFM-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -754,4 +754,4 @@ This is also why the `native-image` tool currently disallows native `MemorySegme
* [Interoperability with Native Code](InteropWithNativeCode.md)
* [Collect Metadata with the Tracing Agent](AutomaticMetadataCollection.md)
* [Reachability Metadata](ReachabilityMetadata.md)
* [reachability-metadata-schema-v1.1.0.json](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json)
* [reachability-metadata-schema-v1.2.0.json](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.2.0.json)
2 changes: 1 addition & 1 deletion docs/reference-manual/native-image/ReachabilityMetadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Computing metadata in code can be achieved in two ways:
## Specifying Metadata with JSON

All metadata specified in the _reachability-metadata.json_ file that is located in any of the classpath entries at _META-INF/native-image/\<group.Id>\/\<artifactId>\/_.
The JSON schema for the reachability metadata is defined in [reachability-metadata-schema-v1.1.0.json](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json).
The JSON schema for the reachability metadata is defined in [reachability-metadata-schema-v1.2.0.json](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.2.0.json).

A sample _reachability-metadata.json_ file can be found [in the sample section](#sample-reachability-metadata).
The _reachability-metadata.json_ configuration contains a single object with one field for each type of metadata. Each field in the top-level object contains an array of *metadata entries*:
Expand Down
112 changes: 112 additions & 0 deletions sdk/mx.sdk/mx_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import datetime
import shutil
import tempfile
from typing import Iterable, Tuple
from mx_bisect import define_bisect_default_build_steps
from mx_bisect_strategy import BuildStepsGraalVMStrategy

Expand Down Expand Up @@ -262,6 +263,117 @@ def jlink_new_jdk(jdk, dst_jdk_dir, module_dists, ignore_dists,
use_upgrade_module_path=use_upgrade_module_path,
default_to_jvmci=default_to_jvmci)


def create_jsonschema_validator(schema_path):
"""Create and return a jsonschema Validator for the schema at the given file path. Abort on missing jsonschema or invalid schema."""
import json
try:
with open(schema_path, "r", encoding="utf-8") as f:
schema = json.load(f)
except json.JSONDecodeError as e:
mx.abort(f'Failed to parse JSON in schema file "{schema_path}" at line {e.lineno}, column {e.colno}: {e.msg}')
except OSError as e:
mx.abort(f'I/O error when opening schema file "{schema_path}": {e}')

try:
import jsonschema # type: ignore
except ImportError as e:
mx.abort(
'Python module "jsonschema" is required to validate reachability metadata but was not found. '
'Install it with: \n\npython3 -m pip install --user jsonschema \n\n or \n\npip3 install jsonschema\n\n'
f'Original error: {e}')
try:
if hasattr(jsonschema, 'Draft202012Validator'):
return jsonschema.Draft202012Validator(schema) # type: ignore[attr-defined]
else:
return jsonschema.Draft7Validator(schema) # type: ignore
except (jsonschema.exceptions.SchemaError, TypeError) as e:
mx.abort(f'Invalid reachability metadata schema: {e}')


def validate_json_file_with_validator(validator, file_path):
"""Validates a JSON file against the provided Validator. Returns a list of detailed error strings; empty if valid."""
import json
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
except json.JSONDecodeError as e:
mx.abort(f'Invalid JSON syntax at line {e.lineno}, column {e.colno}: {e.msg}')
except OSError as e:
mx.abort(f'I/O error: {e}')

# Use jsonschema's ValidationError string representation to produce messages in the form:
# Failed validating '<validator>' in schema[...] :
# { ... failing subschema ... }
#
# On instance[...] :
# <offending instance>
errors = []
for err in validator.iter_errors(data):
errors.append(str(err))
return errors


def validate_dir_files_with_file_schema_pairs(dir_with_json: str, json_and_schema_file_pairs: Iterable[Tuple[str, str]]) -> None:
"""
Validate JSON files in a directory against corresponding JSON Schemas.

This scans the given directory for each JSON filename listed in json_and_schema_file_pairs.
For every file that exists, it validates the file against the associated schema located in
the Native Image docs assets directory. Validation uses a pre-built jsonschema.Validator
per schema to ensure consistent behavior and error reporting.

Parameters:
- dir_with_json: Directory path containing JSON files to validate.
- json_and_schema_file_pairs: Iterable of (json_filename, schema_filename) pairs.

Behavior:
- Logs each validation attempt.
- Accumulates all validation errors across files.
- Calls mx.abort with a detailed message if any error is found; otherwise returns None.
"""
assets_dir = os.path.join(_suite.dir, '..', 'docs', 'reference-manual', 'native-image', 'assets')

# Build validators for all known schema files using CE helper to ensure uniform behavior and error reporting.
validators = {}
for _, schema_file in json_and_schema_file_pairs:
schema_path = os.path.join(assets_dir, schema_file)
validators[schema_file] = create_jsonschema_validator(schema_path)

validation_errors_by_file = {}
for json_filename, schema_file in json_and_schema_file_pairs:
json_path = os.path.join(dir_with_json, json_filename)
if os.path.exists(json_path):
mx.log(f'Validating {json_path} against {schema_file}...')
errs = validate_json_file_with_validator(validators[schema_file], json_path)
if errs:
validation_errors_by_file[(json_path, schema_file)] = errs

if validation_errors_by_file:
sections = []
for (json_path, schema_file), errs in validation_errors_by_file.items():
header = (
"-------------------------------------------------------------------------------\n"
f"File: {json_path}\n"
f"Schema: {schema_file}\n"
"-------------------------------------------------------------------------------"
)
sections.append(header + "\n\n" + "\n\n".join(errs))
msg = "Unable to validate JSON file(s) against the schema:\n\n" + "\n\n".join(sections)
mx.abort(msg)
# Validate any present config files
validation_errors = []
for json_filename, schema_file in json_and_schema_file_pairs:
json_path = os.path.join(dir_with_json, json_filename)
if os.path.exists(json_path):
mx.log(f'Validating {json_path} against {schema_file}...')
errs = validate_json_file_with_validator(validators[schema_file], json_path)
validation_errors.extend(errs)

if validation_errors:
mx.abort('Unable to validate JSON file against the schema:\n\n' + '\n\n'.join(validation_errors))


class GraalVMJDKConfig(mx.JDKConfig):

# Oracle JDK includes the libjvmci compiler, allowing it to function as GraalVM.
Expand Down
2 changes: 2 additions & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ This changelog summarizes major changes to GraalVM Native Image.
* (GR-2092) Add jitdump support for recording run-time compilation metadata for perf (see PerfProfiling.md). Can be enabled with `-g -H:+RuntimeDebugInfo -H:RuntimeDebugInfoFormat=jitdump`.
* (GR-69572) Deprecates the `native-image-inspect` tool. To extract embedded SBOMs, use `native-image-configure extract-sbom --image-path=<path_to_binary>`.
* (GR-70136) Add a new tool `--tool:llvm` for the LLVM backend of Native Image.
* (GR-68984) Ship the `reachability-metadata-schema.json` together with GraalVM at `<graalvm-home>/lib/svm/schemas/reachability-metadata-schema.json`.
* (GR-68984) Improve the schema to capture detailed constraints about each element in the `reachability-metadata-schema.json`.

## GraalVM 25
* (GR-52276) (GR-61959) Add support for Arena.ofShared().
Expand Down
22 changes: 2 additions & 20 deletions substratevm/mx.substratevm/mx_substratevm.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import mx_compiler
import mx_gate
import mx_unittest
import mx_sdk
import mx_sdk_vm
import mx_sdk_vm_impl
import mx_javamodules
Expand Down Expand Up @@ -525,13 +526,6 @@ def help_stdout_check(output):

with Task('Validate JSON build info', tasks, tags=[GraalTags.helloworld]) as t:
if t:
import json
try:
from jsonschema import validate as json_validate
from jsonschema.exceptions import ValidationError, SchemaError
except ImportError:
mx.abort('Unable to import jsonschema')

json_and_schema_file_pairs = [
('build-artifacts.json', 'build-artifacts-schema-v0.9.0.json'),
('build-output.json', 'build-output-schema-v0.9.4.json'),
Expand All @@ -540,19 +534,7 @@ def help_stdout_check(output):
build_output_file = join(svmbuild_dir(), 'build-output.json')
helloworld(['--output-path', svmbuild_dir()] + svm_experimental_options([f'-H:BuildOutputJSONFile={build_output_file}', '-H:+GenerateBuildArtifactsFile']))

try:
for json_file, schema_file in json_and_schema_file_pairs:
with open(join(svmbuild_dir(), json_file)) as f:
json_contents = json.load(f)
with open(join(suite.dir, '..', 'docs', 'reference-manual', 'native-image', 'assets', schema_file)) as f:
schema_contents = json.load(f)
json_validate(json_contents, schema_contents)
except IOError as e:
mx.abort(f'Unable to load JSON build info: {e}')
except ValidationError as e:
mx.abort(f'Unable to validate JSON build info against the schema: {e}')
except SchemaError as e:
mx.abort(f'JSON schema not valid: {e}')
mx_sdk.validate_dir_files_with_file_schema_pairs(svmbuild_dir(), json_and_schema_file_pairs)

with Task('java agent tests', tasks, tags=[GraalTags.java_agent]) as t:
if t:
Expand Down
1 change: 1 addition & 0 deletions substratevm/mx.substratevm/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -2475,6 +2475,7 @@
"clibraries/" : ["extracted-dependency:substratevm:SVM_HOSTED_NATIVE"],
"builder/clibraries/" : ["extracted-dependency:substratevm:SVM_HOSTED_NATIVE"],
"builder/lib/" : ["dependency:com.oracle.svm.native.reporterchelper"],
"schemas/reachability-metadata-schema.json" : ["file:schemas/reachability-metadata-schema-v1.2.0.json"],
# Note: `ld64.lld` is a symlink to `lld`, but it is dereferenced here.
"bin/" : ["extracted-dependency:LLVM_LLD_STANDALONE/bin/ld64.lld"],
},
Expand Down
Loading