diff options
Diffstat (limited to 'mesonbuild')
-rw-r--r-- | mesonbuild/backend/backends.py | 45 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 79 | ||||
-rw-r--r-- | mesonbuild/backend/vs2010backend.py | 38 | ||||
-rw-r--r-- | mesonbuild/build.py | 79 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 14 | ||||
-rw-r--r-- | mesonbuild/modules/gnome.py | 8 | ||||
-rw-r--r-- | mesonbuild/modules/i18n.py | 6 | ||||
-rw-r--r-- | mesonbuild/mtest.py | 132 | ||||
-rw-r--r-- | mesonbuild/scripts/commandrunner.py | 84 | ||||
-rw-r--r-- | mesonbuild/scripts/meson_exe.py | 11 |
10 files changed, 189 insertions, 307 deletions
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 233173f..e19afca 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -21,8 +21,6 @@ import json import os import pickle import re -import shlex -import textwrap import typing as T import hashlib import copy @@ -34,7 +32,7 @@ from .. import mlog from ..compilers import LANGUAGES_USING_LDFLAGS from ..mesonlib import ( File, MachineChoice, MesonException, OptionType, OrderedSet, OptionOverrideProxy, - classify_unity_sources, unholder, OptionKey + classify_unity_sources, unholder, OptionKey, join_args ) if T.TYPE_CHECKING: @@ -138,6 +136,7 @@ class ExecutableSerialisation: self.capture = capture self.pickled = False self.skip_if_destdir = False + self.verbose = False class TestSerialisation: def __init__(self, name: str, project: str, suite: str, fname: T.List[str], @@ -432,12 +431,14 @@ class Backend: def as_meson_exe_cmdline(self, tname, exe, cmd_args, workdir=None, extra_bdeps=None, capture=None, force_serialize=False, - env: T.Optional[build.EnvironmentVariables] = None): + env: T.Optional[build.EnvironmentVariables] = None, + verbose: bool = False): ''' Serialize an executable for running with a generator or a custom target ''' cmd = [exe] + cmd_args es = self.get_executable_serialisation(cmd, workdir, extra_bdeps, capture, env) + es.verbose = verbose reasons = [] if es.extra_paths: reasons.append('to set PATH') @@ -987,18 +988,8 @@ class Backend: if delta > 0.001: raise MesonException('Clock skew detected. File {} has a time stamp {:.4f}s in the future.'.format(absf, delta)) - def build_target_to_cmd_array(self, bt, check_cross): + def build_target_to_cmd_array(self, bt): if isinstance(bt, build.BuildTarget): - if check_cross and isinstance(bt, build.Executable) and bt.for_machine is not MachineChoice.BUILD: - if (self.environment.is_cross_build() and - self.environment.exe_wrapper is None and - self.environment.need_exe_wrapper()): - s = textwrap.dedent(''' - Cannot use target {} as a generator because it is built for the - host machine and no exe wrapper is defined or needs_exe_wrapper is - true. You might want to set `native: true` instead to build it for - the build machine.'''.format(bt.name)) - raise MesonException(s) arr = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(bt))] else: arr = bt.get_command() @@ -1129,11 +1120,9 @@ class Backend: inputs = self.get_custom_target_sources(target) # Evaluate the command list cmd = [] - index = -1 for i in target.command: - index += 1 if isinstance(i, build.BuildTarget): - cmd += self.build_target_to_cmd_array(i, (index == 0)) + cmd += self.build_target_to_cmd_array(i) continue elif isinstance(i, build.CustomTarget): # GIR scanner will attempt to execute this binary but @@ -1146,10 +1135,7 @@ class Backend: i = os.path.join(self.environment.get_build_dir(), i) # FIXME: str types are blindly added ignoring 'target.absolute_paths' # because we can't know if they refer to a file or just a string - elif not isinstance(i, str): - err_msg = 'Argument {0} is of unknown type {1}' - raise RuntimeError(err_msg.format(str(i), str(type(i)))) - else: + elif isinstance(i, str): if '@SOURCE_ROOT@' in i: i = i.replace('@SOURCE_ROOT@', source_root) if '@BUILD_ROOT@' in i: @@ -1179,6 +1165,9 @@ class Backend: else: lead_dir = self.environment.get_build_dir() i = i.replace(source, os.path.join(lead_dir, outdir)) + else: + err_msg = 'Argument {0} is of unknown type {1}' + raise RuntimeError(err_msg.format(str(i), str(type(i)))) cmd.append(i) # Substitute the rest of the template strings values = mesonlib.get_filenames_templates_dict(inputs, outputs) @@ -1204,11 +1193,21 @@ class Backend: cmd = [i.replace('\\', '/') for i in cmd] return inputs, outputs, cmd + def get_run_target_env(self, target: build.RunTarget) -> build.EnvironmentVariables: + env = target.env if target.env else build.EnvironmentVariables() + introspect_cmd = join_args(self.environment.get_build_command() + ['introspect']) + env.add_var(env.set, 'MESON_SOURCE_ROOT', [self.environment.get_source_dir()], {}) + env.add_var(env.set, 'MESON_BUILD_ROOT', [self.environment.get_build_dir()], {}) + env.add_var(env.set, 'MESON_SUBDIR', [target.subdir], {}) + env.add_var(env.set, 'MESONINTROSPECT', [introspect_cmd], {}) + return env + def run_postconf_scripts(self) -> None: from ..scripts.meson_exe import run_exe + introspect_cmd = join_args(self.environment.get_build_command() + ['introspect']) env = {'MESON_SOURCE_ROOT': self.environment.get_source_dir(), 'MESON_BUILD_ROOT': self.environment.get_build_dir(), - 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in self.environment.get_build_command() + ['introspect']]), + 'MESONINTROSPECT': introspect_cmd, } for s in self.build.postconf_scripts: diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 8b4ba7f..b93ac39 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -28,7 +28,6 @@ from .. import modules from .. import environment, mesonlib from .. import build from .. import mlog -from .. import dependencies from .. import compilers from ..arglist import CompilerArgs from ..compilers import ( @@ -974,7 +973,6 @@ int dummy; elem.add_item('DEPFILE', rel_dfile) if target.console: elem.add_item('pool', 'console') - cmd = self.replace_paths(target, cmd) elem.add_item('COMMAND', cmd) elem.add_item('description', desc.format(target.name, cmd_type)) self.add_build(elem) @@ -988,65 +986,28 @@ int dummy; return '{}{}'.format(subproject_prefix, target.name) def generate_run_target(self, target): - cmd = self.environment.get_build_command() + ['--internal', 'commandrunner'] - deps = self.unwrap_dep_list(target) - arg_strings = [] - for i in target.args: - if isinstance(i, str): - arg_strings.append(i) - elif isinstance(i, (build.BuildTarget, build.CustomTarget)): - relfname = self.get_target_filename(i) - arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname)) - deps.append(relfname) - elif isinstance(i, mesonlib.File): - relfname = i.rel_to_builddir(self.build_to_src) - arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname)) - else: - raise AssertionError('Unreachable code in generate_run_target: ' + str(i)) - cmd += [self.environment.get_source_dir(), - self.environment.get_build_dir(), - target.subdir] + self.environment.get_build_command() - texe = target.command - try: - texe = texe.held_object - except AttributeError: - pass - if isinstance(texe, build.Executable): - abs_exe = os.path.join(self.environment.get_build_dir(), self.get_target_filename(texe)) - deps.append(self.get_target_filename(texe)) - if self.environment.is_cross_build(): - exe_wrap = self.environment.get_exe_wrapper() - if exe_wrap: - if not exe_wrap.found(): - msg = 'The exe_wrapper {!r} defined in the cross file is ' \ - 'needed by run target {!r}, but was not found. ' \ - 'Please check the command and/or add it to PATH.' - raise MesonException(msg.format(exe_wrap.name, target.name)) - cmd += exe_wrap.get_command() - cmd.append(abs_exe) - elif isinstance(texe, dependencies.ExternalProgram): - cmd += texe.get_command() - elif isinstance(texe, build.CustomTarget): - deps.append(self.get_target_filename(texe)) - cmd += [os.path.join(self.environment.get_build_dir(), self.get_target_filename(texe))] - elif isinstance(texe, mesonlib.File): - cmd.append(texe.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir())) + target_name = self.build_run_target_name(target) + if not target.command: + # This is an alias target, it has no command, it just depends on + # other targets. + elem = NinjaBuildElement(self.all_outputs, target_name, 'phony', []) else: - cmd.append(target.command) - cmd += arg_strings - - if texe: - target_name = 'meson-{}'.format(self.build_run_target_name(target)) - elem = NinjaBuildElement(self.all_outputs, target_name, 'CUSTOM_COMMAND', []) - elem.add_item('COMMAND', cmd) - elem.add_item('description', 'Running external command {}'.format(target.name)) + target_env = self.get_run_target_env(target) + _, _, cmd = self.eval_custom_target_command(target) + desc = 'Running external command {}{}' + meson_exe_cmd, reason = self.as_meson_exe_cmdline(target_name, cmd[0], cmd[1:], + force_serialize=True, env=target_env, + verbose=True) + cmd_type = ' (wrapped by meson {})'.format(reason) + internal_target_name = 'meson-{}'.format(target_name) + elem = NinjaBuildElement(self.all_outputs, internal_target_name, 'CUSTOM_COMMAND', []) + elem.add_item('COMMAND', meson_exe_cmd) + elem.add_item('description', desc.format(target.name, cmd_type)) elem.add_item('pool', 'console') # Alias that runs the target defined above with the name the user specified - self.create_target_alias(target_name) - else: - target_name = self.build_run_target_name(target) - elem = NinjaBuildElement(self.all_outputs, target_name, 'phony', []) - + self.create_target_alias(internal_target_name) + deps = self.unwrap_dep_list(target) + deps += self.get_custom_target_depend_files(target) elem.add_dep(deps) self.add_build(elem) self.processed_targets[target.get_id()] = True @@ -2110,7 +2071,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) generator = genlist.get_generator() subdir = genlist.subdir exe = generator.get_exe() - exe_arr = self.build_target_to_cmd_array(exe, True) + exe_arr = self.build_target_to_cmd_array(exe) infilelist = genlist.get_inputs() outfilelist = genlist.get_outputs() extra_dependencies = self.get_custom_target_depend_files(genlist) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index c47fb4a..e94ab49 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -28,7 +28,7 @@ from .. import mlog from .. import compilers from ..interpreter import Interpreter from ..mesonlib import ( - MesonException, File, python_command, replace_if_different, OptionKey, + MesonException, python_command, replace_if_different, OptionKey, ) from ..environment import Environment, build_filename @@ -121,7 +121,7 @@ class Vs2010Backend(backends.Backend): infilelist = genlist.get_inputs() outfilelist = genlist.get_outputs() source_dir = os.path.join(down, self.build_to_src, genlist.subdir) - exe_arr = self.build_target_to_cmd_array(exe, True) + exe_arr = self.build_target_to_cmd_array(exe) idgroup = ET.SubElement(parent_node, 'ItemGroup') for i in range(len(infilelist)): if len(infilelist) == len(outfilelist): @@ -257,9 +257,8 @@ class Vs2010Backend(backends.Backend): for d in target.get_target_dependencies(): all_deps[d.get_id()] = d elif isinstance(target, build.RunTarget): - for d in [target.command] + target.args: - if isinstance(d, (build.BuildTarget, build.CustomTarget)): - all_deps[d.get_id()] = d + for d in target.get_dependencies(): + all_deps[d.get_id()] = d elif isinstance(target, build.BuildTarget): for ldep in target.link_targets: if isinstance(ldep, build.CustomTargetIndex): @@ -534,27 +533,14 @@ class Vs2010Backend(backends.Backend): # is probably a better way than running a this dummy command. cmd_raw = python_command + ['-c', 'exit'] else: - cmd_raw = [target.command] + target.args - cmd = python_command + \ - [os.path.join(self.environment.get_script_dir(), 'commandrunner.py'), - self.environment.get_source_dir(), - self.environment.get_build_dir(), - self.get_target_dir(target)] + self.environment.get_build_command() - for i in cmd_raw: - if isinstance(i, build.BuildTarget): - cmd.append(os.path.join(self.environment.get_build_dir(), self.get_target_filename(i))) - elif isinstance(i, dependencies.ExternalProgram): - cmd += i.get_command() - elif isinstance(i, File): - relfname = i.rel_to_builddir(self.build_to_src) - cmd.append(os.path.join(self.environment.get_build_dir(), relfname)) - elif isinstance(i, str): - # Escape embedded quotes, because we quote the entire argument below. - cmd.append(i.replace('"', '\\"')) - else: - cmd.append(i) - cmd_templ = '''"%s" ''' * len(cmd) - self.add_custom_build(root, 'run_target', cmd_templ % tuple(cmd)) + _, _, cmd_raw = self.eval_custom_target_command(target) + depend_files = self.get_custom_target_depend_files(target) + target_env = self.get_run_target_env(target) + wrapper_cmd, _ = self.as_meson_exe_cmdline(target.name, cmd_raw[0], cmd_raw[1:], + force_serialize=True, env=target_env, + verbose=True) + self.add_custom_build(root, 'run_target', ' '.join(self.quote_arguments(wrapper_cmd)), + deps=depend_files) ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets') self.add_regen_dependency(root) self.add_target_deps(root, target) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index e9d6dae..9a4f8b1 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -29,7 +29,7 @@ from .mesonlib import ( File, MesonException, MachineChoice, PerMachine, OrderedSet, listify, extract_as_list, typeslistify, stringlistify, classify_unity_sources, get_filenames_templates_dict, substitute_values, has_path_sep, unholder, - OptionKey, + OptionKey ) from .compilers import ( Compiler, is_object, clink_langs, sort_clink, lang_suffixes, @@ -39,7 +39,6 @@ from .linkers import StaticLinker from .interpreterbase import FeatureNew if T.TYPE_CHECKING: - from .coredata import KeyedOptionDictType, OptionDictType from .interpreter import Test from .mesonlib import FileMode, FileOrString @@ -2149,8 +2148,35 @@ class SharedModule(SharedLibrary): def get_default_install_dir(self, environment): return environment.get_shared_module_dir() +class CommandBase: + def flatten_command(self, cmd): + cmd = unholder(listify(cmd)) + final_cmd = [] + for c in cmd: + if isinstance(c, str): + final_cmd.append(c) + elif isinstance(c, File): + self.depend_files.append(c) + final_cmd.append(c) + elif isinstance(c, dependencies.ExternalProgram): + if not c.found(): + raise InvalidArguments('Tried to use not-found external program in "command"') + path = c.get_path() + if os.path.isabs(path): + # Can only add a dependency on an external program which we + # know the absolute path of + self.depend_files.append(File.from_absolute_file(path)) + final_cmd += c.get_command() + elif isinstance(c, (BuildTarget, CustomTarget)): + self.dependencies.append(c) + final_cmd.append(c) + elif isinstance(c, list): + final_cmd += self.flatten_command(c) + else: + raise InvalidArguments('Argument {!r} in "command" is invalid'.format(c)) + return final_cmd -class CustomTarget(Target): +class CustomTarget(Target, CommandBase): known_kwargs = set([ 'input', 'output', @@ -2222,33 +2248,6 @@ class CustomTarget(Target): bdeps.update(d.get_transitive_build_target_deps()) return bdeps - def flatten_command(self, cmd): - cmd = unholder(listify(cmd)) - final_cmd = [] - for c in cmd: - if isinstance(c, str): - final_cmd.append(c) - elif isinstance(c, File): - self.depend_files.append(c) - final_cmd.append(c) - elif isinstance(c, dependencies.ExternalProgram): - if not c.found(): - raise InvalidArguments('Tried to use not-found external program in "command"') - path = c.get_path() - if os.path.isabs(path): - # Can only add a dependency on an external program which we - # know the absolute path of - self.depend_files.append(File.from_absolute_file(path)) - final_cmd += c.get_command() - elif isinstance(c, (BuildTarget, CustomTarget)): - self.dependencies.append(c) - final_cmd.append(c) - elif isinstance(c, list): - final_cmd += self.flatten_command(c) - else: - raise InvalidArguments('Argument {!r} in "command" is invalid'.format(c)) - return final_cmd - def process_kwargs(self, kwargs, backend): self.process_kwargs_base(kwargs) self.sources = unholder(extract_as_list(kwargs, 'input')) @@ -2333,7 +2332,7 @@ class CustomTarget(Target): for ed in unholder(extra_deps): if not isinstance(ed, (CustomTarget, BuildTarget)): raise InvalidArguments('Can only depend on toplevel targets: custom_target or build_target (executable or a library) got: {}({})' - .format(type(ed), ed)) + .format(type(ed), ed)) self.extra_depends.append(ed) for i in depend_files: if isinstance(i, (File, str)): @@ -2430,18 +2429,20 @@ class CustomTarget(Target): for i in self.outputs: yield CustomTargetIndex(self, i) -class RunTarget(Target): - def __init__(self, name, command, args, dependencies, subdir, subproject): +class RunTarget(Target, CommandBase): + def __init__(self, name, command, dependencies, subdir, subproject, env=None): self.typename = 'run' # These don't produce output artifacts super().__init__(name, subdir, subproject, False, MachineChoice.BUILD) - self.command = command - self.args = args self.dependencies = dependencies + self.depend_files = [] + self.command = self.flatten_command(command) + self.absolute_paths = False + self.env = env def __repr__(self): repr_str = "<{0} {1}: {2}>" - return repr_str.format(self.__class__.__name__, self.get_id(), self.command) + return repr_str.format(self.__class__.__name__, self.get_id(), self.command[0]) def process_kwargs(self, kwargs): return self.process_kwargs_base(kwargs) @@ -2474,7 +2475,7 @@ class RunTarget(Target): class AliasTarget(RunTarget): def __init__(self, name, dependencies, subdir, subproject): - super().__init__(name, '', [], dependencies, subdir, subproject) + super().__init__(name, [], dependencies, subdir, subproject) class Jar(BuildTarget): known_kwargs = known_jar_kwargs @@ -2626,11 +2627,13 @@ class Data: class TestSetup: def __init__(self, exe_wrapper: T.Optional[T.List[str]], gdb: bool, - timeout_multiplier: int, env: EnvironmentVariables): + timeout_multiplier: int, env: EnvironmentVariables, + exclude_suites: T.List[str]): self.exe_wrapper = exe_wrapper self.gdb = gdb self.timeout_multiplier = timeout_multiplier self.env = env + self.exclude_suites = exclude_suites def get_sources_string_names(sources, backend): ''' diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 54e9a04..f670aec 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2299,7 +2299,8 @@ permitted_kwargs = {'add_global_arguments': {'language', 'native'}, 'add_languages': {'required', 'native'}, 'add_project_link_arguments': {'language', 'native'}, 'add_project_arguments': {'language', 'native'}, - 'add_test_setup': {'exe_wrapper', 'gdb', 'timeout_multiplier', 'env', 'is_default'}, + 'add_test_setup': {'exe_wrapper', 'gdb', 'timeout_multiplier', 'env', 'is_default', + 'exclude_suites'}, 'benchmark': _base_test_args, 'build_target': known_build_target_kwargs, 'configure_file': {'input', @@ -2376,7 +2377,7 @@ permitted_kwargs = {'add_global_arguments': {'language', 'native'}, 'jar': build.known_jar_kwargs, 'project': {'version', 'meson_version', 'default_options', 'license', 'subproject_dir'}, 'run_command': {'check', 'capture', 'env'}, - 'run_target': {'command', 'depends'}, + 'run_target': {'command', 'depends', 'env'}, 'shared_library': build.known_shlib_kwargs, 'shared_module': build.known_shmod_kwargs, 'static_library': build.known_stlib_kwargs, @@ -4056,6 +4057,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self self.add_target(name, tg.held_object) return tg + @FeatureNewKwargs('run_target', '0.57.0', ['env']) @permittedKwargs(permitted_kwargs['run_target']) def func_run_target(self, node, args, kwargs): if len(args) > 1: @@ -4084,8 +4086,8 @@ This will become a hard error in the future.''' % kwargs['input'], location=self if not isinstance(d, (build.BuildTarget, build.CustomTarget)): raise InterpreterException('Depends items must be build targets.') cleaned_deps.append(d) - command, *cmd_args = cleaned_args - tg = RunTargetHolder(build.RunTarget(name, command, cmd_args, cleaned_deps, self.subdir, self.subproject), self) + env = self.unpack_env_kwarg(kwargs) + tg = RunTargetHolder(build.RunTarget(name, cleaned_args, cleaned_deps, self.subdir, self.subproject, env), self) self.add_target(name, tg.held_object) full_name = (self.subproject, name) assert(full_name not in self.build.run_target_names) @@ -4683,8 +4685,10 @@ different subdirectory. raise InterpreterException('\'%s\' is already set as default. ' 'is_default can be set to true only once' % self.build.test_setup_default_name) self.build.test_setup_default_name = setup_name + exclude_suites = mesonlib.stringlistify(kwargs.get('exclude_suites', [])) env = self.unpack_env_kwarg(kwargs) - self.build.test_setups[setup_name] = build.TestSetup(exe_wrapper, gdb, timeout_multiplier, env) + self.build.test_setups[setup_name] = build.TestSetup(exe_wrapper, gdb, timeout_multiplier, env, + exclude_suites) @permittedKwargs(permitted_kwargs['add_global_arguments']) @stringArgs diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index e72944d..f966083 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -956,8 +956,8 @@ class GnomeModule(ExtensionModule): '--id=' + project_id, '--sources=' + source_str, ] - pottarget = build.RunTarget('help-' + project_id + '-pot', potargs[0], - potargs[1:], [], state.subdir, state.subproject) + pottarget = build.RunTarget('help-' + project_id + '-pot', potargs, + [], state.subdir, state.subproject) poargs = state.environment.get_build_command() + [ '--internal', 'yelphelper', 'update-po', @@ -966,8 +966,8 @@ class GnomeModule(ExtensionModule): '--sources=' + source_str, '--langs=' + '@@'.join(langs), ] - potarget = build.RunTarget('help-' + project_id + '-update-po', poargs[0], - poargs[1:], [], state.subdir, state.subproject) + potarget = build.RunTarget('help-' + project_id + '-update-po', poargs, + [], state.subdir, state.subproject) rv = [inscript, pottarget, potarget] return ModuleReturnValue(None, rv) diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py index ae24e6e..54faf4c 100644 --- a/mesonbuild/modules/i18n.py +++ b/mesonbuild/modules/i18n.py @@ -152,12 +152,12 @@ class I18nModule(ExtensionModule): potargs.append(datadirs) if extra_args: potargs.append(extra_args) - pottarget = build.RunTarget(packagename + '-pot', potargs[0], potargs[1:], [], state.subdir, state.subproject) + pottarget = build.RunTarget(packagename + '-pot', potargs, [], state.subdir, state.subproject) gmoargs = state.environment.get_build_command() + ['--internal', 'gettext', 'gen_gmo'] if lang_arg: gmoargs.append(lang_arg) - gmotarget = build.RunTarget(packagename + '-gmo', gmoargs[0], gmoargs[1:], [], state.subdir, state.subproject) + gmotarget = build.RunTarget(packagename + '-gmo', gmoargs, [], state.subdir, state.subproject) updatepoargs = state.environment.get_build_command() + ['--internal', 'gettext', 'update_po', pkg_arg] if lang_arg: @@ -166,7 +166,7 @@ class I18nModule(ExtensionModule): updatepoargs.append(datadirs) if extra_args: updatepoargs.append(extra_args) - updatepotarget = build.RunTarget(packagename + '-update-po', updatepoargs[0], updatepoargs[1:], [], state.subdir, state.subproject) + updatepotarget = build.RunTarget(packagename + '-update-po', updatepoargs, [], state.subdir, state.subproject) targets = [pottarget, gmotarget, updatepotarget] diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 24db5ce..79bb075 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -1114,22 +1114,6 @@ def check_testdata(objs: T.List[TestSerialisation]) -> T.List[TestSerialisation] raise MesonVersionMismatchException(obj.version, coredata_version) return objs -def load_benchmarks(build_dir: str) -> T.List[TestSerialisation]: - datafile = Path(build_dir) / 'meson-private' / 'meson_benchmark_setup.dat' - if not datafile.is_file(): - raise TestException('Directory {!r} does not seem to be a Meson build directory.'.format(build_dir)) - with datafile.open('rb') as f: - objs = check_testdata(pickle.load(f)) - return objs - -def load_tests(build_dir: str) -> T.List[TestSerialisation]: - datafile = Path(build_dir) / 'meson-private' / 'meson_test_setup.dat' - if not datafile.is_file(): - raise TestException('Directory {!r} does not seem to be a Meson build directory.'.format(build_dir)) - with datafile.open('rb') as f: - objs = check_testdata(pickle.load(f)) - return objs - # Custom waiting primitives for asyncio async def try_wait_one(*awaitables: T.Any, timeout: T.Optional[T.Union[int, float]]) -> None: @@ -1428,16 +1412,47 @@ class TestHarness: self.loggers.append(ConsoleLogger()) self.need_console = False - if self.options.benchmark: - self.tests = load_benchmarks(options.wd) - else: - self.tests = load_tests(options.wd) + self.logfile_base = None # type: T.Optional[str] + if self.options.logbase and not self.options.gdb: + namebase = None + self.logfile_base = os.path.join(self.options.wd, 'meson-logs', self.options.logbase) + + if self.options.wrapper: + namebase = os.path.basename(self.get_wrapper(self.options)[0]) + elif self.options.setup: + namebase = self.options.setup.replace(":", "_") + + if namebase: + self.logfile_base += '-' + namebase.replace(' ', '_') + + startdir = os.getcwd() + try: + if self.options.wd: + os.chdir(self.options.wd) + self.build_data = build.load(os.getcwd()) + if not self.options.setup: + self.options.setup = self.build_data.test_setup_default_name + if self.options.benchmark: + self.tests = self.load_tests('meson_benchmark_setup.dat') + else: + self.tests = self.load_tests('meson_test_setup.dat') + finally: + os.chdir(startdir) + ss = set() for t in self.tests: for s in t.suite: ss.add(s) self.suites = list(ss) + def load_tests(self, file_name: str) -> T.List[TestSerialisation]: + datafile = Path('meson-private') / file_name + if not datafile.is_file(): + raise TestException('Directory {!r} does not seem to be a Meson build directory.'.format(self.options.wd)) + with datafile.open('rb') as f: + objs = check_testdata(pickle.load(f)) + return objs + def __enter__(self) -> 'TestHarness': return self @@ -1448,16 +1463,19 @@ class TestHarness: for l in self.loggers: l.close() - def merge_suite_options(self, options: argparse.Namespace, test: TestSerialisation) -> T.Dict[str, str]: - if ':' in options.setup: - if options.setup not in self.build_data.test_setups: - sys.exit("Unknown test setup '{}'.".format(options.setup)) - current = self.build_data.test_setups[options.setup] + def get_test_setup(self, test: T.Optional[TestSerialisation]) -> build.TestSetup: + if ':' in self.options.setup: + if self.options.setup not in self.build_data.test_setups: + sys.exit("Unknown test setup '{}'.".format(self.options.setup)) + return self.build_data.test_setups[self.options.setup] else: - full_name = test.project_name + ":" + options.setup + full_name = test.project_name + ":" + self.options.setup if full_name not in self.build_data.test_setups: - sys.exit("Test setup '{}' not found from project '{}'.".format(options.setup, test.project_name)) - current = self.build_data.test_setups[full_name] + sys.exit("Test setup '{}' not found from project '{}'.".format(self.options.setup, test.project_name)) + return self.build_data.test_setups[full_name] + + def merge_setup_options(self, options: argparse.Namespace, test: TestSerialisation) -> T.Dict[str, str]: + current = self.get_test_setup(test) if not options.gdb: options.gdb = current.gdb if options.gdb: @@ -1475,10 +1493,8 @@ class TestHarness: def get_test_runner(self, test: TestSerialisation) -> SingleTestRunner: name = self.get_pretty_suite(test) options = deepcopy(self.options) - if not options.setup: - options.setup = self.build_data.test_setup_default_name - if options.setup: - env = self.merge_suite_options(options, test) + if self.options.setup: + env = self.merge_setup_options(options, test) else: env = os.environ.copy() test_env = test.env.get_env(env) @@ -1564,14 +1580,14 @@ class TestHarness: def total_failure_count(self) -> int: return self.fail_count + self.unexpectedpass_count + self.timeout_count - def doit(self, options: argparse.Namespace) -> int: + def doit(self) -> int: if self.is_run: raise RuntimeError('Test harness object can only be used once.') self.is_run = True tests = self.get_tests() if not tests: return 0 - if not options.no_rebuild and not rebuild_deps(options.wd, tests): + if not self.options.no_rebuild and not rebuild_deps(self.options.wd, tests): # We return 125 here in case the build failed. # The reason is that exit code 125 tells `git bisect run` that the current # commit should be skipped. Thus users can directly use `meson test` to @@ -1585,7 +1601,6 @@ class TestHarness: try: if self.options.wd: os.chdir(self.options.wd) - self.build_data = build.load(os.getcwd()) runners = [self.get_test_runner(test) for test in tests] self.duration_max_len = max([len(str(int(runner.timeout or 99))) for runner in runners]) @@ -1639,9 +1654,20 @@ class TestHarness: return False def test_suitable(self, test: TestSerialisation) -> bool: - return ((not self.options.include_suites or - TestHarness.test_in_suites(test, self.options.include_suites)) and not - TestHarness.test_in_suites(test, self.options.exclude_suites)) + if TestHarness.test_in_suites(test, self.options.exclude_suites): + return False + + if self.options.include_suites: + # Both force inclusion (overriding add_test_setup) and exclude + # everything else + return TestHarness.test_in_suites(test, self.options.include_suites) + + if self.options.setup: + setup = self.get_test_setup(test) + if TestHarness.test_in_suites(test, setup.exclude_suites): + return False + + return True def tests_from_args(self, tests: T.List[TestSerialisation]) -> T.Generator[TestSerialisation, None, None]: ''' @@ -1670,14 +1696,7 @@ class TestHarness: print('No tests defined.') return [] - if self.options.include_suites or self.options.exclude_suites: - tests = [] - for tst in self.tests: - if self.test_suitable(tst): - tests.append(tst) - else: - tests = self.tests - + tests = [t for t in self.tests if self.test_suitable(t)] if self.options.args: tests = list(self.tests_from_args(tests)) @@ -1692,23 +1711,12 @@ class TestHarness: l.flush() def open_logfiles(self) -> None: - if not self.options.logbase or self.options.gdb: + if not self.logfile_base: return - namebase = None - logfile_base = os.path.join(self.options.wd, 'meson-logs', self.options.logbase) - - if self.options.wrapper: - namebase = os.path.basename(self.get_wrapper(self.options)[0]) - elif self.options.setup: - namebase = self.options.setup.replace(":", "_") - - if namebase: - logfile_base += '-' + namebase.replace(' ', '_') - - self.loggers.append(JunitBuilder(logfile_base + '.junit.xml')) - self.loggers.append(JsonLogfileBuilder(logfile_base + '.json')) - self.loggers.append(TextLogfileBuilder(logfile_base + '.txt', errors='surrogateescape')) + self.loggers.append(JunitBuilder(self.logfile_base + '.junit.xml')) + self.loggers.append(JsonLogfileBuilder(self.logfile_base + '.json')) + self.loggers.append(TextLogfileBuilder(self.logfile_base + '.txt', errors='surrogateescape')) @staticmethod def get_wrapper(options: argparse.Namespace) -> T.List[str]: @@ -1913,7 +1921,7 @@ def run(options: argparse.Namespace) -> int: try: if options.list: return list_tests(th) - return th.doit(options) + return th.doit() except TestException as e: print('Meson test encountered an error:\n') if os.environ.get('MESON_FORCE_BACKTRACE'): diff --git a/mesonbuild/scripts/commandrunner.py b/mesonbuild/scripts/commandrunner.py deleted file mode 100644 index aeeaa3b..0000000 --- a/mesonbuild/scripts/commandrunner.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 2014 The Meson development team - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""This program is a wrapper to run external commands. It determines -what to run, sets up the environment and executes the command.""" - -import sys, os, subprocess, shutil, shlex -import re -import typing as T - -def run_command(source_dir: str, build_dir: str, subdir: str, meson_command: T.List[str], command: str, arguments: T.List[str]) -> subprocess.Popen: - env = {'MESON_SOURCE_ROOT': source_dir, - 'MESON_BUILD_ROOT': build_dir, - 'MESON_SUBDIR': subdir, - 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in meson_command + ['introspect']]), - } - cwd = os.path.join(source_dir, subdir) - child_env = os.environ.copy() - child_env.update(env) - - # Is the command an executable in path? - exe = shutil.which(command) - if exe is not None: - command_array = [exe] + arguments - else:# No? Maybe it is a script in the source tree. - fullpath = os.path.join(source_dir, subdir, command) - command_array = [fullpath] + arguments - try: - return subprocess.Popen(command_array, env=child_env, cwd=cwd) - except FileNotFoundError: - print('Could not execute command "%s". File not found.' % command) - sys.exit(1) - except PermissionError: - print('Could not execute command "%s". File not executable.' % command) - sys.exit(1) - except OSError as err: - print('Could not execute command "{}": {}'.format(command, err)) - sys.exit(1) - except subprocess.SubprocessError as err: - print('Could not execute command "{}": {}'.format(command, err)) - sys.exit(1) - -def is_python_command(cmdname: str) -> bool: - end_py_regex = r'python(3|3\.\d+)?(\.exe)?$' - return re.search(end_py_regex, cmdname) is not None - -def run(args: T.List[str]) -> int: - if len(args) < 4: - print('commandrunner.py <source dir> <build dir> <subdir> <command> [arguments]') - return 1 - src_dir = args[0] - build_dir = args[1] - subdir = args[2] - meson_bin = args[3] - if is_python_command(meson_bin): - meson_command = [meson_bin, args[4]] - command = args[5] - arguments = args[6:] - else: - meson_command = [meson_bin] - command = args[4] - arguments = args[5:] - pc = run_command(src_dir, build_dir, subdir, meson_command, command, arguments) - while True: - try: - pc.wait() - break - except KeyboardInterrupt: - pass - return pc.returncode - -if __name__ == '__main__': - sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py index 620f579..27db144 100644 --- a/mesonbuild/scripts/meson_exe.py +++ b/mesonbuild/scripts/meson_exe.py @@ -52,10 +52,13 @@ def run_exe(exe: ExecutableSerialisation, extra_env: T.Optional[dict] = None) -> ['Z:' + p for p in exe.extra_paths] + child_env.get('WINEPATH', '').split(';') ) + pipe = subprocess.PIPE + if exe.verbose: + assert not exe.capture, 'Cannot capture and print to console at the same time' + pipe = None + p = subprocess.Popen(cmd_args, env=child_env, cwd=exe.workdir, - close_fds=False, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + close_fds=False, stdout=pipe, stderr=pipe) stdout, stderr = p.communicate() if p.returncode == 0xc0000135: @@ -65,6 +68,8 @@ def run_exe(exe: ExecutableSerialisation, extra_env: T.Optional[dict] = None) -> if p.returncode != 0: if exe.pickled: print('while executing {!r}'.format(cmd_args)) + if exe.verbose: + return p.returncode if not exe.capture: print('--- stdout ---') print(stdout.decode()) |