diff options
-rw-r--r-- | mesonbuild/coredata.py | 260 | ||||
-rw-r--r-- | mesonbuild/dependencies/base.py | 14 | ||||
-rw-r--r-- | mesonbuild/msetup.py | 8 | ||||
-rw-r--r-- | mesonbuild/optinterpreter.py | 9 | ||||
-rwxr-xr-x | run_cross_test.py | 2 | ||||
-rwxr-xr-x | run_unittests.py | 12 | ||||
-rw-r--r-- | test cases/unit/55 pkg_config_path option/extra_path/totally_made_up_dep.pc | 7 | ||||
-rw-r--r-- | test cases/unit/55 pkg_config_path option/meson.build | 3 |
8 files changed, 176 insertions, 139 deletions
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 01db635..739f6e7 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -26,6 +26,7 @@ from .wrap import WrapMode import ast import argparse import configparser +from typing import Optional, Any, TypeVar, Generic, Type version = '0.50.999' backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'xcode'] @@ -269,7 +270,6 @@ class CoreData: self.cross_compilers = OrderedDict() self.deps = OrderedDict() # Only to print a warning if it changes between Meson invocations. - self.pkgconf_envvar = os.environ.get('PKG_CONFIG_PATH', '') self.config_files = self.__load_config_files(options.native_file) self.libdir_cross_fixup() @@ -335,11 +335,10 @@ class CoreData: def init_builtins(self): # Create builtin options with default values self.builtins = {} - prefix = get_builtin_option_default('prefix') - for key in get_builtin_options(): - value = get_builtin_option_default(key, prefix) - args = [key] + builtin_options[key][1:-1] + [value] - self.builtins[key] = builtin_options[key][0](*args) + for key, opt in builtin_options.items(): + self.builtins[key] = opt.init_option(key) + if opt.separate_cross: + self.builtins['cross_' + key] = opt.init_option(key) def init_backend_options(self, backend_name): if backend_name == 'ninja': @@ -462,7 +461,7 @@ class CoreData: self.builtins['prefix'].set_value(prefix) for key in builtin_dir_noprefix_options: if key not in options: - self.builtins[key].set_value(get_builtin_option_default(key, prefix)) + self.builtins[key].set_value(builtin_options[key].prefixed_default(key, prefix)) unknown_options = [] for k, v in options.items(): @@ -495,7 +494,7 @@ class CoreData: from . import optinterpreter for k, v in default_options.items(): if subproject: - if optinterpreter.is_invalid_name(k): + if optinterpreter.is_invalid_name(k, log=False): continue k = subproject + ':' + k env.cmd_line_options.setdefault(k, v) @@ -506,14 +505,20 @@ class CoreData: # languages and setting the backend (builtin options must be set first # to know which backend we'll use). options = {} + + # Some options default to environment variables if they are + # unset, set those now. These will either be overwritten + # below, or they won't. + options['pkg_config_path'] = os.environ.get('PKG_CONFIG_PATH', '').split(':') + for k, v in env.cmd_line_options.items(): if subproject: if not k.startswith(subproject + ':'): continue - elif k not in get_builtin_options(): + elif k not in builtin_options: if ':' in k: continue - if optinterpreter.is_invalid_name(k): + if optinterpreter.is_invalid_name(k, log=False): continue options[k] = v @@ -656,80 +661,10 @@ def save(obj, build_dir): os.replace(tempfilename, filename) return filename -def get_builtin_options(): - return list(builtin_options.keys()) - -def is_builtin_option(optname): - return optname in get_builtin_options() - -def get_builtin_option_choices(optname): - if is_builtin_option(optname): - if builtin_options[optname][0] == UserComboOption: - return builtin_options[optname][2] - elif builtin_options[optname][0] == UserBooleanOption: - return [True, False] - elif builtin_options[optname][0] == UserFeatureOption: - return UserFeatureOption.static_choices - else: - return None - else: - raise RuntimeError('Tried to get the supported values for an unknown builtin option \'%s\'.' % optname) - -def get_builtin_option_description(optname): - if is_builtin_option(optname): - return builtin_options[optname][1] - else: - raise RuntimeError('Tried to get the description for an unknown builtin option \'%s\'.' % optname) - -def get_builtin_option_action(optname): - default = builtin_options[optname][2] - if default is True: - return 'store_false' - elif default is False: - return 'store_true' - return None - -def get_builtin_option_default(optname, prefix=''): - if is_builtin_option(optname): - o = builtin_options[optname] - if o[0] == UserComboOption: - return o[3] - if o[0] == UserIntegerOption: - return o[4] - try: - return builtin_dir_noprefix_options[optname][prefix] - except KeyError: - pass - return o[2] - else: - raise RuntimeError('Tried to get the default value for an unknown builtin option \'%s\'.' % optname) - -def get_builtin_option_cmdline_name(name): - if name == 'warning_level': - return '--warnlevel' - else: - return '--' + name.replace('_', '-') - -def add_builtin_argument(p, name): - kwargs = {} - c = get_builtin_option_choices(name) - b = get_builtin_option_action(name) - h = get_builtin_option_description(name) - if not b: - h = h.rstrip('.') + ' (default: %s).' % get_builtin_option_default(name) - else: - kwargs['action'] = b - if c and not b: - kwargs['choices'] = c - kwargs['default'] = argparse.SUPPRESS - kwargs['dest'] = name - - cmdline_name = get_builtin_option_cmdline_name(name) - p.add_argument(cmdline_name, help=h, **kwargs) def register_builtin_arguments(parser): - for n in builtin_options: - add_builtin_argument(parser, n) + for n, b in builtin_options.items(): + b.add_to_argparse(n, parser) parser.add_argument('-D', action='append', dest='projectoptions', default=[], metavar="option", help='Set the value of an option, can be used several times to set multiple options.') @@ -747,48 +682,129 @@ def parse_cmd_line_options(args): args.cmd_line_options = create_options_dict(args.projectoptions) # Merge builtin options set with --option into the dict. - for name in builtin_options: - value = getattr(args, name, None) - if value is not None: - if name in args.cmd_line_options: - cmdline_name = get_builtin_option_cmdline_name(name) - raise MesonException( - 'Got argument {0} as both -D{0} and {1}. Pick one.'.format(name, cmdline_name)) - args.cmd_line_options[name] = value - delattr(args, name) + for name, builtin in builtin_options.items(): + names = [name] + if builtin.separate_cross: + names.append('cross_' + name) + for name in names: + value = getattr(args, name, None) + if value is not None: + if name in args.cmd_line_options: + cmdline_name = BuiltinOption.argparse_name_to_arg(name) + raise MesonException( + 'Got argument {0} as both -D{0} and {1}. Pick one.'.format(name, cmdline_name)) + args.cmd_line_options[name] = value + delattr(args, name) + + +_U = TypeVar('_U', bound=UserOption) + +class BuiltinOption(Generic[_U]): + + """Class for a builtin option type. + + Currently doesn't support UserIntegerOption, or a few other cases. + """ + + def __init__(self, opt_type: Type[_U], description: str, default: Any, yielding: Optional[bool] = None, *, + choices: Any = None, separate_cross: bool = False): + self.opt_type = opt_type + self.description = description + self.default = default + self.choices = choices + self.yielding = yielding + self.separate_cross = separate_cross + + def init_option(self, name: str) -> _U: + """Create an instance of opt_type and return it.""" + keywords = {'yielding': self.yielding, 'value': self.default} + if self.choices: + keywords['choices'] = self.choices + return self.opt_type(name, self.description, **keywords) + + def _argparse_action(self) -> Optional[str]: + if self.default is True: + return 'store_false' + elif self.default is False: + return 'store_true' + return None + + def _argparse_choices(self) -> Any: + if self.opt_type is UserBooleanOption: + return [True, False] + elif self.opt_type is UserFeatureOption: + return UserFeatureOption.static_choices + return self.choices + + @staticmethod + def argparse_name_to_arg(name: str) -> str: + if name == 'warning_level': + return '--warnlevel' + else: + return '--' + name.replace('_', '-') + + def prefixed_default(self, name: str, prefix: str = '') -> Any: + if self.opt_type in [UserComboOption, UserIntegerOption]: + return self.default + try: + return builtin_dir_noprefix_options[name][prefix] + except KeyError: + pass + return self.default + + def add_to_argparse(self, name: str, parser: argparse.ArgumentParser) -> None: + kwargs = {} + + c = self._argparse_choices() + b = self._argparse_action() + h = self.description + if not b: + h = '{} (default: {}).'.format(h.rstrip('.'), self.prefixed_default(name)) + else: + kwargs['action'] = b + if c and not b: + kwargs['choices'] = c + kwargs['default'] = argparse.SUPPRESS + kwargs['dest'] = name + + cmdline_name = self.argparse_name_to_arg(name) + parser.add_argument(cmdline_name, help=h, **kwargs) + if self.separate_cross: + kwargs['dest'] = 'cross_' + name + parser.add_argument(self.argparse_name_to_arg('cross_' + name), help=h + ' (for host in cross compiles)', **kwargs) + builtin_options = { - 'buildtype': [UserComboOption, 'Build type to use', ['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom'], 'debug'], - 'strip': [UserBooleanOption, 'Strip targets on install', False], - 'unity': [UserComboOption, 'Unity build', ['on', 'off', 'subprojects'], 'off'], - 'prefix': [UserStringOption, 'Installation prefix', default_prefix()], - 'libdir': [UserStringOption, 'Library directory', default_libdir()], - 'libexecdir': [UserStringOption, 'Library executable directory', default_libexecdir()], - 'bindir': [UserStringOption, 'Executable directory', 'bin'], - 'sbindir': [UserStringOption, 'System executable directory', 'sbin'], - 'includedir': [UserStringOption, 'Header file directory', 'include'], - 'datadir': [UserStringOption, 'Data file directory', 'share'], - 'mandir': [UserStringOption, 'Manual page directory', 'share/man'], - 'infodir': [UserStringOption, 'Info page directory', 'share/info'], - 'localedir': [UserStringOption, 'Locale data directory', 'share/locale'], - 'sysconfdir': [UserStringOption, 'Sysconf data directory', 'etc'], - 'localstatedir': [UserStringOption, 'Localstate data directory', 'var'], - 'sharedstatedir': [UserStringOption, 'Architecture-independent data directory', 'com'], - 'werror': [UserBooleanOption, 'Treat warnings as errors', False], - 'warning_level': [UserComboOption, 'Compiler warning level to use', ['0', '1', '2', '3'], '1'], - 'layout': [UserComboOption, 'Build directory layout', ['mirror', 'flat'], 'mirror'], - 'default_library': [UserComboOption, 'Default library type', ['shared', 'static', 'both'], 'shared'], - 'backend': [UserComboOption, 'Backend to use', backendlist, 'ninja'], - 'stdsplit': [UserBooleanOption, 'Split stdout and stderr in test logs', True], - 'errorlogs': [UserBooleanOption, "Whether to print the logs from failing tests", True], - 'install_umask': [UserUmaskOption, 'Default umask to apply on permissions of installed files', '022'], - 'auto_features': [UserFeatureOption, "Override value of all 'auto' features", 'auto'], - 'optimization': [UserComboOption, 'Optimization level', ['0', 'g', '1', '2', '3', 's'], '0'], - 'debug': [UserBooleanOption, 'Debug', True], - 'wrap_mode': [UserComboOption, 'Wrap mode', ['default', - 'nofallback', - 'nodownload', - 'forcefallback'], 'default'], + 'buildtype': BuiltinOption(UserComboOption, 'Build type to use', 'debug', + choices=['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom']), + 'strip': BuiltinOption(UserBooleanOption, 'Strip targets on install', False), + 'unity': BuiltinOption(UserComboOption, 'Unity build', 'off', choices=['on', 'off', 'subprojects']), + 'prefix': BuiltinOption(UserStringOption, 'Installation prefix', default_prefix()), + 'libdir': BuiltinOption(UserStringOption, 'Library directory', default_libdir()), + 'libexecdir': BuiltinOption(UserStringOption, 'Library executable directory', default_libexecdir()), + 'bindir': BuiltinOption(UserStringOption, 'Executable directory', 'bin'), + 'sbindir': BuiltinOption(UserStringOption, 'System executable directory', 'sbin'), + 'includedir': BuiltinOption(UserStringOption, 'Header file directory', 'include'), + 'datadir': BuiltinOption(UserStringOption, 'Data file directory', 'share'), + 'mandir': BuiltinOption(UserStringOption, 'Manual page directory', 'share/man'), + 'infodir': BuiltinOption(UserStringOption, 'Info page directory', 'share/info'), + 'localedir': BuiltinOption(UserStringOption, 'Locale data directory', 'share/locale'), + 'sysconfdir': BuiltinOption(UserStringOption, 'Sysconf data directory', 'etc'), + 'localstatedir': BuiltinOption(UserStringOption, 'Localstate data directory', 'var'), + 'sharedstatedir': BuiltinOption(UserStringOption, 'Architecture-independent data directory', 'com'), + 'werror': BuiltinOption(UserBooleanOption, 'Treat warnings as errors', False), + 'warning_level': BuiltinOption(UserComboOption, 'Compiler warning level to use', '1', choices=['0', '1', '2', '3']), + 'layout': BuiltinOption(UserComboOption, 'Build directory layout', 'mirror', choices=['mirror', 'flat']), + 'default_library': BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both']), + 'backend': BuiltinOption(UserComboOption, 'Backend to use', 'ninja', choices=backendlist), + 'stdsplit': BuiltinOption(UserBooleanOption, 'Split stdout and stderr in test logs', True), + 'errorlogs': BuiltinOption(UserBooleanOption, "Whether to print the logs from failing tests", True), + 'install_umask': BuiltinOption(UserUmaskOption, 'Default umask to apply on permissions of installed files', '022'), + 'auto_features': BuiltinOption(UserFeatureOption, "Override value of all 'auto' features", 'auto'), + 'optimization': BuiltinOption(UserComboOption, 'Optimization level', '0', choices=['0', 'g', '1', '2', '3', 's']), + 'debug': BuiltinOption(UserBooleanOption, 'Debug', True), + 'wrap_mode': BuiltinOption(UserComboOption, 'Wrap mode', 'default', choices=['default', 'nofallback', 'nodownload', 'forcefallback']), + 'pkg_config_path': BuiltinOption(UserArrayOption, 'List of additional paths for pkg-config to search', [], separate_cross=True), } # Special prefix-dependent defaults for installation directories that reside in diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index ddc56fc..f74eabb 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -617,11 +617,19 @@ class PkgConfigDependency(ExternalDependency): return rc, out def _call_pkgbin(self, args, env=None): + # Always copy the environment since we're going to modify it + # with pkg-config variables if env is None: - fenv = env - env = os.environ + env = os.environ.copy() else: - fenv = frozenset(env.items()) + env = env.copy() + + if self.want_cross: + extra_paths = self.env.coredata.get_builtin_option('cross_pkg_config_path') + else: + extra_paths = self.env.coredata.get_builtin_option('pkg_config_path') + env['PKG_CONFIG_PATH'] = ':'.join([p for p in extra_paths]) + fenv = frozenset(env.items()) targs = tuple(args) cache = PkgConfigDependency.pkgbin_cache if (self.pkgbin, targs, fenv) not in cache: diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py index 6e8ca83..ef0511d 100644 --- a/mesonbuild/msetup.py +++ b/mesonbuild/msetup.py @@ -149,13 +149,6 @@ class MesonApp: sys.exit(1) return src_dir, build_dir - def check_pkgconfig_envvar(self, env): - curvar = os.environ.get('PKG_CONFIG_PATH', '') - if curvar != env.coredata.pkgconf_envvar: - mlog.warning('PKG_CONFIG_PATH has changed between invocations from "%s" to "%s".' % - (env.coredata.pkgconf_envvar, curvar)) - env.coredata.pkgconf_envvar = curvar - def generate(self): env = environment.Environment(self.source_dir, self.build_dir, self.options) mlog.initialize(env.get_log_dir(), self.options.fatal_warnings) @@ -169,7 +162,6 @@ class MesonApp: mlog.debug('Main binary:', sys.executable) mlog.debug('Python system:', platform.system()) mlog.log(mlog.bold('The Meson build system')) - self.check_pkgconfig_envvar(env) mlog.log('Version:', coredata.version) mlog.log('Source dir:', mlog.bold(self.source_dir)) mlog.log('Build dir:', mlog.bold(self.build_dir)) diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 85f6897..e64ed4e 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -20,19 +20,20 @@ from . import coredata from . import mesonlib from . import compilers -forbidden_option_names = coredata.get_builtin_options() +forbidden_option_names = set(coredata.builtin_options.keys()) forbidden_prefixes = [lang + '_' for lang in compilers.all_languages] + ['b_', 'backend_'] reserved_prefixes = ['cross_'] -def is_invalid_name(name): +def is_invalid_name(name: str, *, log: bool = True) -> bool: if name in forbidden_option_names: return True pref = name.split('_')[0] + '_' if pref in forbidden_prefixes: return True if pref in reserved_prefixes: - from . import mlog - mlog.deprecation('Option uses prefix "%s", which is reserved for Meson. This will become an error in the future.' % pref) + if log: + from . import mlog + mlog.deprecation('Option uses prefix "%s", which is reserved for Meson. This will become an error in the future.' % pref) return False class OptionException(mesonlib.MesonException): diff --git a/run_cross_test.py b/run_cross_test.py index b2ef6be..8d18123 100755 --- a/run_cross_test.py +++ b/run_cross_test.py @@ -34,7 +34,7 @@ def runtests(cross_file, failfast): commontests = [('common', gather_tests(Path('test cases', 'common')), False)] try: (passing_tests, failing_tests, skipped_tests) = \ - run_tests(commontests, 'meson-cross-test-run', failfast, ['--cross', cross_file]) + run_tests(commontests, 'meson-cross-test-run', failfast, ['--cross-file', cross_file]) except StopException: pass print('\nTotal passed cross tests:', passing_tests) diff --git a/run_unittests.py b/run_unittests.py index d803e12..19426b8 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -4837,9 +4837,9 @@ endian = 'little' testdir = os.path.join(self.unit_test_dir, '58 pkgconfig relative paths') pkg_dir = os.path.join(testdir, 'pkgconfig') self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'librelativepath.pc'))) - os.environ['PKG_CONFIG_PATH'] = pkg_dir env = get_fake_env(testdir, self.builddir, self.prefix) + env.coredata.set_options({'pkg_config_path': pkg_dir}, '') kwargs = {'required': True, 'silent': True} relative_path_dep = PkgConfigDependency('librelativepath', env, kwargs) self.assertTrue(relative_path_dep.found()) @@ -5068,6 +5068,11 @@ endian = 'little' # Assert that self.assertEqual(len(line.split(lib)), 2, msg=(lib, line)) + @skipIfNoPkgconfig + def test_pkg_config_option(self): + testdir = os.path.join(self.unit_test_dir, '55 pkg_config_path option') + self.init(testdir, extra_args=['-Dpkg_config_path=' + os.path.join(testdir, 'extra_path')]) + def should_run_cross_arm_tests(): return shutil.which('arm-linux-gnueabihf-gcc') and not platform.machine().lower().startswith('arm') @@ -5164,6 +5169,11 @@ class LinuxCrossMingwTests(BasePlatformTests): # Must run in-process or we'll get a generic CalledProcessError self.run_tests(inprocess=True) + @skipIfNoPkgconfig + def test_cross_pkg_config_option(self): + testdir = os.path.join(self.unit_test_dir, '55 pkg_config_path option') + self.init(testdir, extra_args=['-Dcross_pkg_config_path=' + os.path.join(testdir, 'extra_path')]) + class PythonTests(BasePlatformTests): ''' diff --git a/test cases/unit/55 pkg_config_path option/extra_path/totally_made_up_dep.pc b/test cases/unit/55 pkg_config_path option/extra_path/totally_made_up_dep.pc new file mode 100644 index 0000000..6d08687 --- /dev/null +++ b/test cases/unit/55 pkg_config_path option/extra_path/totally_made_up_dep.pc @@ -0,0 +1,7 @@ +prefix=/ +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: totally_made_up_dep +Description: completely and totally made up for a test case +Version: 1.2.3
\ No newline at end of file diff --git a/test cases/unit/55 pkg_config_path option/meson.build b/test cases/unit/55 pkg_config_path option/meson.build new file mode 100644 index 0000000..623c3a2 --- /dev/null +++ b/test cases/unit/55 pkg_config_path option/meson.build @@ -0,0 +1,3 @@ +project('pkg_config_path option') + +dependency('totally_made_up_dep', method : 'pkg-config') |