32
32
import warnings
33
33
34
34
from typing import (
35
- Any , ClassVar , DefaultDict , Dict , List , Optional , Set , TextIO , Tuple , Type ,
36
- Union
35
+ Any , ClassVar , DefaultDict , Dict , List , Optional , Sequence , Set , TextIO ,
36
+ Tuple , Type , Union
37
37
)
38
38
39
39
47
47
import mesonpy ._tags
48
48
import mesonpy ._util
49
49
50
- from mesonpy ._compat import Iterator , Path
50
+ from mesonpy ._compat import Collection , Iterator , Literal , Mapping , Path
51
51
52
52
53
53
if typing .TYPE_CHECKING : # pragma: no cover
@@ -134,6 +134,10 @@ def _setup_cli() -> None:
134
134
colorama .init () # fix colors on windows
135
135
136
136
137
+ class ConfigError (Exception ):
138
+ """Error in the backend configuration."""
139
+
140
+
137
141
class MesonBuilderError (Exception ):
138
142
"""Error when building the Meson package."""
139
143
@@ -538,6 +542,9 @@ def build(self, directory: Path) -> pathlib.Path:
538
542
return wheel_file
539
543
540
544
545
+ MesonArgs = Mapping [Literal ['dist' , 'setup' , 'compile' , 'install' ], Collection [str ]]
546
+
547
+
541
548
class Project ():
542
549
"""Meson project wrapper to generate Python artifacts."""
543
550
@@ -551,6 +558,7 @@ def __init__(
551
558
source_dir : Path ,
552
559
working_dir : Path ,
553
560
build_dir : Optional [Path ] = None ,
561
+ meson_args : Optional [MesonArgs ] = None ,
554
562
) -> None :
555
563
self ._source_dir = pathlib .Path (source_dir ).absolute ()
556
564
self ._working_dir = pathlib .Path (working_dir ).absolute ()
@@ -578,6 +586,13 @@ def __init__(
578
586
if self ._metadata :
579
587
self ._validate_metadata ()
580
588
589
+ # load meson args
590
+ self ._meson_args = collections .defaultdict (tuple , meson_args or {})
591
+ for key in self ._get_config_key ('args' ):
592
+ args_from_config = tuple (self ._get_config_key (f'args.{ key } ' ))
593
+ self ._meson_args [key ] = args_from_config + tuple (self ._meson_args [key ])
594
+ # XXX: We should validate the user args to make sure they don't conflict with ours.
595
+
581
596
# make sure the build dir exists
582
597
self ._build_dir .mkdir (exist_ok = True )
583
598
self ._install_dir .mkdir (exist_ok = True )
@@ -608,6 +623,17 @@ def __init__(
608
623
if self ._metadata and 'version' in self ._metadata .dynamic :
609
624
self ._metadata .version = self .version
610
625
626
+ def _get_config_key (self , key : str ) -> Any :
627
+ value : Any = self ._config
628
+ for part in f'tool.mesonpy.{ key } ' .split ('.' ):
629
+ if not isinstance (value , Mapping ):
630
+ raise ConfigError (
631
+ f'Found unexpected value in `{ part } ` when looking for '
632
+ f'config key `tool.mesonpy.{ key } ` (`{ value } `)'
633
+ )
634
+ value = value .get (part , {})
635
+ return value
636
+
611
637
def _proc (self , * args : str ) -> None :
612
638
"""Invoke a subprocess."""
613
639
print ('{cyan}{bold}+ {}{reset}' .format (' ' .join (args ), ** _STYLES ))
@@ -628,19 +654,18 @@ def _configure(self, reconfigure: bool = False) -> None:
628
654
f'--prefix={ sys .base_prefix } ' ,
629
655
os .fspath (self ._source_dir ),
630
656
os .fspath (self ._build_dir ),
657
+ f'--native-file={ os .fspath (self ._meson_native_file )} ' ,
658
+ # TODO: Allow configuring these arguments
659
+ '-Ddebug=false' ,
660
+ '-Doptimization=2' ,
661
+ # user args
662
+ * self ._meson_args ['setup' ],
631
663
]
632
664
if reconfigure :
633
665
setup_args .insert (0 , '--reconfigure' )
634
666
635
667
try :
636
- self ._meson (
637
- 'setup' ,
638
- f'--native-file={ os .fspath (self ._meson_native_file )} ' ,
639
- # TODO: Allow configuring these arguments
640
- '-Ddebug=false' ,
641
- '-Doptimization=2' ,
642
- * setup_args ,
643
- )
668
+ self ._meson ('setup' , * setup_args )
644
669
except subprocess .CalledProcessError :
645
670
if reconfigure : # if failed reconfiguring, try a normal configure
646
671
self ._configure ()
@@ -686,19 +711,20 @@ def _wheel_builder(self) -> _WheelBuilder:
686
711
@functools .lru_cache (maxsize = None )
687
712
def build (self ) -> None :
688
713
"""Trigger the Meson build."""
689
- self ._meson ('compile' )
690
- self ._meson ('install' , '--destdir' , os .fspath (self ._install_dir ))
714
+ self ._meson ('compile' , * self . _meson_args [ 'compile' ], )
715
+ self ._meson ('install' , '--destdir' , os .fspath (self ._install_dir ), * self . _meson_args [ 'install' ], )
691
716
692
717
@classmethod
693
718
@contextlib .contextmanager
694
719
def with_temp_working_dir (
695
720
cls ,
696
721
source_dir : Path = os .path .curdir ,
697
722
build_dir : Optional [Path ] = None ,
723
+ meson_args : Optional [MesonArgs ] = None ,
698
724
) -> Iterator [Project ]:
699
725
"""Creates a project instance pointing to a temporary working directory."""
700
726
with tempfile .TemporaryDirectory (prefix = '.mesonpy-' , dir = os .fspath (source_dir )) as tmpdir :
701
- yield cls (source_dir , tmpdir , build_dir )
727
+ yield cls (source_dir , tmpdir , build_dir , meson_args )
702
728
703
729
@functools .lru_cache ()
704
730
def _info (self , name : str ) -> Dict [str , Any ]:
@@ -806,7 +832,7 @@ def pep621(self) -> bool:
806
832
def sdist (self , directory : Path ) -> pathlib .Path :
807
833
"""Generates a sdist (source distribution) in the specified directory."""
808
834
# generate meson dist file
809
- self ._meson ('dist' , '--allow-dirty' , '--no-tests' , '--formats' , 'gztar' )
835
+ self ._meson ('dist' , '--allow-dirty' , '--no-tests' , '--formats' , 'gztar' , * self . _meson_args [ 'dist' ], )
810
836
811
837
# move meson dist file to output path
812
838
dist_name = f'{ self .name } -{ self .version } '
@@ -882,8 +908,51 @@ def _project(config_settings: Optional[Dict[Any, Any]]) -> Iterator[Project]:
882
908
if config_settings is None :
883
909
config_settings = {}
884
910
911
+ # expand all string values to single element tuples and convert collections to tuple
912
+ config_settings = {
913
+ key : tuple (value ) if isinstance (value , Collection ) and not isinstance (value , str ) else (value ,)
914
+ for key , value in config_settings .items ()
915
+ }
916
+
917
+ builddir_value = config_settings .get ('builddir' , {})
918
+ if len (builddir_value ) > 0 :
919
+ if len (builddir_value ) != 1 :
920
+ raise ConfigError ('Specified multiple values for `builddir`, only one is allowed' )
921
+ builddir = builddir_value [0 ]
922
+ if not isinstance (builddir , str ):
923
+ raise ConfigError (f'Config option `builddir` should be a string (found `{ type (builddir )} `)' )
924
+ else :
925
+ builddir = None
926
+
927
+ def _validate_string_collection (key : str ) -> None :
928
+ assert isinstance (config_settings , Mapping )
929
+ problematic_items : Sequence [Any ] = list (filter (None , (
930
+ item if not isinstance (item , str ) else None
931
+ for item in config_settings .get (key , ())
932
+ )))
933
+ if problematic_items :
934
+ raise ConfigError (
935
+ f'Config option `{ key } ` should only contain string items, but '
936
+ 'contains the following parameters that do not meet this criteria:' +
937
+ '' .join ((
938
+ f'\t - { item } (type: { type (item )} )'
939
+ for item in problematic_items
940
+ ))
941
+ )
942
+
943
+ _validate_string_collection ('dist_args' )
944
+ _validate_string_collection ('setup_args' )
945
+ _validate_string_collection ('compile_args' )
946
+ _validate_string_collection ('install_args' )
947
+
885
948
with Project .with_temp_working_dir (
886
- build_dir = config_settings .get ('builddir' ),
949
+ build_dir = builddir ,
950
+ meson_args = typing .cast (MesonArgs , {
951
+ 'dist' : config_settings .get ('dist_args' , ()),
952
+ 'setup' : config_settings .get ('setup_args' , ()),
953
+ 'compile' : config_settings .get ('compile_args' , ()),
954
+ 'install' : config_settings .get ('install_args' , ()),
955
+ }),
887
956
) as project :
888
957
yield project
889
958
0 commit comments