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
@@ -132,6 +139,8 @@ def _init_colors() -> Dict[str, str]:
132
139
_EXTENSION_SUFFIX_REGEX = re .compile (r'^\.(?:(?P<abi>[^.]+)\.)?(?:so|pyd|dll)$' )
133
140
assert all (re .match (_EXTENSION_SUFFIX_REGEX , x ) for x in _EXTENSION_SUFFIXES )
134
141
142
+ _REQUIREMENT_NAME_REGEX = re .compile (r'^(?P<name>[A-Za-z0-9][A-Za-z0-9-_.]+)' )
143
+
135
144
136
145
# Maps wheel installation paths to Meson installation path placeholders.
137
146
# See https://docs.python.org/3/library/sysconfig.html#installation-paths
@@ -222,12 +231,13 @@ def __init__(
222
231
source_dir : pathlib .Path ,
223
232
build_dir : pathlib .Path ,
224
233
sources : Dict [str , Dict [str , Any ]],
234
+ build_time_pins_templates : List [str ],
225
235
) -> None :
226
236
self ._project = project
227
237
self ._source_dir = source_dir
228
238
self ._build_dir = build_dir
229
239
self ._sources = sources
230
-
240
+ self . _build_time_pins = build_time_pins_templates
231
241
self ._libs_build_dir = self ._build_dir / 'mesonpy-wheel-libs'
232
242
233
243
@cached_property
@@ -472,8 +482,12 @@ def _install_path(
472
482
wheel_file .write (origin , location )
473
483
474
484
def _wheel_write_metadata (self , whl : mesonpy ._wheelfile .WheelFile ) -> None :
485
+ # copute dynamic dependencies
486
+ metadata = copy .copy (self ._project .metadata )
487
+ metadata .dependencies = _compute_build_time_dependencies (metadata .dependencies , self ._build_time_pins )
488
+
475
489
# add metadata
476
- whl .writestr (f'{ self .distinfo_dir } /METADATA' , bytes (self . _project . metadata .as_rfc822 ()))
490
+ whl .writestr (f'{ self .distinfo_dir } /METADATA' , bytes (metadata .as_rfc822 ()))
477
491
whl .writestr (f'{ self .distinfo_dir } /WHEEL' , self .wheel )
478
492
if self .entrypoints_txt :
479
493
whl .writestr (f'{ self .distinfo_dir } /entry_points.txt' , self .entrypoints_txt )
@@ -573,7 +587,9 @@ def _strings(value: Any, name: str) -> List[str]:
573
587
scheme = _table ({
574
588
'args' : _table ({
575
589
name : _strings for name in _MESON_ARGS_KEYS
576
- })
590
+ }),
591
+ 'dependencies' : _strings ,
592
+ 'build-time-pins' : _strings ,
577
593
})
578
594
579
595
table = pyproject .get ('tool' , {}).get ('meson-python' , {})
@@ -622,6 +638,7 @@ def _validate_metadata(metadata: pyproject_metadata.StandardMetadata) -> None:
622
638
"""Validate package metadata."""
623
639
624
640
allowed_dynamic_fields = [
641
+ 'dependencies' ,
625
642
'version' ,
626
643
]
627
644
@@ -638,9 +655,36 @@ def _validate_metadata(metadata: pyproject_metadata.StandardMetadata) -> None:
638
655
raise ConfigError (f'building with Python { platform .python_version ()} , version { metadata .requires_python } required' )
639
656
640
657
658
+ def _compute_build_time_dependencies (
659
+ dependencies : List [packaging .requirements .Requirement ],
660
+ pins : List [str ]) -> List [packaging .requirements .Requirement ]:
661
+ for template in pins :
662
+ match = _REQUIREMENT_NAME_REGEX .match (template )
663
+ if not match :
664
+ raise ConfigError (f'invalid requirement format in "build-time-pins": { template !r} ' )
665
+ name = match .group (1 )
666
+ try :
667
+ version = packaging .version .parse (importlib_metadata .version (name ))
668
+ except importlib_metadata .PackageNotFoundError as exc :
669
+ raise ConfigError (f'package "{ name } " specified in "build-time-pins" not found: { template !r} ' ) from exc
670
+ pin = packaging .requirements .Requirement (template .format (v = version ))
671
+ if pin .marker :
672
+ raise ConfigError (f'requirements in "build-time-pins" cannot contain markers: { template !r} ' )
673
+ if pin .extras :
674
+ raise ConfigError (f'requirements in "build-time-pins" cannot contain extras: { template !r} ' )
675
+ added = False
676
+ for d in dependencies :
677
+ if d .name == name :
678
+ d .specifier = d .specifier & pin .specifier
679
+ added = True
680
+ if not added :
681
+ dependencies .append (pin )
682
+ return dependencies
683
+
684
+
641
685
class Project ():
642
686
"""Meson project wrapper to generate Python artifacts."""
643
- def __init__ (
687
+ def __init__ ( # noqa: C901
644
688
self ,
645
689
source_dir : Path ,
646
690
working_dir : Path ,
@@ -657,6 +701,7 @@ def __init__(
657
701
self ._meson_cross_file = self ._build_dir / 'meson-python-cross-file.ini'
658
702
self ._meson_args : MesonArgs = collections .defaultdict (list )
659
703
self ._env = os .environ .copy ()
704
+ self ._build_time_pins = []
660
705
661
706
_check_meson_version ()
662
707
@@ -743,6 +788,13 @@ def __init__(
743
788
if 'version' in self ._metadata .dynamic :
744
789
self ._metadata .version = packaging .version .Version (self ._meson_version )
745
790
791
+ # set base dependencie if dynamic
792
+ if 'dependencies' in self ._metadata .dynamic :
793
+ dependencies = [packaging .requirements .Requirement (d ) for d in pyproject_config .get ('dependencies' , [])]
794
+ self ._metadata .dependencies = dependencies
795
+ self ._metadata .dynamic .remove ('dependencies' )
796
+ self ._build_time_pins = pyproject_config .get ('build-time-pins' , [])
797
+
746
798
def _run (self , cmd : Sequence [str ]) -> None :
747
799
"""Invoke a subprocess."""
748
800
print ('{cyan}{bold}+ {}{reset}' .format (' ' .join (cmd ), ** _STYLES ))
@@ -786,6 +838,7 @@ def _wheel_builder(self) -> _WheelBuilder:
786
838
self ._source_dir ,
787
839
self ._build_dir ,
788
840
self ._install_plan ,
841
+ self ._build_time_pins ,
789
842
)
790
843
791
844
def build_commands (self , install_dir : Optional [pathlib .Path ] = None ) -> Sequence [Sequence [str ]]:
0 commit comments