From e7a5c75285ce63a7197cd82e893450eb9bb68b6c Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 11 Dec 2020 12:30:19 -0800 Subject: [PATCH] Move BinaryTable environment lookups to Environment This means that all the env lookups are done once, at initial configure time. This has all of the expected advantages. --- mesonbuild/envconfig.py | 63 +++++++++++++-------------------------- mesonbuild/environment.py | 26 ++++++++++++---- run_unittests.py | 33 ++++++++++---------- 3 files changed, 57 insertions(+), 65 deletions(-) diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py index 642aab3c7..69b667869 100644 --- a/mesonbuild/envconfig.py +++ b/mesonbuild/envconfig.py @@ -23,6 +23,9 @@ from pathlib import Path _T = T.TypeVar('_T') +if T.TYPE_CHECKING: + from .environment import Environment + # These classes contains all the data pulled from configuration files (native # and cross file currently), and also assists with the reading environment @@ -122,10 +125,10 @@ ENV_VAR_PROG_MAP: T.Mapping[str, str] = { # Deprecated environment variables mapped from the new variable to the old one # Deprecated in 0.54.0 DEPRECATED_ENV_PROG_MAP: T.Mapping[str, str] = { - 'DC_LD': 'D_LD', - 'FC_LD': 'F_LD', - 'RUSTC_LD': 'RUST_LD', - 'OBJCXX_LD': 'OBJCPP_LD', + 'd_ld': 'D_LD', + 'fortran_ld': 'F_LD', + 'rust_ld': 'RUST_LD', + 'objcpp_ld': 'OBJCPP_LD', } class CMakeSkipCompilerTest(Enum): @@ -394,17 +397,18 @@ class MachineInfo: return self.is_windows() or self.is_cygwin() class BinaryTable: + def __init__( self, binaries: T.Optional[T.Dict[str, T.Union[str, T.List[str]]]] = None, ): - self.binaries = binaries or {} # type: T.Dict[str, T.Union[str, T.List[str]]] - for name, command in self.binaries.items(): - if not isinstance(command, (list, str)): - # TODO generalize message - raise mesonlib.MesonException( - 'Invalid type {!r} for binary {!r} in cross file' - ''.format(command, name)) + self.binaries: T.Dict[str, T.List[str]] = {} + if binaries: + for name, command in binaries.items(): + if not isinstance(command, (list, str)): + raise mesonlib.MesonException( + f'Invalid type {command!r} for entry {name!r} in cross file') + self.binaries[name] = mesonlib.listify(command) @staticmethod def detect_ccache() -> T.List[str]: @@ -426,42 +430,17 @@ class BinaryTable: # Return value has to be a list of compiler 'choices' return compiler, ccache - def lookup_entry(self, - for_machine: MachineChoice, - is_cross: bool, - name: str) -> T.Optional[T.List[str]]: + def lookup_entry(self, name: str) -> T.Optional[T.List[str]]: """Lookup binary in cross/native file and fallback to environment. Returns command with args as list if found, Returns `None` if nothing is found. """ - # Try explicit map, don't fall back on env var - # Try explict map, then env vars - for _ in [()]: # a trick to get `break` - raw_command = self.binaries.get(name) - if raw_command is not None: - command = mesonlib.stringlistify(raw_command) - break # found - evar = ENV_VAR_PROG_MAP.get(name) - if evar is not None: - raw_command = get_env_var(for_machine, is_cross, evar) - if raw_command is None: - deprecated = DEPRECATED_ENV_PROG_MAP.get(evar) - if deprecated is not None: - raw_command = get_env_var(for_machine, is_cross, deprecated) - if raw_command is not None: - mlog.deprecation( - 'The', deprecated, 'environment variable is deprecated in favor of', - evar, once=True) - if raw_command is not None: - command = split_args(raw_command) - break # found - command = None - - - # Do not return empty or blank string entries - if command is not None and (len(command) == 0 or len(command[0].strip()) == 0): - command = None + command = self.binaries.get(name) + if not command: + return None + elif not command[0].strip(): + return None return command class CMakeVariables: diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 12b9dbb49..6275da05a 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -29,7 +29,7 @@ from .mesonlib import ( from . import mlog from .envconfig import ( - BinaryTable, MachineInfo, + BinaryTable, ENV_VAR_PROG_MAP, MachineInfo, Properties, known_cpu_families, get_env_var_pair, CMakeVariables, ) @@ -129,6 +129,7 @@ from .compilers import ( VisualStudioCCompiler, VisualStudioCPPCompiler, ) +from mesonbuild import envconfig if T.TYPE_CHECKING: from configparser import ConfigParser @@ -654,6 +655,7 @@ class Environment: # Take default value from env if not set in cross/native files or command line. self.set_default_options_from_env() + self._set_default_binaries_from_env() # Warn if the user is using two different ways of setting build-type # options that override each other @@ -804,6 +806,21 @@ class Environment: v = mesonlib.listify(self.options.get(key, [])) self.options.setdefault(key, v + p_list) + def _set_default_binaries_from_env(self) -> None: + """Set default binaries from the environment. + + For example, pkg-config can be set via PKG_CONFIG, or in the machine + file. We want to set the default to the env variable. + """ + opts = itertools.chain(envconfig.DEPRECATED_ENV_PROG_MAP.items(), + envconfig.ENV_VAR_PROG_MAP.items()) + + for (name, evar), for_machine in itertools.product(opts, MachineChoice): + p_env_pair = get_env_var_pair(for_machine, self.is_cross_build(), evar) + if p_env_pair is not None: + _, p_env = p_env_pair + self.binaries[for_machine].binaries.setdefault(name, mesonlib.split_args(p_env)) + def create_new_coredata(self, options: 'argparse.Namespace') -> None: # WARNING: Don't use any values from coredata in __init__. It gets # re-initialized with project options by the interpreter during @@ -853,11 +870,8 @@ class Environment: def is_library(self, fname): return is_library(fname) - def lookup_binary_entry(self, for_machine: MachineChoice, name: str): - return self.binaries[for_machine].lookup_entry( - for_machine, - self.is_cross_build(), - name) + def lookup_binary_entry(self, for_machine: MachineChoice, name: str) -> T.List[str]: + return self.binaries[for_machine].lookup_entry(name) @staticmethod def get_gnu_compiler_defines(compiler): diff --git a/run_unittests.py b/run_unittests.py index cf78e3ef2..bbcdb5fb7 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -2637,17 +2637,16 @@ class AllPlatformTests(BasePlatformTests): # something like `ccache gcc -pipe` or `distcc ccache gcc` works. wrapper = os.path.join(testdir, 'compiler wrapper.py') wrappercc = python_command + [wrapper] + cc.get_exelist() + ['-DSOME_ARG'] - wrappercc_s = '' - for w in wrappercc: - wrappercc_s += quote_arg(w) + ' ' - os.environ[evar] = wrappercc_s - wcc = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST) + os.environ[evar] = ' '.join(quote_arg(w) for w in wrappercc) + # Check static linker too wrapperlinker = python_command + [wrapper] + linker.get_exelist() + linker.get_always_args() - wrapperlinker_s = '' - for w in wrapperlinker: - wrapperlinker_s += quote_arg(w) + ' ' - os.environ['AR'] = wrapperlinker_s + os.environ['AR'] = ' '.join(quote_arg(w) for w in wrapperlinker) + + # Need a new env to re-run environment loading + env = get_fake_env(testdir, self.builddir, self.prefix) + + wcc = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST) wlinker = env.detect_static_linker(wcc) # Pop it so we don't use it for the next detection evalue = os.environ.pop('AR') @@ -5692,9 +5691,9 @@ class WindowsTests(BasePlatformTests): envvars = [mesonbuild.envconfig.ENV_VAR_PROG_MAP['{}_ld'.format(lang)]] # Also test a deprecated variable if there is one. - if envvars[0] in mesonbuild.envconfig.DEPRECATED_ENV_PROG_MAP: + if f'{lang}_ld' in mesonbuild.envconfig.DEPRECATED_ENV_PROG_MAP: envvars.append( - mesonbuild.envconfig.DEPRECATED_ENV_PROG_MAP[envvars[0]]) + mesonbuild.envconfig.DEPRECATED_ENV_PROG_MAP[f'{lang}_ld']) for envvar in envvars: with mock.patch.dict(os.environ, {envvar: name}): @@ -7293,12 +7292,12 @@ class LinuxlikeTests(BasePlatformTests): raise unittest.SkipTest('Solaris currently cannot override the linker.') if not shutil.which(check): raise unittest.SkipTest('Could not find {}.'.format(check)) - envvars = [mesonbuild.envconfig.BinaryTable.ENV_VAR_PROG_MAP['{}_ld'.format(lang)]] + envvars = [mesonbuild.envconfig.ENV_VAR_PROG_MAP['{}_ld'.format(lang)]] # Also test a deprecated variable if there is one. - if envvars[0] in mesonbuild.envconfig.BinaryTable.DEPRECATION_MAP: + if f'{lang}_ld' in mesonbuild.envconfig.DEPRECATED_ENV_PROG_MAP: envvars.append( - mesonbuild.envconfig.BinaryTable.DEPRECATION_MAP[envvars[0]]) + mesonbuild.envconfig.DEPRECATED_ENV_PROG_MAP[f'{lang}_ld']) for envvar in envvars: with mock.patch.dict(os.environ, {envvar: name}): @@ -8274,7 +8273,7 @@ class NativeFileTests(BasePlatformTests): return 'gfortran', 'gcc' self.helper_for_compiler('fortran', cb) - def _single_implementation_compiler(self, lang, binary, version_str, version): + def _single_implementation_compiler(self, lang: str, binary: str, version_str: str, version: str) -> None: """Helper for languages with a single (supported) implementation. Builds a wrapper around the compiler to override the version. @@ -8283,7 +8282,7 @@ class NativeFileTests(BasePlatformTests): env = get_fake_env() getter = getattr(env, 'detect_{}_compiler'.format(lang)) getter = functools.partial(getter, MachineChoice.HOST) - env.binaries.host.binaries[lang] = wrapper + env.binaries.host.binaries[lang] = [wrapper] compiler = getter() self.assertEqual(compiler.version, version) @@ -8310,7 +8309,7 @@ class NativeFileTests(BasePlatformTests): 'swiftc', version='Swift 1.2345', outfile='stderr', extra_args={'Xlinker': 'macosx_version. PROJECT:ld - 1.2.3'}) env = get_fake_env() - env.binaries.host.binaries['swift'] = wrapper + env.binaries.host.binaries['swift'] = [wrapper] compiler = env.detect_swift_compiler(MachineChoice.HOST) self.assertEqual(compiler.version, '1.2345')