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.
216 lines
8.0 KiB
216 lines
8.0 KiB
# Copyright 2021 The Meson development team |
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
|
# you may not use this file except in compliance with the License. |
|
# You may obtain a copy of the License at |
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0 |
|
|
|
# Unless required by applicable law or agreed to in writing, software |
|
# distributed under the License is distributed on an "AS IS" BASIS, |
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
# See the License for the specific language governing permissions and |
|
# limitations under the License. |
|
|
|
from abc import ABCMeta, abstractmethod |
|
from pathlib import Path |
|
import re |
|
import typing as T |
|
|
|
from .model import ( |
|
NamedObject, |
|
FetureCheck, |
|
ArgBase, |
|
PosArg, |
|
DataTypeInfo, |
|
Type, |
|
Function, |
|
Method, |
|
Object, |
|
ObjectType, |
|
ReferenceManual, |
|
) |
|
|
|
from mesonbuild import mlog |
|
|
|
class _Resolver: |
|
def __init__(self) -> None: |
|
self.type_map: T.Dict[str, Object] = {} |
|
self.func_map: T.Dict[str, T.Union[Function, Method]] = {} |
|
self.processed_funcs: T.Set[str] = set() |
|
|
|
def _validate_named_object(self, obj: NamedObject) -> None: |
|
name_regex = re.compile(r'[a-zA-Z0-9_]+') |
|
obj.name = obj.name.strip() |
|
obj.description = obj.description.strip() |
|
assert obj.name and obj.description, 'Both name and description must be set' |
|
assert obj.name.islower(), f'Object names must be lower case ({obj.name})' |
|
assert name_regex.match(obj.name) or obj.name == '[index]', f'Invalid name {obj.name}' |
|
|
|
def _validate_feature_check(self, obj: FetureCheck) -> None: |
|
meson_version_reg = re.compile(r'[0-9]+\.[0-9]+\.[0-9]+') |
|
obj.since = obj.since.strip() |
|
obj.deprecated = obj.deprecated.strip() |
|
if obj.since: |
|
assert meson_version_reg.match(obj.since) |
|
if obj.deprecated: |
|
assert meson_version_reg.match(obj.deprecated) |
|
|
|
def _resolve_type(self, raw: str) -> Type: |
|
typ = Type(raw) |
|
# We can't use `types = raw.split('|')`, because of `list[str | env]` |
|
types: T.List[str] = [''] |
|
stack = 0 |
|
for c in raw: |
|
if stack == 0 and c == '|': |
|
types += [''] |
|
continue |
|
if c == '[': |
|
stack += 1 |
|
if c == ']': |
|
stack -= 1 |
|
types[-1] += c |
|
types = [x.strip() for x in types] |
|
for t in types: |
|
t = t.strip() |
|
idx = t.find('[') |
|
base_type = t |
|
held_type = None |
|
if idx > 0: |
|
base_type = t[:idx] |
|
held_type = self._resolve_type(t[idx+1:-1]) |
|
assert base_type in self.type_map, f'No known object {t}' |
|
obj = self.type_map[base_type] |
|
typ.resolved += [DataTypeInfo(obj, held_type)] |
|
return typ |
|
|
|
def _validate_func(self, func: T.Union[Function, Method]) -> None: |
|
# Always run basic checks, since they also slightly post-process (strip) some strings |
|
self._validate_named_object(func) |
|
self._validate_feature_check(func) |
|
|
|
func_id = f'{func.obj.name}.{func.name}' if isinstance(func, Method) else func.name |
|
if func_id in self.processed_funcs: |
|
return |
|
|
|
func.returns = self._resolve_type(func.returns.raw) |
|
|
|
all_args: T.List[ArgBase] = [] |
|
all_args += func.posargs |
|
all_args += func.optargs |
|
all_args += func.kwargs.values() |
|
all_args += [func.varargs] if func.varargs else [] |
|
|
|
for arg in all_args: |
|
arg.type = self._resolve_type(arg.type.raw) |
|
|
|
# Handle returned_by |
|
for obj in func.returns.resolved: |
|
obj.data_type.returned_by += [func] |
|
|
|
# Handle kwargs inheritance |
|
for base_name in func.kwargs_inherit: |
|
base_name = base_name.strip() |
|
assert base_name in self.func_map, f'Unknown base function `{base_name}` for {func.name}' |
|
base = self.func_map[base_name] |
|
if base_name not in self.processed_funcs: |
|
self._validate_func(base) |
|
|
|
curr_keys = set(func.kwargs.keys()) |
|
base_keys = set(base.kwargs.keys()) |
|
|
|
# Calculate the missing kwargs from the current set |
|
missing = {k: v for k, v in base.kwargs.items() if k in base_keys - curr_keys} |
|
func.kwargs.update(missing) |
|
|
|
# Handle other args inheritance |
|
_T = T.TypeVar('_T', bound=T.Union[ArgBase, T.List[PosArg]]) |
|
def resolve_inherit(name: str, curr: _T, resolver: T.Callable[[Function], _T]) -> _T: |
|
if name and not curr: |
|
name = name.strip() |
|
assert name in self.func_map, f'Unknown base function `{name}` for {func.name}' |
|
if name not in self.processed_funcs: |
|
self._validate_func(self.func_map[name]) |
|
ref_args = resolver(self.func_map[name]) |
|
assert ref_args is not None, f'Inherited function `{name}` does not have inherited args set' |
|
return ref_args |
|
return curr |
|
|
|
func.posargs = resolve_inherit(func.posargs_inherit, func.posargs, lambda x: x.posargs) |
|
func.optargs = resolve_inherit(func.optargs_inherit, func.optargs, lambda x: x.optargs) |
|
func.varargs = resolve_inherit(func.varargs_inherit, func.varargs, lambda x: x.varargs) |
|
|
|
self.processed_funcs.add(func_id) |
|
|
|
def validate_and_resolve(self, manual: ReferenceManual) -> ReferenceManual: |
|
mlog.log('Validating loaded manual...') |
|
|
|
# build type map and func map for methods |
|
for obj in manual.objects: |
|
assert obj.name not in self.type_map, f'Duplicate object name {obj.name}' |
|
self.type_map[obj.name] = obj |
|
for m in obj.methods: |
|
mid = f'{obj.name}.{m.name}' |
|
assert mid not in self.type_map, f'Duplicate method {mid}' |
|
self.func_map[mid] = m |
|
|
|
# Build func map for functions |
|
for func in manual.functions: |
|
assert func.name not in [*self.func_map.keys()], f'Duplicate function {func.name}' |
|
self.func_map[func.name] = func |
|
|
|
mlog.log('Validating functions...') |
|
for func in manual.functions: |
|
mlog.log(' -- validating', mlog.bold(func.name)) |
|
self._validate_func(func) |
|
|
|
mlog.log('Validating objects...') |
|
for obj in manual.objects: |
|
mlog.log(' -- validating', mlog.bold(obj.name)) |
|
self._validate_named_object(obj) |
|
self._validate_feature_check(obj) |
|
# Resolve and validate inheritance |
|
if obj.extends: |
|
assert obj.extends in self.type_map, f'Unknown extends object {obj.extends} in {obj.name}' |
|
obj.extends_obj = self.type_map[obj.extends] |
|
obj.extends_obj.extended_by += [obj] |
|
# Only returned objects can be associated with module |
|
if obj.obj_type is not ObjectType.RETURNED: |
|
assert obj.defined_by_module is None |
|
for m in obj.methods: |
|
assert m.obj is obj |
|
self._validate_func(m) |
|
|
|
# Resolve inherited methods |
|
for obj in manual.objects: |
|
inherited_methods = obj.inherited_methods |
|
curr = obj.extends_obj |
|
while curr is not None: |
|
inherited_methods += curr.methods |
|
curr = curr.extends_obj |
|
return manual |
|
|
|
class LoaderBase(metaclass=ABCMeta): |
|
def __init__(self) -> None: |
|
self._input_files: T.List[Path] = [] |
|
|
|
@property |
|
def input_files(self) -> T.List[Path]: |
|
return list(self._input_files) |
|
|
|
def read_file(self, f: Path) -> str: |
|
assert f.exists() |
|
assert f.is_file() |
|
self._input_files += [f.resolve()] |
|
return f.read_text(encoding='utf-8') |
|
|
|
@abstractmethod |
|
def load_impl(self) -> ReferenceManual: |
|
pass |
|
|
|
def load(self) -> ReferenceManual: |
|
self._input_files = [] # Reset input files |
|
manual = self.load_impl() |
|
resolver = _Resolver() |
|
with mlog.nested(): |
|
return resolver.validate_and_resolve(manual)
|
|
|