From 95403cb61520978c52b3693a9bf0119e8348c20b Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 18 Sep 2018 10:28:14 -0700 Subject: [PATCH 01/11] replace ExternalProgram.from_cross_info with from_bin_list This more generic method will also be used to check a config file for binary information. --- mesonbuild/dependencies/base.py | 9 +++++---- mesonbuild/dependencies/ui.py | 2 +- mesonbuild/environment.py | 3 ++- mesonbuild/interpreter.py | 2 +- mesonbuild/modules/windows.py | 4 ++-- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index e67f4c086..a7e298037 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -500,7 +500,8 @@ class PkgConfigDependency(ExternalDependency): if self.required: raise DependencyException('Pkg-config binary missing from cross file') else: - potential_pkgbin = ExternalProgram.from_cross_info(environment.cross_info, 'pkgconfig') + potential_pkgbin = ExternalProgram.from_bin_list( + environment.cross_info.config['binaries'], 'pkgconfig') if potential_pkgbin.found(): self.pkgbin = potential_pkgbin else: @@ -1076,10 +1077,10 @@ class ExternalProgram: return ' '.join(self.command) @staticmethod - def from_cross_info(cross_info, name): - if name not in cross_info.config['binaries']: + def from_bin_list(bins, name): + if name not in bins: return NonExistingExternalProgram() - command = cross_info.config['binaries'][name] + command = bins[name] if not isinstance(command, (list, str)): raise MesonException('Invalid type {!r} for binary {!r} in cross file' ''.format(command, name)) diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index e3b371d3b..ae0567337 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -303,7 +303,7 @@ class QtBaseDependency(ExternalDependency): # Even when cross-compiling, if a cross-info qmake is not specified, we # fallback to using the qmake in PATH because that's what we used to do if self.env.is_cross_build() and 'qmake' in self.env.cross_info.config['binaries']: - return ExternalProgram.from_cross_info(self.env.cross_info, 'qmake') + return ExternalProgram.from_bin_list(self.env.cross_info.config['binaries'], 'qmake') return ExternalProgram(qmake, silent=True) def _qmake_detect(self, mods, kwargs): diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 80917ed0b..073825ae7 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -340,7 +340,8 @@ class Environment: self.cross_info = CrossBuildInfo(self.coredata.cross_file) if 'exe_wrapper' in self.cross_info.config['binaries']: from .dependencies import ExternalProgram - self.exe_wrapper = ExternalProgram.from_cross_info(self.cross_info, 'exe_wrapper') + self.exe_wrapper = ExternalProgram.from_bin_list( + self.cross_info.config['binaries'], 'exe_wrapper') if 'host_machine' in self.cross_info.config: self.machines.host = MachineInfo.from_literal( self.cross_info.config['host_machine']) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index d7a5b6679..73c21fd91 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2757,7 +2757,7 @@ external dependencies (including libraries) must go to "dependencies".''') continue # Always points to a local (i.e. self generated) file. if not isinstance(p, str): raise InterpreterException('Executable name must be a string') - prog = ExternalProgram.from_cross_info(cross_info, p) + prog = ExternalProgram.from_bin_list(bins, p) if prog.found(): return ExternalProgramHolder(prog) return None diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py index 4d0f244ed..85cc3bc69 100644 --- a/mesonbuild/modules/windows.py +++ b/mesonbuild/modules/windows.py @@ -49,8 +49,8 @@ class WindowsModule(ExtensionModule): if state.environment.is_cross_build(): # If cross compiling see if windres has been specified in the # cross file before trying to find it another way. - cross_info = state.environment.cross_info - rescomp = ExternalProgram.from_cross_info(cross_info, 'windres') + bins = state.environment.cross_info.config['binaries'] + rescomp = ExternalProgram.from_bin_list(bins, 'windres') if not rescomp or not rescomp.found(): if 'WINDRES' in os.environ: From 3af4407a16e188bfee0dc91b171decee1b8966eb Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Wed, 2 May 2018 16:01:05 -0700 Subject: [PATCH 02/11] Get basic native config file loading working --- mesonbuild/coredata.py | 76 +++++++++++++++++++++++++++++++++++++++ mesonbuild/environment.py | 6 ++++ mesonbuild/msetup.py | 4 +++ run_tests.py | 1 + 4 files changed, 87 insertions(+) diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 245741a57..ae3757674 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -183,6 +183,7 @@ class UserArrayOption(UserOption): ', '.join(bad), ', '.join(self.choices))) return newvalue + class UserFeatureOption(UserComboOption): static_choices = ['enabled', 'disabled', 'auto'] @@ -198,6 +199,72 @@ class UserFeatureOption(UserComboOption): def is_auto(self): return self.value == 'auto' + +def load_configs(filenames): + """Load native files.""" + def gen(): + for f in filenames: + f = os.path.expanduser(os.path.expandvars(f)) + if os.path.exists(f): + yield f + continue + elif sys.platform != 'win32': + f = os.path.basename(f) + paths = [ + os.environ.get('XDG_DATA_HOME', os.path.expanduser('~/.local/share')), + ] + os.environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':') + for path in paths: + path_to_try = os.path.join(path, 'meson', 'native', f) + if os.path.isfile(path_to_try): + yield path_to_try + break + else: + raise MesonException('Cannot find specified native file: ' + f) + continue + + raise MesonException('Cannot find specified native file: ' + f) + + config = configparser.SafeConfigParser() + config.read(gen()) + return config + + +def _get_section(config, section): + if config.has_section(section): + final = {} + for k, v in config.items(section): + # Windows paths... + v = v.replace('\\', '\\\\') + try: + final[k] = ast.literal_eval(v) + except SyntaxError: + raise MesonException( + 'Malformed value in native file variable: {}'.format(v)) + return final + return {} + + +class ConfigData: + + """Contains configuration information provided by the user for the build.""" + + def __init__(self, config=None): + if config: + self.binaries = _get_section(config, 'binaries') + # global is a keyword and globals is a builtin, rather than mangle it, + # use a similar word + self.universal = _get_section(config, 'globals') + self.subprojects = {s: _get_section(config, s) for s in config.sections() + if s not in {'binaries', 'globals'}} + else: + self.binaries = {} + self.universal = {} + self.subprojects = {} + + def get_binaries(self, name): + return self.binaries.get(name, None) + + # This class contains all data that must persist over multiple # invocations of Meson. It is roughly the same thing as # cmakecache. @@ -229,6 +296,15 @@ class CoreData: 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) + + @staticmethod + def __load_config_files(filenames): + if not filenames: + return [] + filenames = [os.path.abspath(os.path.expanduser(os.path.expanduser(f))) + for f in filenames] + return filenames @staticmethod def __load_cross_file(filename): diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 073825ae7..72ca6a2ba 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -352,6 +352,12 @@ class Environment: self.cross_info = None self.machines.default_missing() + if self.coredata.config_files: + self.config_info = coredata.ConfigData( + coredata.load_configs(self.coredata.config_files)) + else: + self.config_info = coredata.ConfigData() + self.cmd_line_options = options.cmd_line_options.copy() # List of potential compilers. diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py index ce03f43c6..f9a5e1c88 100644 --- a/mesonbuild/msetup.py +++ b/mesonbuild/msetup.py @@ -29,6 +29,10 @@ def add_arguments(parser): coredata.register_builtin_arguments(parser) parser.add_argument('--cross-file', default=None, help='File describing cross compilation environment.') + parser.add_argument('--native-file', + default=[], + action='append', + help='File containing overrides for native compilation environment.') parser.add_argument('-v', '--version', action='version', version=coredata.version) parser.add_argument('--profile-self', action='store_true', dest='profile', diff --git a/run_tests.py b/run_tests.py index 3445e30ca..ebee60292 100755 --- a/run_tests.py +++ b/run_tests.py @@ -73,6 +73,7 @@ def get_fake_options(prefix): opts.wrap_mode = None opts.prefix = prefix opts.cmd_line_options = {} + opts.native_file = [] return opts def get_fake_env(sdir, bdir, prefix): From cb2a66cdef3776d917031ccecb7f96340eb3a42b Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Thu, 20 Sep 2018 09:13:25 -0700 Subject: [PATCH 03/11] unittests: Add framework for testing native config files --- run_unittests.py | 80 ++++++++++++++++++- .../unit/46 native file binary/meson.build | 3 + .../46 native file binary/meson_options.txt | 5 ++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 test cases/unit/46 native file binary/meson.build create mode 100644 test cases/unit/46 native file binary/meson_options.txt diff --git a/run_unittests.py b/run_unittests.py index d3545db75..007a05bcc 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -4446,6 +4446,83 @@ class RewriterTests(unittest.TestCase): self.assertEqual(s2, self.read_contents('sub2/meson.build')) +class NativeFileTests(BasePlatformTests): + + def setUp(self): + super().setUp() + self.testcase = os.path.join(self.unit_test_dir, '46 native file binary') + self.current_config = 0 + self.current_wrapper = 0 + + def helper_create_native_file(self, values): + """Create a config file as a temporary file. + + values should be a nested dictionary structure of {section: {key: + value}} + """ + filename = os.path.join(self.builddir, 'generated{}.config'.format(self.current_config)) + self.current_config += 1 + with open(filename, 'wt') as f: + for section, entries in values.items(): + f.write('[{}]\n'.format(section)) + for k, v in entries.items(): + f.write("{}='{}'\n".format(k, v)) + return filename + + def helper_create_binary_wrapper(self, binary, **kwargs): + """Creates a wrapper around a binary that overrides specific values.""" + filename = os.path.join(self.builddir, 'binary_wrapper{}.py'.format(self.current_wrapper)) + self.current_wrapper += 1 + if is_haiku(): + chbang = '#!/bin/env python3' + else: + chbang = '#!/usr/bin/env python3' + + with open(filename, 'wt') as f: + f.write(textwrap.dedent('''\ + {} + #!/usr/bin/env python3 + import argparse + import subprocess + import sys + + def main(): + parser = argparse.ArgumentParser() + '''.format(chbang))) + for name in kwargs: + f.write(' parser.add_argument("--{}", action="store_true")\n'.format(name)) + f.write(' args, extra_args = parser.parse_known_args()\n') + for name, value in kwargs.items(): + f.write(' if args.{}:\n'.format(name)) + f.write(' print({})\n'.format(value)) + f.write(' sys.exit(0)\n') + f.write(textwrap.dedent(''' + ret = subprocess.run( + ["{}"] + extra_args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding='utf-8') + print(ret.stdout) + print(ret.stderr, file=sys.stderr) + sys.exit(ret.returncode) + + if __name__ == '__main__': + main() + '''.format(binary))) + + if not is_windows(): + os.chmod(filename, 0o755) + return filename + + # On windows we need yet another level of indirection, as cmd cannot + # invoke python files itself, so instead we generate a .bat file, which + # invokes our python wrapper + batfile = os.path.join(self.builddir, 'binary_wrapper{}.bat'.format(self.current_wrapper)) + with open(batfile, 'wt') as f: + f.write('py -3 {} %*'.format(filename)) + return batfile + + def unset_envs(): # For unit tests we must fully control all command lines # so that there are no unexpected changes coming from the @@ -4463,7 +4540,8 @@ def should_run_cross_mingw_tests(): def main(): unset_envs() - cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests', 'PythonTests'] + cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests', + 'PythonTests', 'NativeFileTests'] if not is_windows(): cases += ['LinuxlikeTests'] if should_run_cross_arm_tests(): diff --git a/test cases/unit/46 native file binary/meson.build b/test cases/unit/46 native file binary/meson.build new file mode 100644 index 000000000..2e458b0bb --- /dev/null +++ b/test cases/unit/46 native file binary/meson.build @@ -0,0 +1,3 @@ +project('test project') + +case = get_option('case') diff --git a/test cases/unit/46 native file binary/meson_options.txt b/test cases/unit/46 native file binary/meson_options.txt new file mode 100644 index 000000000..df535db86 --- /dev/null +++ b/test cases/unit/46 native file binary/meson_options.txt @@ -0,0 +1,5 @@ +option( + 'case', + type : 'combo', + choices : [] +) From 42ddc30cfef6aebf4f8b595be4ac0dfa6f4da098 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Wed, 2 May 2018 16:33:08 -0700 Subject: [PATCH 04/11] interpreter: allow find_program to use the native file --- mesonbuild/interpreter.py | 19 +++++++++++--- run_unittests.py | 26 +++++++++++++++++++ .../unit/46 native file binary/meson.build | 6 +++++ .../46 native file binary/meson_options.txt | 2 +- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 73c21fd91..e820afb71 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2748,8 +2748,7 @@ external dependencies (including libraries) must go to "dependencies".''') self.coredata. base_options[optname] = oobj self.emit_base_options_warnings(enabled_opts) - def program_from_cross_file(self, prognames, silent=False): - cross_info = self.environment.cross_info + def _program_from_file(self, prognames, bins, silent): for p in prognames: if hasattr(p, 'held_object'): p = p.held_object @@ -2762,6 +2761,14 @@ external dependencies (including libraries) must go to "dependencies".''') return ExternalProgramHolder(prog) return None + def program_from_cross_file(self, prognames, silent=False): + bins = self.environment.cross_info.config['binaries'] + return self._program_from_file(prognames, bins, silent) + + def program_from_config_file(self, prognames, silent=False): + bins = self.environment.config_info.binaries + return self._program_from_file(prognames, bins, silent) + def program_from_system(self, args, silent=False): # Search for scripts relative to current subdir. # Do not cache found programs because find_program('foobar') @@ -2816,10 +2823,14 @@ external dependencies (including libraries) must go to "dependencies".''') def find_program_impl(self, args, native=False, required=True, silent=True): if not isinstance(args, list): args = [args] + progobj = self.program_from_overrides(args, silent=silent) - if progobj is None and self.build.environment.is_cross_build(): - if not native: + if progobj is None: + if self.build.environment.is_cross_build() and not native: progobj = self.program_from_cross_file(args, silent=silent) + else: + progobj = self.program_from_config_file(args, silent=silent) + if progobj is None: progobj = self.program_from_system(args, silent=silent) if required and (progobj is None or not progobj.found()): diff --git a/run_unittests.py b/run_unittests.py index 007a05bcc..b37562995 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -4522,6 +4522,32 @@ class NativeFileTests(BasePlatformTests): f.write('py -3 {} %*'.format(filename)) return batfile + def test_multiple_native_files_override(self): + wrapper = self.helper_create_binary_wrapper('bash', version='foo') + config = self.helper_create_native_file({'binaries': {'bash': wrapper}}) + wrapper = self.helper_create_binary_wrapper('bash', version='12345') + config2 = self.helper_create_native_file({'binaries': {'bash': wrapper}}) + self.init(self.testcase, extra_args=[ + '--native-file', config, '--native-file', config2, + '-Dcase=find_program']) + + def test_multiple_native_files(self): + wrapper = self.helper_create_binary_wrapper('bash', version='12345') + config = self.helper_create_native_file({'binaries': {'bash': wrapper}}) + wrapper = self.helper_create_binary_wrapper('python') + config2 = self.helper_create_native_file({'binaries': {'python': wrapper}}) + self.init(self.testcase, extra_args=[ + '--native-file', config, '--native-file', config2, + '-Dcase=find_program']) + + def _simple_test(self, case, binary): + wrapper = self.helper_create_binary_wrapper(binary, version='12345') + config = self.helper_create_native_file({'binaries': {binary: wrapper}}) + self.init(self.testcase, extra_args=['--native-file', config, '-Dcase={}'.format(case)]) + + def test_find_program(self): + self._simple_test('find_program', 'bash') + def unset_envs(): # For unit tests we must fully control all command lines diff --git a/test cases/unit/46 native file binary/meson.build b/test cases/unit/46 native file binary/meson.build index 2e458b0bb..3c99929fe 100644 --- a/test cases/unit/46 native file binary/meson.build +++ b/test cases/unit/46 native file binary/meson.build @@ -1,3 +1,9 @@ project('test project') case = get_option('case') + +if case == 'find_program' + prog = find_program('bash') + result = run_command(prog, ['--version']) + assert(result.stdout().strip().endswith('12345'), 'Didn\'t load bash from config file') +endif diff --git a/test cases/unit/46 native file binary/meson_options.txt b/test cases/unit/46 native file binary/meson_options.txt index df535db86..83d34e84e 100644 --- a/test cases/unit/46 native file binary/meson_options.txt +++ b/test cases/unit/46 native file binary/meson_options.txt @@ -1,5 +1,5 @@ option( 'case', type : 'combo', - choices : [] + choices : ['find_program'] ) From caf1066cd198ecf66cc5a4e2958627e5a4a5c26b Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Wed, 2 May 2018 16:22:08 -0700 Subject: [PATCH 05/11] dependencies: Allow config tool dependencies to use native files This allows tools like llvm-config to be selected from the config file Fixes: #3327 --- mesonbuild/dependencies/base.py | 2 ++ run_unittests.py | 6 ++++++ test cases/unit/46 native file binary/meson.build | 4 ++++ test cases/unit/46 native file binary/meson_options.txt | 2 +- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index a7e298037..3b4edc237 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -399,6 +399,8 @@ class ConfigToolDependency(ExternalDependency): 'Falling back to searching PATH. This may find a ' 'native version of {0}!'.format(self.tool_name)) tools = self.tools + elif self.tool_name in self.env.config_info.binaries: + tools = [self.env.config_info.binaries[self.tool_name]] else: tools = self.tools diff --git a/run_unittests.py b/run_unittests.py index b37562995..cd1bf6212 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -4548,6 +4548,12 @@ class NativeFileTests(BasePlatformTests): def test_find_program(self): self._simple_test('find_program', 'bash') + def test_config_tool_dep(self): + # Do the skip at this level to avoid screwing up the cache + if not shutil.which('llvm-config'): + raise unittest.SkipTest('No llvm-installed, cannot test') + self._simple_test('config_dep', 'llvm-config') + def unset_envs(): # For unit tests we must fully control all command lines diff --git a/test cases/unit/46 native file binary/meson.build b/test cases/unit/46 native file binary/meson.build index 3c99929fe..3754dfafd 100644 --- a/test cases/unit/46 native file binary/meson.build +++ b/test cases/unit/46 native file binary/meson.build @@ -6,4 +6,8 @@ if case == 'find_program' prog = find_program('bash') result = run_command(prog, ['--version']) assert(result.stdout().strip().endswith('12345'), 'Didn\'t load bash from config file') +elif case == 'config_dep' + add_languages('cpp') + dep = dependency('llvm') + assert(dep.get_configtool_variable('version').endswith('12345'), 'Didn\'t load llvm from config file') endif diff --git a/test cases/unit/46 native file binary/meson_options.txt b/test cases/unit/46 native file binary/meson_options.txt index 83d34e84e..5663ab353 100644 --- a/test cases/unit/46 native file binary/meson_options.txt +++ b/test cases/unit/46 native file binary/meson_options.txt @@ -1,5 +1,5 @@ option( 'case', type : 'combo', - choices : ['find_program'] + choices : ['find_program', 'config_dep'] ) From f159735c3b82849d0c5d33345b7b07a5f5024428 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 11 May 2018 09:07:34 -0700 Subject: [PATCH 06/11] modules/windows: Allow getting windres from native file --- mesonbuild/modules/windows.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py index 85cc3bc69..96f3a7ea0 100644 --- a/mesonbuild/modules/windows.py +++ b/mesonbuild/modules/windows.py @@ -58,6 +58,14 @@ class WindowsModule(ExtensionModule): # specifying an arch-specific windres. rescomp = ExternalProgram('windres', command=os.environ.get('WINDRES'), silent=True) + if not rescomp or not rescomp.found(): + # Take windres from the config file after the environment, which is + # in keeping with the expectations on unix-like OSes that + # environment variables trump config files. + _win = state.environment.config_info.binaries.get('windres') + if _win: + rescomp = ExternalProgram('windres', command=_win, silent=True) + if not rescomp or not rescomp.found(): comp = self.detect_compiler(state.compilers) if comp.id == 'msvc' or comp.id == 'clang-cl': From 091452f8cd886dedc2bc5d3ec292dcf899989a24 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 11 May 2018 09:14:18 -0700 Subject: [PATCH 07/11] modules/python3: allow specifying in the native file --- mesonbuild/modules/python3.py | 5 ++++- mesonbuild/modules/windows.py | 5 ++--- run_unittests.py | 3 +++ test cases/unit/46 native file binary/meson.build | 4 ++++ test cases/unit/46 native file binary/meson_options.txt | 2 +- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/mesonbuild/modules/python3.py b/mesonbuild/modules/python3.py index 5bda5ab08..f6646327a 100644 --- a/mesonbuild/modules/python3.py +++ b/mesonbuild/modules/python3.py @@ -48,7 +48,10 @@ class Python3Module(ExtensionModule): @noKwargs def find_python(self, state, args, kwargs): - py3 = dependencies.ExternalProgram('python3', mesonlib.python_command, silent=True) + options = [state.environment.config_info.binaries.get('python3')] + if not options[0]: # because this would be [None] + options = ['python3', mesonlib.python_command] + py3 = dependencies.ExternalProgram(*options, silent=True) return ModuleReturnValue(py3, [py3]) @noKwargs diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py index 96f3a7ea0..d185d8913 100644 --- a/mesonbuild/modules/windows.py +++ b/mesonbuild/modules/windows.py @@ -62,9 +62,8 @@ class WindowsModule(ExtensionModule): # Take windres from the config file after the environment, which is # in keeping with the expectations on unix-like OSes that # environment variables trump config files. - _win = state.environment.config_info.binaries.get('windres') - if _win: - rescomp = ExternalProgram('windres', command=_win, silent=True) + bins = state.environment.config_info.binaries + rescomp = ExternalProgram.from_bin_list(bins, 'windres') if not rescomp or not rescomp.found(): comp = self.detect_compiler(state.compilers) diff --git a/run_unittests.py b/run_unittests.py index cd1bf6212..fc4e82b8b 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -4554,6 +4554,9 @@ class NativeFileTests(BasePlatformTests): raise unittest.SkipTest('No llvm-installed, cannot test') self._simple_test('config_dep', 'llvm-config') + def test_python3_module(self): + self._simple_test('python3', 'python3') + def unset_envs(): # For unit tests we must fully control all command lines diff --git a/test cases/unit/46 native file binary/meson.build b/test cases/unit/46 native file binary/meson.build index 3754dfafd..76d69835a 100644 --- a/test cases/unit/46 native file binary/meson.build +++ b/test cases/unit/46 native file binary/meson.build @@ -10,4 +10,8 @@ elif case == 'config_dep' add_languages('cpp') dep = dependency('llvm') assert(dep.get_configtool_variable('version').endswith('12345'), 'Didn\'t load llvm from config file') +elif case == 'python3' + prog = import('python3').find_python() + result = run_command(prog, ['--version']) + assert(result.stdout().strip().endswith('12345'), 'Didn\'t load python3 from config file') endif diff --git a/test cases/unit/46 native file binary/meson_options.txt b/test cases/unit/46 native file binary/meson_options.txt index 5663ab353..ef7fe0228 100644 --- a/test cases/unit/46 native file binary/meson_options.txt +++ b/test cases/unit/46 native file binary/meson_options.txt @@ -1,5 +1,5 @@ option( 'case', type : 'combo', - choices : ['find_program', 'config_dep'] + choices : ['find_program', 'config_dep', 'python3'] ) From 71a5f990d09f04d8eb8d636abf7e2b446fdf826a Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 18 Sep 2018 15:17:31 -0700 Subject: [PATCH 08/11] modules/python: Allow use of the native file Currently this just allows setting a "python" variable that always overrides, probably we want to have the option for "python2" and "python3" as well. --- mesonbuild/modules/python.py | 4 +++- run_unittests.py | 8 ++++++++ test cases/unit/46 native file binary/meson.build | 4 ++++ test cases/unit/46 native file binary/meson_options.txt | 2 +- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 954220bde..3b2bf077e 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -480,7 +480,9 @@ class PythonModule(ExtensionModule): if len(args) > 1: raise InvalidArguments('find_installation takes zero or one positional argument.') - if args: + if 'python' in state.environment.config_info.binaries: + name_or_path = state.environment.config_info.binaries['python'] + elif args: name_or_path = args[0] if not isinstance(name_or_path, str): raise InvalidArguments('find_installation argument must be a string.') diff --git a/run_unittests.py b/run_unittests.py index fc4e82b8b..05a84579b 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -4557,6 +4557,14 @@ class NativeFileTests(BasePlatformTests): def test_python3_module(self): self._simple_test('python3', 'python3') + def test_python_module(self): + if is_windows(): + # Bat adds extra crap to stdout, so the version check logic in the + # python module breaks. This is fine on other OSes because they + # don't need the extra indirection. + raise unittest.SkipTest('bat indirection breaks internal sanity checks.') + self._simple_test('python', 'python') + def unset_envs(): # For unit tests we must fully control all command lines diff --git a/test cases/unit/46 native file binary/meson.build b/test cases/unit/46 native file binary/meson.build index 76d69835a..4489ac1e6 100644 --- a/test cases/unit/46 native file binary/meson.build +++ b/test cases/unit/46 native file binary/meson.build @@ -14,4 +14,8 @@ elif case == 'python3' prog = import('python3').find_python() result = run_command(prog, ['--version']) assert(result.stdout().strip().endswith('12345'), 'Didn\'t load python3 from config file') +elif case == 'python' + prog = import('python').find_installation() + result = run_command(prog, ['--version']) + assert(result.stdout().strip().endswith('12345'), 'Didn\'t load python from config file') endif diff --git a/test cases/unit/46 native file binary/meson_options.txt b/test cases/unit/46 native file binary/meson_options.txt index ef7fe0228..651da0ebe 100644 --- a/test cases/unit/46 native file binary/meson_options.txt +++ b/test cases/unit/46 native file binary/meson_options.txt @@ -1,5 +1,5 @@ option( 'case', type : 'combo', - choices : ['find_program', 'config_dep', 'python3'] + choices : ['find_program', 'config_dep', 'python3', 'python'] ) From aa04147a4a2a37aee197ebc0ed179c6fb582b3e9 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Wed, 24 Oct 2018 14:17:44 -0700 Subject: [PATCH 09/11] dependencies/ui: Allow qt compilers and qmake to be specified in native file --- mesonbuild/dependencies/base.py | 4 +- mesonbuild/dependencies/ui.py | 66 ++++++++++++++++++++++++--------- mesonbuild/modules/qt.py | 9 +++-- mesonbuild/modules/qt4.py | 7 ++-- mesonbuild/modules/qt5.py | 7 ++-- 5 files changed, 62 insertions(+), 31 deletions(-) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 3b4edc237..b1d79bbcb 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -1241,8 +1241,8 @@ class ExternalProgram: class NonExistingExternalProgram(ExternalProgram): "A program that will never exist" - def __init__(self): - self.name = 'nonexistingprogram' + def __init__(self, name='nonexistingprogram'): + self.name = name self.command = [None] self.path = None diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index ae0567337..b58988991 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -30,7 +30,7 @@ from ..mesonlib import ( from ..environment import detect_cpu from .base import DependencyException, DependencyMethods -from .base import ExternalDependency, ExternalProgram +from .base import ExternalDependency, ExternalProgram, NonExistingExternalProgram from .base import ExtraFrameworkDependency, PkgConfigDependency from .base import ConfigToolDependency @@ -230,21 +230,46 @@ class QtBaseDependency(ExternalDependency): self.from_text = mlog.format_list(methods) self.version = None - def compilers_detect(self): + def compilers_detect(self, interp_obj): "Detect Qt (4 or 5) moc, uic, rcc in the specified bindir or in PATH" - if self.bindir or for_windows(self.env.is_cross_build(), self.env): - moc = ExternalProgram(os.path.join(self.bindir, 'moc'), silent=True) - uic = ExternalProgram(os.path.join(self.bindir, 'uic'), silent=True) - rcc = ExternalProgram(os.path.join(self.bindir, 'rcc'), silent=True) - lrelease = ExternalProgram(os.path.join(self.bindir, 'lrelease'), silent=True) - else: - # We don't accept unsuffixed 'moc', 'uic', and 'rcc' because they - # are sometimes older, or newer versions. - moc = ExternalProgram('moc-' + self.name, silent=True) - uic = ExternalProgram('uic-' + self.name, silent=True) - rcc = ExternalProgram('rcc-' + self.name, silent=True) - lrelease = ExternalProgram('lrelease-' + self.name, silent=True) - return moc, uic, rcc, lrelease + # It is important that this list does not change order as the order of + # the returned ExternalPrograms will change as well + bins = ['moc', 'uic', 'rcc', 'lrelease'] + found = {b: NonExistingExternalProgram(name='{}-{}'.format(b, self.name)) + for b in bins} + + def gen_bins(): + for b in bins: + yield '{}-{}'.format(b, self.name), b, False + yield b, b, self.required + + for b, name, required in gen_bins(): + if found[name].found(): + continue + + # prefer the -qt of the tool to the plain one, as we + # don't know what the unsuffixed one points to without calling it. + p = interp_obj.find_program_impl([b], silent=True, required=required).held_object + if not p.found(): + continue + + if b.startswith('lrelease'): + arg = ['-version'] + elif mesonlib.version_compare(self.version, '>= 5'): + arg = ['--version'] + else: + arg = ['-v'] + + # Ensure that the version of qt and each tool are the same + _, out, err = mesonlib.Popen_safe(p.get_command() + arg) + if b.startswith('lrelease') or not self.version.startswith('4'): + care = out + else: + care = err + if mesonlib.version_compare(self.version, '== {}'.format(care.split(' ')[-1])): + found[name] = p + + return tuple([found[b] for b in bins]) def _pkgconfig_detect(self, mods, kwargs): # We set the value of required to False so that we can try the @@ -302,8 +327,15 @@ class QtBaseDependency(ExternalDependency): def _find_qmake(self, qmake): # Even when cross-compiling, if a cross-info qmake is not specified, we # fallback to using the qmake in PATH because that's what we used to do - if self.env.is_cross_build() and 'qmake' in self.env.cross_info.config['binaries']: - return ExternalProgram.from_bin_list(self.env.cross_info.config['binaries'], 'qmake') + if self.env.is_cross_build(): + if 'qmake' in self.env.cross_info.config['binaries']: + return ExternalProgram.from_bin_list(self.env.cross_info.config['binaries'], 'qmake') + elif self.env.config_info: + # Prefer suffixed to unsuffixed version + p = ExternalProgram.from_bin_list(self.env.config_info.binaries, 'qmake-' + self.name) + if p.found(): + return p + return ExternalProgram.from_bin_list(self.env.config_info.binaries, 'qmake') return ExternalProgram(qmake, silent=True) def _qmake_detect(self, mods, kwargs): diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py index 7a2c3380a..367b15bff 100644 --- a/mesonbuild/modules/qt.py +++ b/mesonbuild/modules/qt.py @@ -18,7 +18,7 @@ from .. import build from ..mesonlib import MesonException, Popen_safe, extract_as_list, File from ..dependencies import Dependency, Qt4Dependency, Qt5Dependency import xml.etree.ElementTree as ET -from . import ModuleReturnValue, get_include_args +from . import ModuleReturnValue, get_include_args, ExtensionModule from ..interpreterbase import permittedKwargs, FeatureNewKwargs _QT_DEPS_LUT = { @@ -27,10 +27,11 @@ _QT_DEPS_LUT = { } -class QtBaseModule: +class QtBaseModule(ExtensionModule): tools_detected = False - def __init__(self, qt_version=5): + def __init__(self, interpreter, qt_version=5): + ExtensionModule.__init__(self, interpreter) self.qt_version = qt_version def _detect_tools(self, env, method): @@ -43,7 +44,7 @@ class QtBaseModule: kwargs = {'required': 'true', 'modules': 'Core', 'silent': 'true', 'method': method} qt = _QT_DEPS_LUT[self.qt_version](env, kwargs) # Get all tools and then make sure that they are the right version - self.moc, self.uic, self.rcc, self.lrelease = qt.compilers_detect() + self.moc, self.uic, self.rcc, self.lrelease = qt.compilers_detect(self.interpreter) # Moc, uic and rcc write their version strings to stderr. # Moc and rcc return a non-zero result when doing so. # What kind of an idiot thought that was a good idea? diff --git a/mesonbuild/modules/qt4.py b/mesonbuild/modules/qt4.py index 29992d52a..112e3e4fe 100644 --- a/mesonbuild/modules/qt4.py +++ b/mesonbuild/modules/qt4.py @@ -14,14 +14,13 @@ from .. import mlog from .qt import QtBaseModule -from . import ExtensionModule -class Qt4Module(ExtensionModule, QtBaseModule): +class Qt4Module(QtBaseModule): def __init__(self, interpreter): - QtBaseModule.__init__(self, qt_version=4) - ExtensionModule.__init__(self, interpreter) + QtBaseModule.__init__(self, interpreter, qt_version=4) + def initialize(*args, **kwargs): mlog.warning('rcc dependencies will not work properly until this upstream issue is fixed:', diff --git a/mesonbuild/modules/qt5.py b/mesonbuild/modules/qt5.py index 19623ac1f..96a79649b 100644 --- a/mesonbuild/modules/qt5.py +++ b/mesonbuild/modules/qt5.py @@ -14,14 +14,13 @@ from .. import mlog from .qt import QtBaseModule -from . import ExtensionModule -class Qt5Module(ExtensionModule, QtBaseModule): +class Qt5Module(QtBaseModule): def __init__(self, interpreter): - QtBaseModule.__init__(self, qt_version=5) - ExtensionModule.__init__(self, interpreter) + QtBaseModule.__init__(self, interpreter, qt_version=5) + def initialize(*args, **kwargs): mlog.warning('rcc dependencies will not work reliably until this upstream issue is fixed:', From e338e9ad2f08c52ec96541704e8f798d5a6e2742 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 5 Nov 2018 09:59:01 -0800 Subject: [PATCH 10/11] environment: detect compilers from native files --- mesonbuild/environment.py | 26 ++++-- run_unittests.py | 185 +++++++++++++++++++++++++++++++++++++- 2 files changed, 201 insertions(+), 10 deletions(-) diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 72ca6a2ba..01a7c5196 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -512,7 +512,10 @@ class Environment: The list of compilers is detected in the exact same way for C, C++, ObjC, ObjC++, Fortran, CS so consolidate it here. ''' + is_cross = False + exe_wrap = None evar = BinaryTable.evarMap[lang] + if self.is_cross_build() and want_cross: if lang not in self.cross_info.config['binaries']: raise EnvironmentException('{!r} compiler binary not defined in cross file'.format(lang)) @@ -528,13 +531,13 @@ class Environment: shlex.split(os.environ[evar])) # Return value has to be a list of compiler 'choices' compilers = [compilers] - is_cross = False - exe_wrap = None + elif lang in self.config_info.binaries: + compilers, ccache = BinaryTable.parse_entry( + mesonlib.stringlistify(self.config_info.binaries[lang])) + compilers = [compilers] else: compilers = getattr(self, 'default_' + lang) ccache = BinaryTable.detect_ccache() - is_cross = False - exe_wrap = None return compilers, ccache, is_cross, exe_wrap def _handle_exceptions(self, exceptions, binaries, bintype='compiler'): @@ -805,7 +808,11 @@ class Environment: self._handle_exceptions(popen_exceptions, compilers) def detect_java_compiler(self): - exelist = ['javac'] + if 'java' in self.config_info.binaries: + exelist = mesonlib.stringlistify(self.config_info.binaries['java']) + else: + exelist = ['javac'] + try: p, out, err = Popen_safe(exelist + ['-version']) except OSError: @@ -838,6 +845,8 @@ class Environment: def detect_vala_compiler(self): if 'VALAC' in os.environ: exelist = shlex.split(os.environ['VALAC']) + elif 'vala' in self.config_info.binaries: + exelist = mesonlib.stringlistify(self.config_info.binaries['vala']) else: exelist = ['valac'] try: @@ -882,6 +891,8 @@ class Environment: elif self.is_cross_build() and want_cross: exelist = mesonlib.stringlistify(self.cross_info.config['binaries']['d']) is_cross = True + elif 'd' in self.config_info.binaries: + exelist = mesonlib.stringlistify(self.config_info.binaries['d']) elif shutil.which("ldc2"): exelist = ['ldc2'] elif shutil.which("ldc"): @@ -919,7 +930,10 @@ class Environment: raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') def detect_swift_compiler(self): - exelist = ['swiftc'] + if 'swift' in self.config_info.binaries: + exelist = mesonlib.stringlistify(self.config_info.binaries['swift']) + else: + exelist = ['swiftc'] try: p, _, err = Popen_safe(exelist + ['-v']) except OSError: diff --git a/run_unittests.py b/run_unittests.py index 05a84579b..bc1173288 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -26,6 +26,7 @@ import sys import unittest import platform import pickle +import functools from itertools import chain from unittest import mock from configparser import ConfigParser @@ -41,7 +42,7 @@ import mesonbuild.modules.gnome from mesonbuild.interpreter import Interpreter, ObjectHolder from mesonbuild.mesonlib import ( is_windows, is_osx, is_cygwin, is_dragonflybsd, is_openbsd, is_haiku, - windows_proof_rmtree, python_command, version_compare, + is_linux, windows_proof_rmtree, python_command, version_compare, BuildDirLock, Version ) from mesonbuild.environment import detect_ninja @@ -108,12 +109,41 @@ def skipIfNoPkgconfig(f): Note: Yes, we provide pkg-config even while running Windows CI ''' + @functools.wraps(f) def wrapped(*args, **kwargs): if not is_ci() and shutil.which('pkg-config') is None: raise unittest.SkipTest('pkg-config not found') return f(*args, **kwargs) return wrapped +def skip_if_not_language(lang): + def wrapper(func): + @functools.wraps(func) + def wrapped(*args, **kwargs): + try: + env = get_fake_env('', '', '') + f = getattr(env, 'detect_{}_compiler'.format(lang)) + if lang in ['cs', 'vala', 'java', 'swift']: + f() + else: + f(False) + except EnvironmentException: + raise unittest.SkipTest('No {} compiler found.'.format(lang)) + return func(*args, **kwargs) + return wrapped + return wrapper + +def skip_if_env_value(value): + def wrapper(func): + @functools.wraps(func) + def wrapped(*args, **kwargs): + if value in os.environ: + raise unittest.SkipTest( + 'Environment variable "{}" set, skipping.'.format(value)) + return func(*args, **kwargs) + return wrapped + return wrapper + class PatchModule: ''' Fancy monkey-patching! Whee! Can't use mock.patch because it only @@ -4481,7 +4511,6 @@ class NativeFileTests(BasePlatformTests): with open(filename, 'wt') as f: f.write(textwrap.dedent('''\ {} - #!/usr/bin/env python3 import argparse import subprocess import sys @@ -4490,11 +4519,11 @@ class NativeFileTests(BasePlatformTests): parser = argparse.ArgumentParser() '''.format(chbang))) for name in kwargs: - f.write(' parser.add_argument("--{}", action="store_true")\n'.format(name)) + f.write(' parser.add_argument("-{0}", "--{0}", action="store_true")\n'.format(name)) f.write(' args, extra_args = parser.parse_known_args()\n') for name, value in kwargs.items(): f.write(' if args.{}:\n'.format(name)) - f.write(' print({})\n'.format(value)) + f.write(' print("{}", file=sys.{})\n'.format(value, kwargs.get('outfile', 'stdout'))) f.write(' sys.exit(0)\n') f.write(textwrap.dedent(''' ret = subprocess.run( @@ -4522,6 +4551,20 @@ class NativeFileTests(BasePlatformTests): f.write('py -3 {} %*'.format(filename)) return batfile + def helper_for_compiler(self, lang, cb): + """Helper for generating tests for overriding compilers for langaugages + with more than one implementation, such as C, C++, ObjC, ObjC++, and D. + """ + env = get_fake_env('', '', '') + getter = getattr(env, 'detect_{}_compiler'.format(lang)) + if lang not in ['cs']: + getter = functools.partial(getter, False) + cc = getter() + binary, newid = cb(cc) + env.config_info.binaries = {lang: binary} + compiler = getter() + self.assertEqual(compiler.id, newid) + def test_multiple_native_files_override(self): wrapper = self.helper_create_binary_wrapper('bash', version='foo') config = self.helper_create_native_file({'binaries': {'bash': wrapper}}) @@ -4565,6 +4608,140 @@ class NativeFileTests(BasePlatformTests): raise unittest.SkipTest('bat indirection breaks internal sanity checks.') self._simple_test('python', 'python') + @unittest.skipIf(is_windows(), 'Setting up multiple compilers on windows is hard') + @skip_if_env_value('CC') + def test_c_compiler(self): + def cb(comp): + if comp.id == 'gcc': + if not shutil.which('clang'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'clang', 'clang' + if not shutil.which('gcc'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'gcc', 'gcc' + self.helper_for_compiler('c', cb) + + @unittest.skipIf(is_windows(), 'Setting up multiple compilers on windows is hard') + @skip_if_env_value('CXX') + def test_cpp_compiler(self): + def cb(comp): + if comp.id == 'gcc': + if not shutil.which('clang++'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'clang++', 'clang' + if not shutil.which('g++'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'g++', 'gcc' + self.helper_for_compiler('cpp', cb) + + @skip_if_not_language('objc') + @skip_if_env_value('OBJC') + def test_objc_compiler(self): + def cb(comp): + if comp.id == 'gcc': + if not shutil.which('clang'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'clang', 'clang' + if not shutil.which('gcc'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'gcc', 'gcc' + self.helper_for_compiler('objc', cb) + + @skip_if_not_language('objcpp') + @skip_if_env_value('OBJCXX') + def test_objcpp_compiler(self): + def cb(comp): + if comp.id == 'gcc': + if not shutil.which('clang++'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'clang++', 'clang' + if not shutil.which('g++'): + raise unittest.SkipTest('Only one compiler found, cannot test.') + return 'g++', 'gcc' + self.helper_for_compiler('objcpp', cb) + + @skip_if_not_language('d') + @skip_if_env_value('DC') + def test_d_compiler(self): + def cb(comp): + if comp.id == 'dmd': + if shutil.which('ldc'): + return 'ldc', 'ldc' + elif shutil.which('gdc'): + return 'gdc', 'gdc' + else: + raise unittest.SkipTest('No alternative dlang compiler found.') + return 'dmd', 'dmd' + self.helper_for_compiler('d', cb) + + @skip_if_not_language('cs') + @skip_if_env_value('CSC') + def test_cs_compiler(self): + def cb(comp): + if comp.id == 'csc': + if not shutil.which('mcs'): + raise unittest.SkipTest('No alternate C# implementation.') + return 'mcs', 'mcs' + if not shutil.which('csc'): + raise unittest.SkipTest('No alternate C# implementation.') + return 'csc', 'csc' + self.helper_for_compiler('cs', cb) + + @skip_if_not_language('fortran') + @skip_if_env_value('FC') + def test_fortran_compiler(self): + def cb(comp): + if comp.id == 'gcc': + if shutil.which('ifort'): + return 'ifort', 'intel' + # XXX: there are several other fortran compilers meson + # supports, but I don't have any of them to test with + raise unittest.SkipTest('No alternate Fortran implementation.') + if not shutil.which('gfortran'): + raise unittest.SkipTest('No alternate C# implementation.') + return 'gfortran', 'gcc' + self.helper_for_compiler('fortran', cb) + + def _single_implementation_compiler(self, lang, binary, version_str, version): + """Helper for languages with a single (supported) implementation. + + Builds a wrapper around the compiler to override the version. + """ + wrapper = self.helper_create_binary_wrapper(binary, version=version_str) + env = get_fake_env('', '', '') + getter = getattr(env, 'detect_{}_compiler'.format(lang)) + if lang in ['rust']: + getter = functools.partial(getter, False) + env.config_info.binaries = {lang: wrapper} + compiler = getter() + self.assertEqual(compiler.version, version) + + @skip_if_not_language('vala') + @skip_if_env_value('VALAC') + def test_vala_compiler(self): + self._single_implementation_compiler( + 'vala', 'valac', 'Vala 1.2345', '1.2345') + + @skip_if_not_language('rust') + @skip_if_env_value('RUSTC') + def test_rust_compiler(self): + self._single_implementation_compiler( + 'rust', 'rustc', 'rustc 1.2345', '1.2345') + + @skip_if_not_language('java') + def test_java_compiler(self): + self._single_implementation_compiler( + 'java', 'javac', 'javac 9.99.77', '9.99.77') + + @skip_if_not_language('swift') + def test_swift_compiler(self): + wrapper = self.helper_create_binary_wrapper( + 'swiftc', version='Swift 1.2345', outfile='stderr') + env = get_fake_env('', '', '') + env.config_info.binaries = {'swift': wrapper} + compiler = env.detect_swift_compiler() + self.assertEqual(compiler.version, '1.2345') + def unset_envs(): # For unit tests we must fully control all command lines From 0ab27add49cedb6f5985dfba7ba40c8d29ae91dc Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 5 Nov 2018 09:05:24 -0800 Subject: [PATCH 11/11] docs: Add documentation for Native Files --- docs/markdown/Native-environments.md | 76 ++++++++++++++++++++++++++ docs/markdown/snippets/native_files.md | 15 +++++ docs/sitemap.txt | 1 + 3 files changed, 92 insertions(+) create mode 100644 docs/markdown/Native-environments.md create mode 100644 docs/markdown/snippets/native_files.md diff --git a/docs/markdown/Native-environments.md b/docs/markdown/Native-environments.md new file mode 100644 index 000000000..af7edd23b --- /dev/null +++ b/docs/markdown/Native-environments.md @@ -0,0 +1,76 @@ +--- +short-description: Setting up native compilation +... + +# Persistent native environments + +New in 0.49.0 + +Meson has [cross files for describing cross compilation environments](Cross-compilation.md), +for describing native environments it has equivalent "native files". + +Natives describe the *build machine*, and can be used to override properties of +non-cross builds, as well as properties that are marked as "native" in a cross +build. + +There are a couple of reasons you might want to use a native file to keep a +persistent environment: + +* To build with a non-default native tool chain (such as clang instead of gcc) +* To use a non-default version of another binary, such as yacc, or llvm-config + + +## Changing native file settings + +All of the rules about cross files and changed settings apply to native files +as well, see [here](Cross-compilation.md#Changing-cross-file-settings) + + +## Defining the environment + +### Binaries + +Currently the only use of native files is to override native binaries. This +includes the compilers and binaries collected with `find_program`, and those +used by dependencies that use a config-tool instead of pkgconfig for detection, +like `llvm-config` + +```ini +[binaries] +c = '/usr/local/bin/clang' +cpp = '/usr/local/bin/clang++' +rust = '/usr/local/bin/rust' +llvm-conifg = '/usr/local/llvm-svn/bin/llvm-config' +``` + +## Loading multiple native files + +Unlike cross file, native files allow layering. More than one native file can be +loaded, with values from a previous file being overridden by the next. The +intention of this is not overriding, but to allow composing native files. + +For example, if there is a project using C and C++, python 3.4-3.7, and LLVM +5-7, and it needs to build with clang 5, 6, and 7, and gcc 5.x, 6.x, and 7.x; +expressing all of these configurations in monolithic configurations would +result in 81 different native files. By layering them, it can be expressed by +just 12 native files. + + +## Native file locations + +Like cross files, native files may be installed to user or system wide +locations, defined as: + - $XDG_DATA_DIRS/meson/native + (/usr/local/share/meson/native:/usr/share/meson/native if $XDG_DATA_DIRS is + undefined) + - $XDG_DATA_HOME/meson/native ($HOME/.local/share/meson/native if + $XDG_DATA_HOME is undefined) + +The order of locations tried is as follows: + - A file relative to the local dir + - The user local location + - The system wide locations in order + +These files are not intended to be shipped by distributions, unless they are +specifically for distribution packaging, they are mainly intended for +developers. diff --git a/docs/markdown/snippets/native_files.md b/docs/markdown/snippets/native_files.md new file mode 100644 index 000000000..7bc3644b4 --- /dev/null +++ b/docs/markdown/snippets/native_files.md @@ -0,0 +1,15 @@ +## Native config files + +Native files are the counterpart to cross files, and allow specifying +information about the build machine, both when cross compiling and when not. + +Currently the native files only allow specifying the names of binaries, similar +to the cross file, for example: + +```ini +[binaries] +llvm-config = "/opt/llvm-custom/bin/llvm-config" +``` + +Will override the llvm-config used for *native* binaries. Targets for the host +machine will continue to use the cross file. diff --git a/docs/sitemap.txt b/docs/sitemap.txt index bfed027cf..f79eb0584 100644 --- a/docs/sitemap.txt +++ b/docs/sitemap.txt @@ -9,6 +9,7 @@ index.md Using-with-Visual-Studio.md Meson-sample.md Syntax.md + Native-environments.md Build-targets.md Include-directories.md Installing.md