# Copyright 2013-2016 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. from __future__ import annotations import os import sys import argparse import pickle import subprocess import typing as T import locale from ..utils.core import ExecutableSerialisation def buildparser() -> argparse.ArgumentParser: 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') parser.add_argument('--feed') return parser def run_exe(exe: ExecutableSerialisation, extra_env: T.Optional[T.Dict[str, str]] = None) -> int: if exe.exe_wrapper: if not exe.exe_wrapper.found(): raise AssertionError('BUG: Can\'t run cross-compiled exe {!r} with not-found ' 'wrapper {!r}'.format(exe.cmd_args[0], exe.exe_wrapper.get_path())) cmd_args = exe.exe_wrapper.get_command() + exe.cmd_args else: cmd_args = exe.cmd_args child_env = os.environ.copy() if extra_env: child_env.update(extra_env) if exe.env: child_env = exe.env.get_env(child_env) if exe.extra_paths: child_env['PATH'] = (os.pathsep.join(exe.extra_paths + ['']) + child_env['PATH']) if exe.exe_wrapper and any('wine' in i for i in exe.exe_wrapper.get_command()): from .. import mesonlib child_env['WINEPATH'] = mesonlib.get_wine_shortpath( exe.exe_wrapper.get_command(), ['Z:' + p for p in exe.extra_paths] + child_env.get('WINEPATH', '').split(';'), exe.workdir ) stdin = None if exe.feed: stdin = open(exe.feed, 'rb') 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, stdin=stdin, stdout=pipe, stderr=pipe) stdout, stderr = p.communicate() if stdin is not None: stdin.close() if p.returncode == 0xc0000135: # STATUS_DLL_NOT_FOUND on Windows indicating a common problem that is otherwise hard to diagnose raise FileNotFoundError('due to missing DLLs') if p.returncode != 0: if exe.pickled: print(f'while executing {cmd_args!r}') if exe.verbose: return p.returncode encoding = locale.getpreferredencoding() if not exe.capture: print('--- stdout ---') print(stdout.decode(encoding=encoding, errors='replace')) print('--- stderr ---') print(stderr.decode(encoding=encoding, errors='replace')) return p.returncode if exe.capture: skip_write = False try: with open(exe.capture, 'rb') as cur: skip_write = cur.read() == stdout except OSError: pass if not skip_write: with open(exe.capture, 'wb') as output: output.write(stdout) return 0 def run(args: T.List[str]) -> int: 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 or options.feed: parser.error('no other arguments can be used with --unpickle') with open(options.unpickle, 'rb') as f: exe = pickle.load(f) exe.pickled = True else: exe = ExecutableSerialisation(cmd_args, capture=options.capture, feed=options.feed) return run_exe(exe) if __name__ == '__main__': sys.exit(run(sys.argv[1:]))