@@ -531,8 +531,15 @@ def _top_level_modules(self) -> Collection[str]:
531531 modules .add (name )
532532 return modules
533533
534- def build (self , directory : Path , source_dir : pathlib .Path , build_dir : pathlib .Path , # type: ignore[override]
535- build_command : List [str ], verbose : bool = False ) -> pathlib .Path :
534+ def build ( # type: ignore[override]
535+ self ,
536+ directory : Path ,
537+ source_dir : pathlib .Path ,
538+ build_dir : pathlib .Path ,
539+ build_command : List [str ],
540+ build_env : Dict [str , str ],
541+ verbose : bool = False ,
542+ ) -> pathlib .Path :
536543
537544 wheel_file = pathlib .Path (directory , f'{ self .name } .whl' )
538545 with mesonpy ._wheelfile .WheelFile (wheel_file , 'w' ) as whl :
@@ -551,6 +558,7 @@ def build(self, directory: Path, source_dir: pathlib.Path, build_dir: pathlib.Pa
551558 { self ._top_level_modules !r} ,
552559 { os .fspath (build_dir )!r} ,
553560 { build_command !r} ,
561+ { build_env !r} ,
554562 { verbose !r} ,
555563 )''' ).encode ('utf-8' ))
556564
@@ -587,6 +595,11 @@ def _bool(value: Any, name: str) -> bool:
587595 raise ConfigError (f'Configuration entry "{ name } " must be a boolean' )
588596 return value
589597
598+ def _strings_table (value : Any , name : str ) -> Dict [str , str ]:
599+ if not isinstance (value , dict ) or not all (isinstance (k , str ) and isinstance (v , str ) for k , v in value .items ()):
600+ raise ConfigError (f'Configuration entry "{ name } " must be a table with string values' )
601+ return value
602+
590603 def _string_or_path (value : Any , name : str ) -> str :
591604 if not isinstance (value , str ):
592605 raise ConfigError (f'Configuration entry "{ name } " must be a string' )
@@ -598,6 +611,7 @@ def _string_or_path(value: Any, name: str) -> str:
598611 'meson' : _string_or_path ,
599612 'limited-api' : _bool ,
600613 'allow-windows-internal-shared-libs' : _bool ,
614+ 'env' : _strings_table ,
601615 'args' : _table (dict .fromkeys (_MESON_ARGS_KEYS , _strings )),
602616 'wheel' : _table ({
603617 'exclude' : _strings ,
@@ -682,6 +696,9 @@ def __init__(
682696
683697 # load meson args from pyproject.toml
684698 pyproject_config = _validate_pyproject_config (pyproject )
699+ self ._config_env = pyproject_config .get ('env' , {})
700+ self ._env = os .environ .copy ()
701+ self ._env .update (self ._config_env )
685702 for key , value in pyproject_config .get ('args' , {}).items ():
686703 self ._meson_args [key ].extend (value )
687704
@@ -692,12 +709,12 @@ def __init__(
692709 self ._meson_args [key ].extend (value )
693710
694711 # determine command to invoke meson
695- self ._meson = _get_meson_command (pyproject_config .get ('meson' ))
712+ self ._meson = _get_meson_command (pyproject_config .get ('meson' ), env = self . _env )
696713
697- self ._ninja = _env_ninja_command ()
714+ self ._ninja = _env_ninja_command (env = self . _env )
698715 if self ._ninja is None :
699716 raise ConfigError (f'Could not find ninja version { _NINJA_REQUIRED_VERSION } or newer.' )
700- os . environ .setdefault ('NINJA' , self ._ninja )
717+ self . _env .setdefault ('NINJA' , self ._ninja )
701718
702719 # make sure the build dir exists
703720 self ._build_dir .mkdir (exist_ok = True , parents = True )
@@ -708,7 +725,7 @@ def __init__(
708725
709726 # setuptools-like ARCHFLAGS environment variable support
710727 if sysconfig .get_platform ().startswith ('macosx-' ):
711- archflags = os . environ .get ('ARCHFLAGS' , '' ).strip ()
728+ archflags = self . _env .get ('ARCHFLAGS' , '' ).strip ()
712729 if archflags :
713730
714731 # parse the ARCHFLAGS environment variable
@@ -723,7 +740,7 @@ def __init__(
723740
724741 macver , _ , nativearch = platform .mac_ver ()
725742 if arch != nativearch :
726- x = os . environ .setdefault ('_PYTHON_HOST_PLATFORM' , f'macosx-{ macver } -{ arch } ' )
743+ x = self . _env .setdefault ('_PYTHON_HOST_PLATFORM' , f'macosx-{ macver } -{ arch } ' )
727744 if not x .endswith (arch ):
728745 raise ConfigError (f'$ARCHFLAGS={ archflags !r} and $_PYTHON_HOST_PLATFORM={ x !r} do not agree' )
729746 family = 'aarch64' if arch == 'arm64' else arch
@@ -747,7 +764,7 @@ def __init__(
747764 # Simplify cross-compilation for Android with cibuildwheel: detect the
748765 # cross-compilation environment set up by cibuildwheel and synthesize an
749766 # appropriate cross file.
750- elif sysconfig .get_platform ().startswith ('android-' ) and 'CIBUILDWHEEL' in os . environ :
767+ elif sysconfig .get_platform ().startswith ('android-' ) and 'CIBUILDWHEEL' in self . _env :
751768 cpu = platform .machine ()
752769 cpu_family = 'x86' if cpu == 'i686' else 'arm' if cpu .startswith ('arm' ) else cpu
753770
@@ -893,7 +910,7 @@ def _run(self, cmd: Sequence[str]) -> None:
893910 # command line appears before the command output. Without it,
894911 # the lines appear in the wrong order in pip output.
895912 _log ('{style.INFO}+ {cmd}{style.RESET}' .format (style = style , cmd = ' ' .join (cmd )), flush = True )
896- r = subprocess .run (cmd , cwd = self ._build_dir )
913+ r = subprocess .run (cmd , cwd = self ._build_dir , env = self . _env )
897914 if r .returncode != 0 :
898915 raise SystemExit (r .returncode )
899916
@@ -1135,7 +1152,14 @@ def editable(self, directory: Path) -> pathlib.Path:
11351152 """Generates an editable wheel in the specified directory."""
11361153 self .build ()
11371154 builder = _EditableWheelBuilder (self ._metadata , self ._manifest , self ._limited_api , self ._allow_windows_shared_libs )
1138- return builder .build (directory , self ._source_dir , self ._build_dir , self ._build_command , self ._editable_verbose )
1155+ return builder .build (
1156+ directory ,
1157+ self ._source_dir ,
1158+ self ._build_dir ,
1159+ self ._build_command ,
1160+ self ._config_env ,
1161+ self ._editable_verbose ,
1162+ )
11391163
11401164
11411165@contextlib .contextmanager
@@ -1163,13 +1187,14 @@ def _parse_version_string(string: str) -> Tuple[int, ...]:
11631187
11641188
11651189def _get_meson_command (
1166- meson : Optional [str ] = None , * , version : str = _MESON_REQUIRED_VERSION
1190+ meson : Optional [str ] = None , * , version : str = _MESON_REQUIRED_VERSION , env : Optional [ Dict [ str , str ]] = None
11671191 ) -> List [str ]:
11681192 """Return the command to invoke meson."""
1193+ env = os .environ if env is None else env
11691194
11701195 # The MESON env var, if set, overrides the config value from pyproject.toml.
11711196 # The config value, if given, is an absolute path or the name of an executable.
1172- meson = os . environ .get ('MESON' , meson or 'meson' )
1197+ meson = env .get ('MESON' , meson or 'meson' )
11731198
11741199 # If the specified Meson string ends in `.py`, we run it with the current
11751200 # Python executable. This avoids problems for users on Windows, where
@@ -1188,7 +1213,7 @@ def _get_meson_command(
11881213 # but the corresponding meson command is not available in $PATH. Implement
11891214 # a runtime check to verify that the build environment is setup correcly.
11901215 try :
1191- r = subprocess .run (cmd + ['--version' ], text = True , capture_output = True )
1216+ r = subprocess .run (cmd + ['--version' ], text = True , capture_output = True , env = env )
11921217 except FileNotFoundError as err :
11931218 raise ConfigError (f'meson executable "{ meson } " not found' ) from err
11941219 if r .returncode != 0 :
@@ -1201,15 +1226,16 @@ def _get_meson_command(
12011226 return cmd
12021227
12031228
1204- def _env_ninja_command (* , version : str = _NINJA_REQUIRED_VERSION ) -> Optional [str ]:
1229+ def _env_ninja_command (* , version : str = _NINJA_REQUIRED_VERSION , env : Optional [ Dict [ str , str ]] = None ) -> Optional [str ]:
12051230 """Returns the path to ninja, or None if no ninja found."""
12061231 required_version = _parse_version_string (version )
1207- env_ninja = os .environ .get ('NINJA' )
1232+ env = os .environ if env is None else env
1233+ env_ninja = env .get ('NINJA' )
12081234 ninja_candidates = [env_ninja ] if env_ninja else ['ninja' , 'ninja-build' , 'samu' ]
12091235 for ninja in ninja_candidates :
12101236 ninja_path = shutil .which (ninja )
12111237 if ninja_path is not None :
1212- version = subprocess .run ([ninja_path , '--version' ], check = False , text = True , capture_output = True ).stdout
1238+ version = subprocess .run ([ninja_path , '--version' ], check = False , text = True , capture_output = True , env = env ).stdout
12131239 if _parse_version_string (version ) >= required_version :
12141240 return ninja_path
12151241 return None
0 commit comments