The Meson Build System
http://mesonbuild.com/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
196 lines
8.2 KiB
196 lines
8.2 KiB
#!/usr/bin/env python3 |
|
|
|
# SPDX-License-Identifier: Apache-2.0 |
|
# Copyright 2021 The Meson development team |
|
|
|
import argparse |
|
import json |
|
from pathlib import Path |
|
from copy import deepcopy |
|
|
|
import typing as T |
|
|
|
T_None = type(None) |
|
|
|
# Global root object |
|
root: dict |
|
|
|
def assert_has_typed_keys(path: str, data: dict, keys: T.Dict[str, T.Any]) -> dict: |
|
assert set(data.keys()).issuperset(keys.keys()), f'{path}: DIFF: {set(data.keys()).difference(keys.keys())}' |
|
res = dict() |
|
for key, val in keys.items(): |
|
cur = data.pop(key) |
|
assert isinstance(cur, val), f'{path}: type({key}: {cur}) != {val}' |
|
res[key] = cur |
|
return res |
|
|
|
def validate_base_obj(path: str, name: str, obj: dict) -> None: |
|
expected: T.Dict[str, T.Any] = { |
|
'name': str, |
|
'description': str, |
|
'since': (str, T_None), |
|
'deprecated': (str, T_None), |
|
'notes': list, |
|
'warnings': list, |
|
} |
|
cur = assert_has_typed_keys(f'{path}.{name}', obj, expected) |
|
assert cur['name'], f'{path}.{name}' |
|
assert cur['description'], f'{path}.{name}' |
|
assert cur['name'] == name, f'{path}.{name}' |
|
assert all(isinstance(x, str) and x for x in cur['notes']), f'{path}.{name}' |
|
assert all(isinstance(x, str) and x for x in cur['warnings']), f'{path}.{name}' |
|
|
|
def validate_type(path: str, typ: dict) -> None: |
|
expected: T.Dict[str, T.Any] = { |
|
'obj': str, |
|
'holds': list, |
|
} |
|
cur = assert_has_typed_keys(path, typ, expected) |
|
assert not typ, f'{path} has extra keys: {typ.keys()}' |
|
assert cur['obj'] in root['objects'], path |
|
for i in cur['holds']: |
|
validate_type(path, i) |
|
|
|
def validate_arg(path: str, name: str, arg: dict) -> None: |
|
validate_base_obj(path, name, arg) |
|
expected: T.Dict[str, T.Any] = { |
|
'type': list, |
|
'type_str': str, |
|
'required': bool, |
|
'default': (str, T_None), |
|
'min_varargs': (int, T_None), |
|
'max_varargs': (int, T_None), |
|
} |
|
cur = assert_has_typed_keys(f'{path}.{name}', arg, expected) |
|
assert not arg, f'{path}.{name} has extra keys: {arg.keys()}' |
|
assert cur['type'], f'{path}.{name}' |
|
assert cur['type_str'], f'{path}.{name}' |
|
for i in cur['type']: |
|
validate_type(f'{path}.{name}', i) |
|
if cur['min_varargs'] is not None: |
|
assert cur['min_varargs'] > 0, f'{path}.{name}' |
|
if cur['max_varargs'] is not None: |
|
assert cur['max_varargs'] > 0, f'{path}.{name}' |
|
|
|
def validate_function(path: str, name: str, func: dict) -> None: |
|
validate_base_obj(path, name, func) |
|
expected: T.Dict[str, T.Any] = { |
|
'returns': list, |
|
'returns_str': str, |
|
'example': (str, T_None), |
|
'posargs': dict, |
|
'optargs': dict, |
|
'kwargs': dict, |
|
'varargs': (dict, T_None), |
|
'arg_flattening': bool, |
|
} |
|
cur = assert_has_typed_keys(f'{path}.{name}', func, expected) |
|
assert not func, f'{path}.{name} has extra keys: {func.keys()}' |
|
assert cur['returns'], f'{path}.{name}' |
|
assert cur['returns_str'], f'{path}.{name}' |
|
for i in cur['returns']: |
|
validate_type(f'{path}.{name}', i) |
|
for k, v in cur['posargs'].items(): |
|
validate_arg(f'{path}.{name}', k, v) |
|
for k, v in cur['optargs'].items(): |
|
validate_arg(f'{path}.{name}', k, v) |
|
for k, v in cur['kwargs'].items(): |
|
validate_arg(f'{path}.{name}', k, v) |
|
if cur['varargs']: |
|
validate_arg(f'{path}.{name}', cur['varargs']['name'], cur['varargs']) |
|
|
|
def validate_object(path: str, name: str, obj: dict) -> None: |
|
validate_base_obj(path, name, obj) |
|
expected: T.Dict[str, T.Any] = { |
|
'example': (str, T_None), |
|
'object_type': str, |
|
'methods': dict, |
|
'is_container': bool, |
|
'extends': (str, T_None), |
|
'returned_by': list, |
|
'extended_by': list, |
|
'defined_by_module': (str, T_None), |
|
} |
|
cur = assert_has_typed_keys(f'{path}.{name}', obj, expected) |
|
assert not obj, f'{path}.{name} has extra keys: {obj.keys()}' |
|
for key, val in cur['methods'].items(): |
|
validate_function(f'{path}.{name}', key, val) |
|
if cur['extends'] is not None: |
|
assert cur['extends'] in root['objects'], f'{path}.{name}' |
|
assert all(isinstance(x, str) for x in cur['returned_by']), f'{path}.{name}' |
|
assert all(isinstance(x, str) for x in cur['extended_by']), f'{path}.{name}' |
|
assert all(x in root['objects'] for x in cur['extended_by']), f'{path}.{name}' |
|
if cur['defined_by_module'] is not None: |
|
assert cur['defined_by_module'] in root['objects'], f'{path}.{name}' |
|
assert cur['object_type'] == 'RETURNED', f'{path}.{name}' |
|
assert root['objects'][cur['defined_by_module']]['object_type'] == 'MODULE', f'{path}.{name}' |
|
assert name in root['objects_by_type']['modules'][cur['defined_by_module']], f'{path}.{name}' |
|
return |
|
assert cur['object_type'] in {'ELEMENTARY', 'BUILTIN', 'MODULE', 'RETURNED'}, f'{path}.{name}' |
|
if cur['object_type'] == 'ELEMENTARY': |
|
assert name in root['objects_by_type']['elementary'], f'{path}.{name}' |
|
if cur['object_type'] == 'BUILTIN': |
|
assert name in root['objects_by_type']['builtins'], f'{path}.{name}' |
|
if cur['object_type'] == 'RETURNED': |
|
assert name in root['objects_by_type']['returned'], f'{path}.{name}' |
|
if cur['object_type'] == 'MODULE': |
|
assert name in root['objects_by_type']['modules'], f'{path}.{name}' |
|
|
|
def main() -> int: |
|
global root |
|
|
|
parser = argparse.ArgumentParser(description='Meson JSON docs validator') |
|
parser.add_argument('doc_file', type=Path, help='The JSON docs to validate') |
|
args = parser.parse_args() |
|
|
|
root_tmp = json.loads(args.doc_file.read_text(encoding='utf-8')) |
|
root = deepcopy(root_tmp) |
|
assert isinstance(root, dict) |
|
|
|
expected: T.Dict[str, T.Any] = { |
|
'version_major': int, |
|
'version_minor': int, |
|
'meson_version': str, |
|
'functions': dict, |
|
'objects': dict, |
|
'objects_by_type': dict, |
|
} |
|
cur = assert_has_typed_keys('root', root_tmp, expected) |
|
assert not root_tmp, f'root has extra keys: {root_tmp.keys()}' |
|
|
|
refs = cur['objects_by_type'] |
|
expected = { |
|
'elementary': list, |
|
'builtins': list, |
|
'returned': list, |
|
'modules': dict, |
|
} |
|
assert_has_typed_keys(f'root.objects_by_type', refs, expected) |
|
assert not refs, f'root.objects_by_type has extra keys: {refs.keys()}' |
|
assert all(isinstance(x, str) for x in root['objects_by_type']['elementary']) |
|
assert all(isinstance(x, str) for x in root['objects_by_type']['builtins']) |
|
assert all(isinstance(x, str) for x in root['objects_by_type']['returned']) |
|
assert all(isinstance(x, str) for x in root['objects_by_type']['modules']) |
|
assert all(x in root['objects'] for x in root['objects_by_type']['elementary']) |
|
assert all(x in root['objects'] for x in root['objects_by_type']['builtins']) |
|
assert all(x in root['objects'] for x in root['objects_by_type']['returned']) |
|
assert all(x in root['objects'] for x in root['objects_by_type']['modules']) |
|
assert all(root['objects'][x]['object_type'] == 'ELEMENTARY' for x in root['objects_by_type']['elementary']) |
|
assert all(root['objects'][x]['object_type'] == 'BUILTIN' for x in root['objects_by_type']['builtins']) |
|
assert all(root['objects'][x]['object_type'] == 'RETURNED' for x in root['objects_by_type']['returned']) |
|
assert all(root['objects'][x]['object_type'] == 'MODULE' for x in root['objects_by_type']['modules']) |
|
|
|
# Check that module references are correct |
|
assert all(all(isinstance(x, str) for x in v) for k, v in root['objects_by_type']['modules'].items()) |
|
assert all(all(x in root['objects'] for x in v) for k, v in root['objects_by_type']['modules'].items()) |
|
assert all(all(root['objects'][x]['defined_by_module'] == k for x in v) for k, v in root['objects_by_type']['modules'].items()) |
|
|
|
for key, val in cur['functions'].items(): |
|
validate_function('root', key, val) |
|
for key, val in cur['objects'].items(): |
|
validate_object('root', key, val) |
|
|
|
return 0 |
|
|
|
if __name__ == '__main__': |
|
raise SystemExit(main())
|
|
|