From 28c93ce4af25750b721e2dcaebb1864d509edd32 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 16 Jul 2019 12:37:45 +0200 Subject: [PATCH 1/9] work around failure of test 185 in single-byte locales MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Windows CI runs with codepage 1252, which is basically ISO-8859-1 and does not have a mapping for character U+0151 (ő). It is currently passing because of a happy accident, as the generator command line is emitted in UTF-8 anyway (starting at commit 6089631a, "Open build files with utf-8", 2018-04-17, which however lacks documentation or history) and file.py treats it as two single-byte characters. When going through meson_exe, however, Windows passes a genuine Unicode character via CreateProcessW and file.py fails to decode it, so we need to pass errors='replace' when opening the output file. On Windows, the test is then fixed. On POSIX systems it is _still_ passing as a happy accident because (according to the current locale) the output file contains two single-byte characters rather than the single Unicode character "ő"; in fact, if one modifies the ninja backend to use force_serialize=True, meson_exe fails to build the command line for file.py and stops with a UnicodeEncodeError. --- test cases/common/185 escape and unicode/file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test cases/common/185 escape and unicode/file.py b/test cases/common/185 escape and unicode/file.py index af67a0950..40fa7ca48 100644 --- a/test cases/common/185 escape and unicode/file.py +++ b/test cases/common/185 escape and unicode/file.py @@ -6,5 +6,5 @@ import os with open(sys.argv[1]) as fh: content = fh.read().replace("{NAME}", sys.argv[2]) -with open(os.path.join(sys.argv[3]), 'w') as fh: +with open(os.path.join(sys.argv[3]), 'w', errors='replace') as fh: fh.write(content) From 3ef7b231782e3554217a61b4b04f947be76e7ca7 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 12 Jul 2019 09:31:22 +0200 Subject: [PATCH 2/9] backends: hide meson --internal exe cmdline from backends Return the command line from serialize_executable, which is then renamed to as_meson_exe_cmdline. This avoids repeating code that is common to custom targets and generators. --- mesonbuild/backend/backends.py | 4 ++-- mesonbuild/backend/ninjabackend.py | 14 ++++++-------- mesonbuild/backend/vs2010backend.py | 16 +++++++--------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 982b0ee26..71d5e200f 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -323,7 +323,7 @@ class Backend: raise MesonException('Unknown data type in object list.') return obj_list - def serialize_executable(self, tname, exe, cmd_args, workdir, env=None, + def as_meson_exe_cmdline(self, tname, exe, cmd_args, workdir, env=None, extra_paths=None, capture=None): ''' Serialize an executable for running with a generator or a custom target @@ -376,7 +376,7 @@ class Backend: extra_paths, capture, self.environment.need_exe_wrapper()) pickle.dump(es, f) - return exe_data + return self.environment.get_build_command() + ['--internal', 'exe', exe_data] def serialize_tests(self): test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index b57a783ce..3dfb2ae91 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -679,12 +679,11 @@ int dummy; if extra_paths: serialize = True if serialize: - exe_data = self.serialize_executable(target.name, target.command[0], cmd[1:], - # All targets are built from the build dir - self.environment.get_build_dir(), - extra_paths=extra_paths, - capture=ofilenames[0] if target.capture else None) - cmd = self.environment.get_build_command() + ['--internal', 'exe', exe_data] + cmd = self.as_meson_exe_cmdline(target.name, target.command[0], cmd[1:], + # All targets are built from the build dir + self.environment.get_build_dir(), + extra_paths=extra_paths, + capture=ofilenames[0] if target.capture else None) cmd_type = 'meson_exe.py custom' else: cmd_type = 'custom' @@ -1787,14 +1786,13 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) args = self.replace_paths(target, args, override_subdir=subdir) cmdlist = exe_arr + self.replace_extra_args(args, genlist) if generator.capture: - exe_data = self.serialize_executable( + cmd = self.as_meson_exe_cmdline( 'generator ' + cmdlist[0], cmdlist[0], cmdlist[1:], self.environment.get_build_dir(), capture=outfiles[0] ) - cmd = self.environment.get_build_command() + ['--internal', 'exe', exe_data] abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) os.makedirs(abs_pdir, exist_ok=True) else: diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 82fc0cf40..716dc27f7 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -144,14 +144,13 @@ class Vs2010Backend(backends.Backend): args = [x.replace('\\', '/') for x in args] cmd = exe_arr + self.replace_extra_args(args, genlist) if generator.capture: - exe_data = self.serialize_executable( + cmd = self.as_meson_exe_cmdline( 'generator ' + cmd[0], cmd[0], cmd[1:], self.environment.get_build_dir(), capture=outfiles[0] ) - cmd = self.environment.get_build_command() + ['--internal', 'exe', exe_data] abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) os.makedirs(abs_pdir, exist_ok=True) cbs = ET.SubElement(idgroup, 'CustomBuild', Include=infilename) @@ -559,18 +558,17 @@ class Vs2010Backend(backends.Backend): tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) extra_bdeps = target.get_transitive_build_target_deps() extra_paths = self.determine_windows_extra_paths(target.command[0], extra_bdeps) - exe_data = self.serialize_executable(target.name, target.command[0], cmd[1:], - # All targets run from the target dir - tdir_abs, - extra_paths=extra_paths, - capture=ofilenames[0] if target.capture else None) - wrapper_cmd = self.environment.get_build_command() + ['--internal', 'exe', exe_data] + wrapper_cmd = self.as_meson_exe_cmdline(target.name, target.command[0], cmd[1:], + # All targets run from the target dir + tdir_abs, + extra_paths=extra_paths, + capture=ofilenames[0] if target.capture else None) if target.build_always_stale: # Use a nonexistent file to always consider the target out-of-date. ofilenames += [self.nonexistent_file(os.path.join(self.environment.get_scratch_dir(), 'outofdate.file'))] self.add_custom_build(root, 'custom_target', ' '.join(self.quote_arguments(wrapper_cmd)), - deps=[exe_data] + srcs + depend_files, outputs=ofilenames) + deps=wrapper_cmd[-1:] + srcs + depend_files, outputs=ofilenames) ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets') self.generate_custom_generator_commands(target, root) self.add_regen_dependency(root) From eb5aff8b767000477e926bd5fc1bc70e4198ccdb Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Sat, 13 Jul 2019 08:54:17 +0200 Subject: [PATCH 3/9] vs2010backend: always serialize executables for generators Do the same as for custom targets. --- mesonbuild/backend/vs2010backend.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 716dc27f7..054fae060 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -143,21 +143,23 @@ class Vs2010Backend(backends.Backend): for x in args] args = [x.replace('\\', '/') for x in args] cmd = exe_arr + self.replace_extra_args(args, genlist) - if generator.capture: - cmd = self.as_meson_exe_cmdline( - 'generator ' + cmd[0], - cmd[0], - cmd[1:], - self.environment.get_build_dir(), - capture=outfiles[0] - ) - abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) - os.makedirs(abs_pdir, exist_ok=True) + # Always use a wrapper because MSBuild eats random characters when + # there are many arguments. + tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) + cmd = self.as_meson_exe_cmdline( + 'generator ' + cmd[0], + cmd[0], + cmd[1:], + tdir_abs, + capture=outfiles[0] if generator.capture else None + ) + deps = cmd[-1:] + deps + abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) + os.makedirs(abs_pdir, exist_ok=True) cbs = ET.SubElement(idgroup, 'CustomBuild', Include=infilename) ET.SubElement(cbs, 'Command').text = ' '.join(self.quote_arguments(cmd)) ET.SubElement(cbs, 'Outputs').text = ';'.join(outfiles) - if deps: - ET.SubElement(cbs, 'AdditionalInputs').text = ';'.join(deps) + ET.SubElement(cbs, 'AdditionalInputs').text = ';'.join(deps) return generator_output_files, custom_target_output_files, custom_target_include_dirs def generate(self, interp): From f95c965043221b2fc0e40c9410bd9efa88f31832 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 12 Jul 2019 09:31:22 +0200 Subject: [PATCH 4/9] backends: choose whether to serialize in as_meson_exe_cmdline This removes more duplicate code between custom targets and generators. In addition, generators can now have arguments with newlines in them. --- mesonbuild/backend/backends.py | 29 ++++++++++------ mesonbuild/backend/ninjabackend.py | 54 ++++++++--------------------- mesonbuild/backend/vs2010backend.py | 13 +++---- 3 files changed, 40 insertions(+), 56 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 71d5e200f..b016fe5e7 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -323,26 +323,33 @@ class Backend: raise MesonException('Unknown data type in object list.') return obj_list - def as_meson_exe_cmdline(self, tname, exe, cmd_args, workdir, env=None, - extra_paths=None, capture=None): + def as_meson_exe_cmdline(self, tname, exe, cmd_args, workdir=None, + for_machine=MachineChoice.BUILD, + extra_bdeps=None, capture=None, force_serialize=False): ''' Serialize an executable for running with a generator or a custom target ''' import hashlib - if env is None: - env = {} - if extra_paths is None: - # The callee didn't check if we needed extra paths, so check it here - if mesonlib.is_windows() or mesonlib.is_cygwin(): - extra_paths = self.determine_windows_extra_paths(exe, []) - else: - extra_paths = [] - # Can't just use exe.name here; it will likely be run more than once + machine = self.environment.machines[for_machine] + if machine.is_windows() or machine.is_cygwin(): + extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps or []) + else: + extra_paths = [] + + force_serialize = force_serialize or extra_paths or capture or workdir or \ + any('\n' in c for c in cmd_args) + if not force_serialize: + return None + + workdir = workdir or self.environment.get_build_dir() + env = {} if isinstance(exe, (dependencies.ExternalProgram, build.BuildTarget, build.CustomTarget)): basename = exe.name else: basename = os.path.basename(exe) + + # Can't just use exe.name here; it will likely be run more than once # Take a digest of the cmd args, env, workdir, and capture. This avoids # collisions and also makes the name deterministic over regenerations # which avoids a rebuild by Ninja because the cmdline stays the same. diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 3dfb2ae91..b0bbbf568 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -658,32 +658,13 @@ int dummy; # Add a dependency on all the outputs of this target for output in d.get_outputs(): elem.add_dep(os.path.join(self.get_target_dir(d), output)) - serialize = False - extra_paths = [] - # If the target requires capturing stdout, then use the serialized - # executable wrapper to capture that output and save it to a file. - if target.capture: - serialize = True - # If the command line requires a newline, also use the wrapper, as - # ninja does not support them in its build rule syntax. - if any('\n' in c for c in cmd): - serialize = True - # Windows doesn't have -rpath, so for EXEs that need DLLs built within - # the project, we need to set PATH so the DLLs are found. We use - # a serialized executable wrapper for that and check if the - # CustomTarget command needs extra paths first. - machine = self.environment.machines[target.for_machine] - if machine.is_windows() or machine.is_cygwin(): - extra_bdeps = target.get_transitive_build_target_deps() - extra_paths = self.determine_windows_extra_paths(target.command[0], extra_bdeps) - if extra_paths: - serialize = True - if serialize: - cmd = self.as_meson_exe_cmdline(target.name, target.command[0], cmd[1:], - # All targets are built from the build dir - self.environment.get_build_dir(), - extra_paths=extra_paths, - capture=ofilenames[0] if target.capture else None) + + meson_exe_cmd = self.as_meson_exe_cmdline(target.name, target.command[0], cmd[1:], + for_machine=target.for_machine, + extra_bdeps=target.get_transitive_build_target_deps(), + capture=ofilenames[0] if target.capture else None) + if meson_exe_cmd: + cmd = meson_exe_cmd cmd_type = 'meson_exe.py custom' else: cmd_type = 'custom' @@ -1785,18 +1766,13 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) outfilelist = outfilelist[len(generator.outputs):] args = self.replace_paths(target, args, override_subdir=subdir) cmdlist = exe_arr + self.replace_extra_args(args, genlist) - if generator.capture: - cmd = self.as_meson_exe_cmdline( - 'generator ' + cmdlist[0], - cmdlist[0], - cmdlist[1:], - self.environment.get_build_dir(), - capture=outfiles[0] - ) - abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) - os.makedirs(abs_pdir, exist_ok=True) - else: - cmd = cmdlist + meson_exe_cmd = self.as_meson_exe_cmdline('generator ' + cmdlist[0], + cmdlist[0], cmdlist[1:], + capture=outfiles[0] if generator.capture else None) + if meson_exe_cmd: + cmdlist = meson_exe_cmd + abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) + os.makedirs(abs_pdir, exist_ok=True) elem = NinjaBuildElement(self.all_outputs, outfiles, rulename, infilename) elem.add_dep([self.get_target_filename(x) for x in generator.depends]) @@ -1811,7 +1787,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) elem.add_item('DESC', 'Generating source from {!r}.'.format(sole_output)) if isinstance(exe, build.BuildTarget): elem.add_dep(self.get_target_filename(exe)) - elem.add_item('COMMAND', cmd) + elem.add_item('COMMAND', cmdlist) self.add_build(elem) def scan_fortran_module_outputs(self, target): diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 054fae060..a0f6a95b0 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -150,8 +150,9 @@ class Vs2010Backend(backends.Backend): 'generator ' + cmd[0], cmd[0], cmd[1:], - tdir_abs, - capture=outfiles[0] if generator.capture else None + workdir=tdir_abs, + capture=outfiles[0] if generator.capture else None, + force_serialize=True ) deps = cmd[-1:] + deps abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) @@ -559,12 +560,12 @@ class Vs2010Backend(backends.Backend): # there are many arguments. tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) extra_bdeps = target.get_transitive_build_target_deps() - extra_paths = self.determine_windows_extra_paths(target.command[0], extra_bdeps) wrapper_cmd = self.as_meson_exe_cmdline(target.name, target.command[0], cmd[1:], # All targets run from the target dir - tdir_abs, - extra_paths=extra_paths, - capture=ofilenames[0] if target.capture else None) + workdir=tdir_abs, + extra_bdeps=extra_bdeps, + capture=ofilenames[0] if target.capture else None, + force_serialize=True) if target.build_always_stale: # Use a nonexistent file to always consider the target out-of-date. ofilenames += [self.nonexistent_file(os.path.join(self.environment.get_scratch_dir(), From 2912f44e9c21aa7578bdceb0d78ba53a555c6397 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 12 Jul 2019 17:32:58 +0200 Subject: [PATCH 5/9] backends: always serialize custom targets if they need a wrapper --- mesonbuild/backend/backends.py | 41 +++++++++++++++++----------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index b016fe5e7..4e6c2591f 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -336,8 +336,28 @@ class Backend: else: extra_paths = [] + if isinstance(exe, dependencies.ExternalProgram): + exe_cmd = exe.get_command() + exe_for_machine = exe.for_machine + elif isinstance(exe, (build.BuildTarget, build.CustomTarget)): + exe_cmd = [self.get_target_filename_abs(exe)] + exe_for_machine = exe.for_machine + else: + exe_cmd = [exe] + exe_for_machine = MachineChoice.BUILD + is_cross_built = not self.environment.machines.matches_build_machine(exe_for_machine) + if is_cross_built and self.environment.need_exe_wrapper(): + exe_wrapper = self.environment.get_exe_wrapper() + if not exe_wrapper.found(): + msg = 'The exe_wrapper {!r} defined in the cross file is ' \ + 'needed by target {!r}, but was not found. Please ' \ + 'check the command and/or add it to PATH.' + raise MesonException(msg.format(exe_wrapper.name, tname)) + else: + exe_wrapper = None + force_serialize = force_serialize or extra_paths or capture or workdir or \ - any('\n' in c for c in cmd_args) + exe_wrapper or any('\n' in c for c in cmd_args) if not force_serialize: return None @@ -359,25 +379,6 @@ class Backend: scratch_file = 'meson_exe_{0}_{1}.dat'.format(basename, digest) exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file) with open(exe_data, 'wb') as f: - if isinstance(exe, dependencies.ExternalProgram): - exe_cmd = exe.get_command() - exe_for_machine = exe.for_machine - elif isinstance(exe, (build.BuildTarget, build.CustomTarget)): - exe_cmd = [self.get_target_filename_abs(exe)] - exe_for_machine = exe.for_machine - else: - exe_cmd = [exe] - exe_for_machine = MachineChoice.BUILD - is_cross_built = not self.environment.machines.matches_build_machine(exe_for_machine) - if is_cross_built and self.environment.need_exe_wrapper(): - exe_wrapper = self.environment.get_exe_wrapper() - if not exe_wrapper.found(): - msg = 'The exe_wrapper {!r} defined in the cross file is ' \ - 'needed by target {!r}, but was not found. Please ' \ - 'check the command and/or add it to PATH.' - raise MesonException(msg.format(exe_wrapper.name, tname)) - else: - exe_wrapper = None es = ExecutableSerialisation(basename, exe_cmd, cmd_args, env, is_cross_built, exe_wrapper, workdir, extra_paths, capture, From 8239d71025ad59e44e7ac7b43849554990efe9f9 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 12 Jul 2019 17:33:50 +0200 Subject: [PATCH 6/9] backends: create ExecutableSerialisation in meson_exe if possible If meson_exe is only being used to capture the output of the command, we can skip going through a pickled ExecutableSerialization object. This makes "ninja -v" output more useful. --- mesonbuild/backend/backends.py | 16 ++++++++++------ mesonbuild/scripts/meson_exe.py | 33 ++++++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 4e6c2591f..8328c57f0 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -69,12 +69,12 @@ class TargetInstallData: self.optional = optional class ExecutableSerialisation: - def __init__(self, name, fname, cmd_args, env, is_cross, exe_wrapper, - workdir, extra_paths, capture, needs_exe_wrapper: bool): + def __init__(self, name, fname, cmd_args, env=None, is_cross=False, exe_wrapper=None, + workdir=None, extra_paths=None, capture=None, needs_exe_wrapper: bool = False): self.name = name self.fname = fname self.cmd_args = cmd_args - self.env = env + self.env = env or {} self.is_cross = is_cross if exe_wrapper is not None: assert(isinstance(exe_wrapper, dependencies.ExternalProgram)) @@ -345,6 +345,7 @@ class Backend: else: exe_cmd = [exe] exe_for_machine = MachineChoice.BUILD + is_cross_built = not self.environment.machines.matches_build_machine(exe_for_machine) if is_cross_built and self.environment.need_exe_wrapper(): exe_wrapper = self.environment.get_exe_wrapper() @@ -356,10 +357,13 @@ class Backend: else: exe_wrapper = None - force_serialize = force_serialize or extra_paths or capture or workdir or \ + force_serialize = force_serialize or extra_paths or workdir or \ exe_wrapper or any('\n' in c for c in cmd_args) if not force_serialize: - return None + if not capture: + return None + return (self.environment.get_build_command() + + ['--internal', 'exe', '--capture', capture, '--'] + exe_cmd + cmd_args) workdir = workdir or self.environment.get_build_dir() env = {} @@ -384,7 +388,7 @@ class Backend: extra_paths, capture, self.environment.need_exe_wrapper()) pickle.dump(es, f) - return self.environment.get_build_command() + ['--internal', 'exe', exe_data] + return self.environment.get_build_command() + ['--internal', 'exe', '--unpickle', exe_data] def serialize_tests(self): test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py index c1d0d64e9..e5bc9dc3c 100644 --- a/mesonbuild/scripts/meson_exe.py +++ b/mesonbuild/scripts/meson_exe.py @@ -20,12 +20,14 @@ import platform import subprocess from .. import mesonlib +from ..backend.backends import ExecutableSerialisation options = None def buildparser(): - parser = argparse.ArgumentParser() - parser.add_argument('args', nargs='+') + parser = argparse.ArgumentParser(description='Custom executable wrapper for Meson. Do not run on your own, mmm\'kay?') + parser.add_argument('--unpickle') + parser.add_argument('--capture') return parser def is_windows(): @@ -101,13 +103,26 @@ def run_exe(exe): def run(args): global options - options = buildparser().parse_args(args) - if len(options.args) != 1: - print('Test runner for Meson. Do not run on your own, mmm\'kay?') - print(sys.argv[0] + ' [data file]') - exe_data_file = options.args[0] - with open(exe_data_file, 'rb') as f: - exe = pickle.load(f) + parser = buildparser() + options, cmd_args = parser.parse_known_args(args) + # argparse supports double dash to separate options and positional arguments, + # but the user has to remove it manually. + if cmd_args and cmd_args[0] == '--': + cmd_args = cmd_args[1:] + if not options.unpickle and not cmd_args: + parser.error('either --unpickle or executable and arguments are required') + if options.unpickle: + if cmd_args or options.capture: + parser.error('no other arguments can be used with --unpickle') + with open(options.unpickle, 'rb') as f: + exe = pickle.load(f) + else: + exe_cmd = cmd_args[0] + cmd_args = cmd_args[1:] + basename = os.path.basename(exe_cmd) + exe = ExecutableSerialisation(basename, [exe_cmd], cmd_args, + capture=options.capture) + return run_exe(exe) if __name__ == '__main__': From ed348b7da81dd05aaa7ac398ae9acc4b15bc2733 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 12 Jul 2019 17:38:55 +0200 Subject: [PATCH 7/9] backends: discover java/mono wrapper at generation time Move the magic to execute jar and .exe files from "meson --internal exe" to the backend, so that "ninja -v" shows more clearly what is happening. Signed-off-by: Paolo Bonzini --- mesonbuild/backend/backends.py | 4 ++++ mesonbuild/scripts/meson_exe.py | 30 ++++++++++-------------------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 8328c57f0..5316d455c 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -355,6 +355,10 @@ class Backend: 'check the command and/or add it to PATH.' raise MesonException(msg.format(exe_wrapper.name, tname)) else: + if exe_cmd[0].endswith('.jar'): + exe_cmd = ['java', '-jar'] + exe_cmd + elif exe_cmd[0].endswith('.exe') and not (mesonlib.is_windows() or mesonlib.is_cygwin()): + exe_cmd = ['mono'] + exe_cmd exe_wrapper = None force_serialize = force_serialize or extra_paths or workdir or \ diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py index e5bc9dc3c..c7efa7f3f 100644 --- a/mesonbuild/scripts/meson_exe.py +++ b/mesonbuild/scripts/meson_exe.py @@ -38,28 +38,18 @@ def is_cygwin(): platname = platform.system().lower() return 'cygwin' in platname -def run_with_mono(fname): - if fname.endswith('.exe') and not (is_windows() or is_cygwin()): - return True - return False - def run_exe(exe): - if exe.fname[0].endswith('.jar'): - cmd = ['java', '-jar'] + exe.fname - elif not exe.is_cross and run_with_mono(exe.fname[0]): - cmd = ['mono'] + exe.fname - else: - if exe.is_cross and exe.needs_exe_wrapper: - if exe.exe_runner is None: - raise AssertionError('BUG: Can\'t run cross-compiled exe {!r} ' - 'with no wrapper'.format(exe.name)) - elif not exe.exe_runner.found(): - raise AssertionError('BUG: Can\'t run cross-compiled exe {!r} with not-found ' - 'wrapper {!r}'.format(exe.name, exe.exe_runner.get_path())) - else: - cmd = exe.exe_runner.get_command() + exe.fname + if exe.is_cross and exe.needs_exe_wrapper: + if exe.exe_runner is None: + raise AssertionError('BUG: Can\'t run cross-compiled exe {!r} ' + 'with no wrapper'.format(exe.name)) + elif not exe.exe_runner.found(): + raise AssertionError('BUG: Can\'t run cross-compiled exe {!r} with not-found ' + 'wrapper {!r}'.format(exe.name, exe.exe_runner.get_path())) else: - cmd = exe.fname + cmd = exe.exe_runner.get_command() + exe.fname + else: + cmd = exe.fname child_env = os.environ.copy() child_env.update(exe.env) if exe.extra_paths: From b4d313b05876a1af40014a4c3b129fe2db7cafb9 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 12 Jul 2019 17:42:56 +0200 Subject: [PATCH 8/9] backends: remove unnecessary fields from ExecutableSerialisation "exe.is_cross and exe.needs_exe_wrapper" is the same condition under which meson chooses whether to include the exe_wrapper. meson_exe has an assertion for that, but now that meson_exe does not need anymore exe.is_cross, we can simplify the code if we just "trust" meson to do the right thing. Remove both fields from ExecutableSerialisation and just test the presence of the wrapper, and also remove the executable basename which is only used to "beautify" an assertion failure. --- mesonbuild/backend/backends.py | 14 +++++--------- mesonbuild/scripts/meson_exe.py | 15 +++++---------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 5316d455c..a334b3444 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -69,17 +69,14 @@ class TargetInstallData: self.optional = optional class ExecutableSerialisation: - def __init__(self, name, fname, cmd_args, env=None, is_cross=False, exe_wrapper=None, - workdir=None, extra_paths=None, capture=None, needs_exe_wrapper: bool = False): - self.name = name + def __init__(self, fname, cmd_args, env=None, exe_wrapper=None, + workdir=None, extra_paths=None, capture=None): self.fname = fname self.cmd_args = cmd_args self.env = env or {} - self.is_cross = is_cross if exe_wrapper is not None: assert(isinstance(exe_wrapper, dependencies.ExternalProgram)) self.exe_runner = exe_wrapper - self.needs_exe_wrapper = needs_exe_wrapper self.workdir = workdir self.extra_paths = extra_paths self.capture = capture @@ -387,10 +384,9 @@ class Backend: scratch_file = 'meson_exe_{0}_{1}.dat'.format(basename, digest) exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file) with open(exe_data, 'wb') as f: - es = ExecutableSerialisation(basename, exe_cmd, cmd_args, env, - is_cross_built, exe_wrapper, workdir, - extra_paths, capture, - self.environment.need_exe_wrapper()) + es = ExecutableSerialisation(exe_cmd, cmd_args, env, + exe_wrapper, workdir, + extra_paths, capture) pickle.dump(es, f) return self.environment.get_build_command() + ['--internal', 'exe', '--unpickle', exe_data] diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py index c7efa7f3f..8f7d0fd3d 100644 --- a/mesonbuild/scripts/meson_exe.py +++ b/mesonbuild/scripts/meson_exe.py @@ -39,15 +39,11 @@ def is_cygwin(): return 'cygwin' in platname def run_exe(exe): - if exe.is_cross and exe.needs_exe_wrapper: - if exe.exe_runner is None: - raise AssertionError('BUG: Can\'t run cross-compiled exe {!r} ' - 'with no wrapper'.format(exe.name)) - elif not exe.exe_runner.found(): + if exe.exe_runner: + if not exe.exe_runner.found(): raise AssertionError('BUG: Can\'t run cross-compiled exe {!r} with not-found ' - 'wrapper {!r}'.format(exe.name, exe.exe_runner.get_path())) - else: - cmd = exe.exe_runner.get_command() + exe.fname + 'wrapper {!r}'.format(exe.fname[0], exe.exe_runner.get_path())) + cmd = exe.exe_runner.get_command() + exe.fname else: cmd = exe.fname child_env = os.environ.copy() @@ -109,8 +105,7 @@ def run(args): else: exe_cmd = cmd_args[0] cmd_args = cmd_args[1:] - basename = os.path.basename(exe_cmd) - exe = ExecutableSerialisation(basename, [exe_cmd], cmd_args, + exe = ExecutableSerialisation([exe_cmd], cmd_args, capture=options.capture) return run_exe(exe) From d34e53202043abab121ba93fa3922e5b2e4961b8 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 12 Jul 2019 17:46:58 +0200 Subject: [PATCH 9/9] backends: do not split command and arguments in ExecutableSerialisation This is not needed anymore, just make a single field with the whole command line. --- mesonbuild/backend/backends.py | 5 ++--- mesonbuild/scripts/meson_exe.py | 13 +++++-------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index a334b3444..8c2752a77 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -69,9 +69,8 @@ class TargetInstallData: self.optional = optional class ExecutableSerialisation: - def __init__(self, fname, cmd_args, env=None, exe_wrapper=None, + def __init__(self, cmd_args, env=None, exe_wrapper=None, workdir=None, extra_paths=None, capture=None): - self.fname = fname self.cmd_args = cmd_args self.env = env or {} if exe_wrapper is not None: @@ -384,7 +383,7 @@ class Backend: scratch_file = 'meson_exe_{0}_{1}.dat'.format(basename, digest) exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file) with open(exe_data, 'wb') as f: - es = ExecutableSerialisation(exe_cmd, cmd_args, env, + es = ExecutableSerialisation(exe_cmd + cmd_args, env, exe_wrapper, workdir, extra_paths, capture) pickle.dump(es, f) diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py index 8f7d0fd3d..8b34448a5 100644 --- a/mesonbuild/scripts/meson_exe.py +++ b/mesonbuild/scripts/meson_exe.py @@ -42,10 +42,10 @@ def run_exe(exe): if exe.exe_runner: if not exe.exe_runner.found(): raise AssertionError('BUG: Can\'t run cross-compiled exe {!r} with not-found ' - 'wrapper {!r}'.format(exe.fname[0], exe.exe_runner.get_path())) - cmd = exe.exe_runner.get_command() + exe.fname + 'wrapper {!r}'.format(exe.cmd_args[0], exe.exe_runner.get_path())) + cmd_args = exe.exe_runner.get_command() + exe.cmd_args else: - cmd = exe.fname + cmd_args = exe.cmd_args child_env = os.environ.copy() child_env.update(exe.env) if exe.extra_paths: @@ -61,7 +61,7 @@ def run_exe(exe): else: child_env['WINEPATH'] = wine_path - p = subprocess.Popen(cmd + exe.cmd_args, env=child_env, cwd=exe.workdir, + p = subprocess.Popen(cmd_args, env=child_env, cwd=exe.workdir, close_fds=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -103,10 +103,7 @@ def run(args): with open(options.unpickle, 'rb') as f: exe = pickle.load(f) else: - exe_cmd = cmd_args[0] - cmd_args = cmd_args[1:] - exe = ExecutableSerialisation([exe_cmd], cmd_args, - capture=options.capture) + exe = ExecutableSerialisation(cmd_args, capture=options.capture) return run_exe(exe)