14
14
import argparse
15
15
import collections
16
16
import contextlib
17
+ import copy
17
18
import difflib
18
19
import functools
19
20
import importlib .machinery
42
43
else :
43
44
import tomllib
44
45
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
45
52
import packaging .version
46
53
import pyproject_metadata
47
54
@@ -125,6 +132,8 @@ def _init_colors() -> Dict[str, str]:
125
132
_EXTENSION_SUFFIX_REGEX = re .compile (r'^\.(?:(?P<abi>[^.]+)\.)?(?:so|pyd|dll)$' )
126
133
assert all (re .match (_EXTENSION_SUFFIX_REGEX , x ) for x in _EXTENSION_SUFFIXES )
127
134
135
+ _REQUIREMENT_NAME_REGEX = re .compile (r'^(?P<name>[A-Za-z0-9][A-Za-z0-9-_.]+)' )
136
+
128
137
129
138
def _showwarning (
130
139
message : Union [Warning , str ],
@@ -197,14 +206,15 @@ def __init__(
197
206
build_dir : pathlib .Path ,
198
207
sources : Dict [str , Dict [str , Any ]],
199
208
copy_files : Dict [str , str ],
209
+ build_time_pins_templates : List [str ],
200
210
) -> None :
201
211
self ._project = project
202
212
self ._source_dir = source_dir
203
213
self ._install_dir = install_dir
204
214
self ._build_dir = build_dir
205
215
self ._sources = sources
206
216
self ._copy_files = copy_files
207
-
217
+ self . _build_time_pins = build_time_pins_templates
208
218
self ._libs_build_dir = self ._build_dir / 'mesonpy-wheel-libs'
209
219
210
220
@cached_property
@@ -550,8 +560,12 @@ def _install_path(
550
560
wheel_file .write (origin , location )
551
561
552
562
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
+
553
567
# add metadata
554
- whl .writestr (f'{ self .distinfo_dir } /METADATA' , bytes (self . _project . metadata .as_rfc822 ()))
568
+ whl .writestr (f'{ self .distinfo_dir } /METADATA' , bytes (metadata .as_rfc822 ()))
555
569
whl .writestr (f'{ self .distinfo_dir } /WHEEL' , self .wheel )
556
570
if self .entrypoints_txt :
557
571
whl .writestr (f'{ self .distinfo_dir } /entry_points.txt' , self .entrypoints_txt )
@@ -677,7 +691,9 @@ def _strings(value: Any, name: str) -> List[str]:
677
691
scheme = _table ({
678
692
'args' : _table ({
679
693
name : _strings for name in _MESON_ARGS_KEYS
680
- })
694
+ }),
695
+ 'dependencies' : _strings ,
696
+ 'build-time-pins' : _strings ,
681
697
})
682
698
683
699
table = pyproject .get ('tool' , {}).get ('meson-python' , {})
@@ -726,6 +742,7 @@ def _validate_metadata(metadata: pyproject_metadata.StandardMetadata) -> None:
726
742
"""Validate package metadata."""
727
743
728
744
allowed_dynamic_fields = [
745
+ 'dependencies' ,
729
746
'version' ,
730
747
]
731
748
@@ -742,9 +759,36 @@ def _validate_metadata(metadata: pyproject_metadata.StandardMetadata) -> None:
742
759
raise ConfigError (f'building with Python { platform .python_version ()} , version { metadata .requires_python } required' )
743
760
744
761
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 } " specified in "build-time-pins" not found: { 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
+
745
789
class Project ():
746
790
"""Meson project wrapper to generate Python artifacts."""
747
- def __init__ (
791
+ def __init__ ( # noqa: C901
748
792
self ,
749
793
source_dir : Path ,
750
794
working_dir : Path ,
@@ -761,6 +805,7 @@ def __init__(
761
805
self ._meson_cross_file = self ._build_dir / 'meson-python-cross-file.ini'
762
806
self ._meson_args : MesonArgs = collections .defaultdict (list )
763
807
self ._env = os .environ .copy ()
808
+ self ._build_time_pins = []
764
809
765
810
# prepare environment
766
811
self ._ninja = _env_ninja_command ()
@@ -846,6 +891,13 @@ def __init__(
846
891
if 'version' in self ._metadata .dynamic :
847
892
self ._metadata .version = packaging .version .Version (self ._meson_version )
848
893
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
+
849
901
def _run (self , cmd : Sequence [str ]) -> None :
850
902
"""Invoke a subprocess."""
851
903
print ('{cyan}{bold}+ {}{reset}' .format (' ' .join (cmd ), ** _STYLES ))
@@ -890,6 +942,7 @@ def _wheel_builder(self) -> _WheelBuilder:
890
942
self ._build_dir ,
891
943
self ._install_plan ,
892
944
self ._copy_files ,
945
+ self ._build_time_pins ,
893
946
)
894
947
895
948
def build_commands (self , install_dir : Optional [pathlib .Path ] = None ) -> Sequence [Sequence [str ]]:
0 commit comments