1414import  argparse 
1515import  collections 
1616import  contextlib 
17+ import  copy 
1718import  difflib 
1819import  functools 
1920import  importlib .machinery 
4243else :
4344    import  tomllib 
4445
46+ if  sys .version_info  <  (3 , 8 ):
47+     import  importlib_metadata 
48+ else :
49+     import  importlib .metadata  as  importlib_metadata 
50+ 
51+ import  packaging .requirements 
4552import  packaging .version 
4653import  pyproject_metadata 
4754
@@ -125,6 +132,8 @@ def _init_colors() -> Dict[str, str]:
125132_EXTENSION_SUFFIX_REGEX  =  re .compile (r'^\.(?:(?P<abi>[^.]+)\.)?(?:so|pyd|dll)$' )
126133assert  all (re .match (_EXTENSION_SUFFIX_REGEX , x ) for  x  in  _EXTENSION_SUFFIXES )
127134
135+ _REQUIREMENT_NAME_REGEX  =  re .compile (r'^(?P<name>[A-Za-z0-9][A-Za-z0-9-_.]+)' )
136+ 
128137
129138def  _showwarning (
130139    message : Union [Warning , str ],
@@ -197,14 +206,15 @@ def __init__(
197206        build_dir : pathlib .Path ,
198207        sources : Dict [str , Dict [str , Any ]],
199208        copy_files : Dict [str , str ],
209+         build_time_pins_templates : List [str ],
200210    ) ->  None :
201211        self ._project  =  project 
202212        self ._source_dir  =  source_dir 
203213        self ._install_dir  =  install_dir 
204214        self ._build_dir  =  build_dir 
205215        self ._sources  =  sources 
206216        self ._copy_files  =  copy_files 
207- 
217+          self . _build_time_pins   =   build_time_pins_templates 
208218        self ._libs_build_dir  =  self ._build_dir  /  'mesonpy-wheel-libs' 
209219
210220    @cached_property  
@@ -550,8 +560,12 @@ def _install_path(
550560            wheel_file .write (origin , location )
551561
552562    def  _wheel_write_metadata (self , whl : mesonpy ._wheelfile .WheelFile ) ->  None :
563+         # copute dynamic dependencies 
564+         metadata  =  copy .copy (self ._project .metadata )
565+         metadata .dependencies  =  _compute_build_time_dependencies (metadata .dependencies , self ._build_time_pins )
566+ 
553567        # add metadata 
554-         whl .writestr (f'{ self .distinfo_dir }  , bytes (self . _project . metadata .as_rfc822 ()))
568+         whl .writestr (f'{ self .distinfo_dir }  , bytes (metadata .as_rfc822 ()))
555569        whl .writestr (f'{ self .distinfo_dir }  , self .wheel )
556570        if  self .entrypoints_txt :
557571            whl .writestr (f'{ self .distinfo_dir }  , self .entrypoints_txt )
@@ -677,7 +691,9 @@ def _strings(value: Any, name: str) -> List[str]:
677691    scheme  =  _table ({
678692        'args' : _table ({
679693            name : _strings  for  name  in  _MESON_ARGS_KEYS 
680-         })
694+         }),
695+         'dependencies' : _strings ,
696+         'build-time-pins' : _strings ,
681697    })
682698
683699    table  =  pyproject .get ('tool' , {}).get ('meson-python' , {})
@@ -726,6 +742,7 @@ def _validate_metadata(metadata: pyproject_metadata.StandardMetadata) -> None:
726742    """Validate package metadata.""" 
727743
728744    allowed_dynamic_fields  =  [
745+         'dependencies' ,
729746        'version' ,
730747    ]
731748
@@ -742,9 +759,36 @@ def _validate_metadata(metadata: pyproject_metadata.StandardMetadata) -> None:
742759            raise  ConfigError (f'building with Python { platform .python_version ()} { metadata .requires_python }  )
743760
744761
762+ def  _compute_build_time_dependencies (
763+         dependencies : List [packaging .requirements .Requirement ],
764+         pins : List [str ]) ->  List [packaging .requirements .Requirement ]:
765+     for  template  in  pins :
766+         match  =  _REQUIREMENT_NAME_REGEX .match (template )
767+         if  not  match :
768+             raise  ConfigError (f'invalid requirement format in "build-time-pins": { template !r}  )
769+         name  =  match .group (1 )
770+         try :
771+             version  =  packaging .version .parse (importlib_metadata .version (name ))
772+         except  importlib_metadata .PackageNotFoundError  as  exc :
773+             raise  ConfigError (f'package "{ name } { template !r}  ) from  exc 
774+         pin  =  packaging .requirements .Requirement (template .format (v = version ))
775+         if  pin .marker :
776+             raise  ConfigError (f'requirements in "build-time-pins" cannot contain markers: { template !r}  )
777+         if  pin .extras :
778+             raise  ConfigError (f'requirements in "build-time-pins" cannot contain erxtras: { template !r}  )
779+         added  =  False 
780+         for  d  in  dependencies :
781+             if  d .name  ==  name :
782+                 d .specifier  =  d .specifier  &  pin .specifier 
783+                 added  =  True 
784+         if  not  added :
785+             dependencies .append (pin )
786+     return  dependencies 
787+ 
788+ 
745789class  Project ():
746790    """Meson project wrapper to generate Python artifacts.""" 
747-     def  __init__ (
791+     def  __init__ (   # noqa: C901 
748792        self ,
749793        source_dir : Path ,
750794        working_dir : Path ,
@@ -761,6 +805,7 @@ def __init__(
761805        self ._meson_cross_file  =  self ._build_dir  /  'meson-python-cross-file.ini' 
762806        self ._meson_args : MesonArgs  =  collections .defaultdict (list )
763807        self ._env  =  os .environ .copy ()
808+         self ._build_time_pins  =  []
764809
765810        # prepare environment 
766811        self ._ninja  =  _env_ninja_command ()
@@ -846,6 +891,13 @@ def __init__(
846891        if  'version'  in  self ._metadata .dynamic :
847892            self ._metadata .version  =  packaging .version .Version (self ._meson_version )
848893
894+         # set base dependencie if dynamic 
895+         if  'dependencies'  in  self ._metadata .dynamic :
896+             dependencies  =  [packaging .requirements .Requirement (d ) for  d  in  pyproject_config .get ('dependencies' , [])]
897+             self ._metadata .dependencies  =  dependencies 
898+             self ._metadata .dynamic .remove ('dependencies' )
899+             self ._build_time_pins  =  pyproject_config .get ('build-time-pins' , [])
900+ 
849901    def  _run (self , cmd : Sequence [str ]) ->  None :
850902        """Invoke a subprocess.""" 
851903        print ('{cyan}{bold}+ {}{reset}' .format (' ' .join (cmd ), ** _STYLES ))
@@ -890,6 +942,7 @@ def _wheel_builder(self) -> _WheelBuilder:
890942            self ._build_dir ,
891943            self ._install_plan ,
892944            self ._copy_files ,
945+             self ._build_time_pins ,
893946        )
894947
895948    def  build_commands (self , install_dir : Optional [pathlib .Path ] =  None ) ->  Sequence [Sequence [str ]]:
0 commit comments