Skip to content

Commit f30406d

Browse files
committed
python: validate python.build_config version and expand relative paths
Signed-off-by: Filipe Laíns <[email protected]>
1 parent 64e51ab commit f30406d

File tree

1 file changed

+94
-9
lines changed

1 file changed

+94
-9
lines changed

mesonbuild/dependencies/python.py

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from __future__ import annotations
55

6-
import functools, json, os, textwrap
6+
import functools, json, os, sys, textwrap
77
from pathlib import Path
88
import typing as T
99

@@ -45,6 +45,11 @@ class PythonIntrospectionDict(TypedDict):
4545
_Base = object
4646

4747

48+
if sys.version_info >= (3, 8):
49+
from functools import cached_property
50+
else:
51+
cached_property = property
52+
4853
class Pybind11ConfigToolDependency(ConfigToolDependency):
4954

5055
tools = ['pybind11-config']
@@ -74,6 +79,93 @@ def __init__(self, name: str, environment: Environment, kwargs: T.Dict[str, T.An
7479
self.compile_args = self.get_config_value(['--cflags'], 'compile_args')
7580

7681

82+
class PythonBuildConfig:
83+
"""PEP 739 build-details.json config file."""
84+
85+
"""Schema version currently implemented."""
86+
IMPLEMENTED_VERSION: T.Final[str] = '1.0'
87+
"""Path keys — may be relative, need to be expanded."""
88+
_PATH_KEYS = (
89+
'base_interpreter',
90+
'libpython.dynamic',
91+
'libpython.dynamic_stableabi',
92+
'libpython.static',
93+
'c_api.headers',
94+
'c_api.pkgconfig_path',
95+
)
96+
97+
def __init__(self, path: str) -> None:
98+
self._path = Path(path)
99+
100+
try:
101+
self._data = json.loads(self._path.read_text(encoding='utf8'))
102+
except OSError as e:
103+
raise DependencyException(f'Failed to read python.build_config: {e}') from e
104+
105+
self._validate_data()
106+
self._expand_paths()
107+
108+
def __getitem__(self, key: str) -> T.Any:
109+
value = self._data
110+
for part in key.split('.'):
111+
value = value[part]
112+
return value
113+
114+
def __contains__(self, key: str) -> bool:
115+
try:
116+
self[key]
117+
except KeyError:
118+
return False
119+
else:
120+
return True
121+
122+
def get(self, key: str, default: T.Any = None) -> T.Any:
123+
try:
124+
self[key]
125+
except KeyError:
126+
return default
127+
128+
def _validate_data(self) -> None:
129+
schema_version = self._data['schema_version']
130+
if mesonlib.version_compare(schema_version, '< 1.0'):
131+
raise DependencyException(f'Invalid schema_version in python.build_config: {schema_version}')
132+
if mesonlib.version_compare(schema_version, '>= 2.0'):
133+
raise DependencyException(
134+
f'Unsupported schema_version {schema_version!r} in python.build_config, '
135+
f'but we only implement suport for {self.IMPLEMENTED_VERSION!r}'
136+
)
137+
# Schema version that we currently understand
138+
if mesonlib.version_compare(schema_version, f'> {self.IMPLEMENTED_VERSION}'):
139+
mlog.log(
140+
f'python.build_config has schema_version {schema_version!r}, '
141+
f'but we only implement suport for {self.IMPLEMENTED_VERSION!r}, '
142+
'new functionality might be missing'
143+
)
144+
145+
def _expand_paths(self) -> None:
146+
"""Expand relative path (they're relative to base_prefix)."""
147+
for key in self._PATH_KEYS:
148+
if key not in self:
149+
continue
150+
parent, _, child = key.rpartition('.')
151+
container = self[parent] if parent else self._data
152+
path = Path(container[child])
153+
if not path.is_absolute():
154+
container[child] = os.fspath(self.base_prefix / path)
155+
156+
@property
157+
def config_path(self) -> Path:
158+
return self._path
159+
160+
@cached_property
161+
def base_prefix(self) -> Path:
162+
path = Path(self._data['base_prefix'])
163+
if path.is_absolute():
164+
return path
165+
# Non-absolute paths are relative to the build config directory
166+
return self.config_path.parent / path
167+
168+
77169
class BasicPythonExternalProgram(ExternalProgram):
78170
def __init__(self, name: str, command: T.Optional[T.List[str]] = None,
79171
ext_prog: T.Optional[ExternalProgram] = None,
@@ -87,14 +179,7 @@ def __init__(self, name: str, command: T.Optional[T.List[str]] = None,
87179
self.cached_version = None
88180
self.version_arg = '--version'
89181

90-
self.build_config = None
91-
92-
if build_config_path:
93-
try:
94-
with open(build_config_path, encoding='utf8') as f:
95-
self.build_config = json.load(f)
96-
except OSError as e:
97-
raise DependencyException(f'Failed to read python.build_config: {e}') from e
182+
self.build_config = PythonBuildConfig(build_config_path) if build_config_path else None
98183

99184
# We want strong key values, so we always populate this with bogus data.
100185
# Otherwise to make the type checkers happy we'd have to do .get() for

0 commit comments

Comments
 (0)