@@ -74,6 +74,94 @@ def __init__(self, name: str, environment: Environment, kwargs: T.Dict[str, T.An
7474 self .compile_args = self .get_config_value (['--cflags' ], 'compile_args' )
7575
7676
77+
78+ class PythonBuildConfig :
79+ """PEP 739 build-details.json config file."""
80+
81+ """Schema version currently implemented."""
82+ IMPLEMENTED_VERSION : T .Final [str ] = '1.0'
83+ """Path keys — may be relative, need to be expanded."""
84+ _PATH_KEYS = (
85+ 'base_interpreter' ,
86+ 'libpython.dynamic' ,
87+ 'libpython.dynamic_stableabi' ,
88+ 'libpython.static' ,
89+ 'c_api.headers' ,
90+ 'c_api.pkgconfig_path' ,
91+ )
92+
93+ def __init__ (self , path : str ) -> None :
94+ self ._path = Path (path )
95+
96+ try :
97+ self ._data = json .loads (self ._path .read_text (encoding = 'utf8' ))
98+ except OSError as e :
99+ raise DependencyException (f'Failed to read python.build_config: { e } ' ) from e
100+
101+ self ._validate_data ()
102+ self ._expand_paths ()
103+
104+ def __getitem__ (self , key : str ) -> T .Any :
105+ value = self ._data
106+ for part in key .split ('.' ):
107+ value = value [part ]
108+ return value
109+
110+ def __contains__ (self , key : str ) -> bool :
111+ try :
112+ self [key ]
113+ except KeyError :
114+ return False
115+ else :
116+ return True
117+
118+ def get (self , key : str , default : T .Any = None ) -> T .Any :
119+ try :
120+ self [key ]
121+ except KeyError :
122+ return default
123+
124+ def _validate_data (self ) -> None :
125+ schema_version = self ._data ['schema_version' ]
126+ if mesonlib .version_compare (schema_version , '< 1.0' ):
127+ raise DependencyException (f'Invalid schema_version in python.build_config: { schema_version } ' )
128+ if mesonlib .version_compare (schema_version , '>= 2.0' ):
129+ raise DependencyException (
130+ f'Unsupported schema_version { schema_version !r} in python.build_config, '
131+ f'but we only implement suport for { self .IMPLEMENTED_VERSION !r} '
132+ )
133+ # Schema version that we currently understand
134+ if mesonlib .version_compare (schema_version , f'> { self .IMPLEMENTED_VERSION } ' ):
135+ mlog .info (
136+ f'python.build_config has schema_version { schema_version !r} , '
137+ f'but we only implement suport for { self .IMPLEMENTED_VERSION !r} , '
138+ 'new functionality might be missing'
139+ )
140+
141+ def _expand_paths (self ) -> None :
142+ """Expand relative path (they're relative to base_prefix)."""
143+ for key in self ._PATH_KEYS :
144+ if key not in self :
145+ continue
146+ parent , _ , child = key .rpartition ('.' )
147+ container = self [parent ] if parent else self ._data
148+ path = Path (container [child ])
149+ if not path .is_absolute ():
150+ container [child ] = os .fspath (self .base_prefix / path )
151+
152+ @property
153+ def config_path (self ) -> Path :
154+ return self ._path
155+
156+ @functools .cached_property
157+ def base_prefix (self ) -> Path :
158+ path = Path (self ._data ['base_prefix' ])
159+ if path .is_absolute ():
160+ return path
161+ # Non-absolute paths are relative to the build config directory
162+ return self .config_path .parent / path
163+
164+
77165class BasicPythonExternalProgram (ExternalProgram ):
78166 def __init__ (self , name : str , command : T .Optional [T .List [str ]] = None ,
79167 ext_prog : T .Optional [ExternalProgram ] = None ,
@@ -87,14 +175,7 @@ def __init__(self, name: str, command: T.Optional[T.List[str]] = None,
87175 self .cached_version = None
88176 self .version_arg = '--version'
89177
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
178+ self .build_config = PythonBuildConfig (build_config_path ) if build_config_path else None
98179
99180 # We want strong key values, so we always populate this with bogus data.
100181 # Otherwise to make the type checkers happy we'd have to do .get() for
0 commit comments