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