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