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