diff options
| author | Daniel Mensinger <daniel@mensinger-ka.de> | 2020-01-08 17:06:58 +0100 |
|---|---|---|
| committer | Daniel Mensinger <daniel@mensinger-ka.de> | 2020-01-26 18:23:34 +0100 |
| commit | 3607f50d7f5bfa33806a4ed48d4fb773f35ee537 (patch) | |
| tree | eb9bcf2554f4c3ea879f1057d82e920cc963d5b1 /mesonbuild/cmake/executor.py | |
| parent | b74ece344fe6d40e1040670a12be1dde3adb71b2 (diff) | |
| download | meson-3607f50d7f5bfa33806a4ed48d4fb773f35ee537.tar.gz | |
cmake: Refactor CMakeExecutor and CMakeTraceParser
This moves most of the execution code from the CMakeInterpreter
into CMakeExecutor. Also, CMakeTraceParser is now responsible
for determining the trace cmd arguments.
Diffstat (limited to 'mesonbuild/cmake/executor.py')
| -rw-r--r-- | mesonbuild/cmake/executor.py | 95 |
1 files changed, 84 insertions, 11 deletions
diff --git a/mesonbuild/cmake/executor.py b/mesonbuild/cmake/executor.py index bf4fa5d08..c3303ebd6 100644 --- a/mesonbuild/cmake/executor.py +++ b/mesonbuild/cmake/executor.py @@ -15,8 +15,9 @@ # This class contains the basic functionality needed to run any interpreter # or an interpreter-based tool. -import subprocess +import subprocess as S from pathlib import Path +from threading import Thread import typing as T import re import os @@ -30,19 +31,22 @@ from ..environment import Environment if T.TYPE_CHECKING: from ..dependencies.base import ExternalProgram +TYPE_result = T.Tuple[int, T.Optional[str], T.Optional[str]] class CMakeExecutor: # The class's copy of the CMake path. Avoids having to search for it # multiple times in the same Meson invocation. class_cmakebin = PerMachine(None, None) class_cmakevers = PerMachine(None, None) - class_cmake_cache = {} + class_cmake_cache = {} # type: T.Dict[T.Any, TYPE_result] def __init__(self, environment: Environment, version: str, for_machine: MachineChoice, silent: bool = False): self.min_version = version self.environment = environment self.for_machine = for_machine self.cmakebin, self.cmakevers = self.find_cmake_binary(self.environment, silent=silent) + self.always_capture_stderr = True + self.print_cmout = False if self.cmakebin is False: self.cmakebin = None return @@ -130,17 +134,77 @@ class CMakeExecutor: cmvers = re.sub(r'\s*cmake version\s*', '', out.split('\n')[0]).strip() return cmvers + def set_exec_mode(self, print_cmout: T.Optional[bool] = None, always_capture_stderr: T.Optional[bool] = None) -> None: + if print_cmout is not None: + self.print_cmout = print_cmout + if always_capture_stderr is not None: + self.always_capture_stderr = always_capture_stderr + def _cache_key(self, args: T.List[str], build_dir: str, env): fenv = frozenset(env.items()) if env is not None else None targs = tuple(args) return (self.cmakebin, targs, build_dir, fenv) - def _call_real(self, args: T.List[str], build_dir: str, env) -> T.Tuple[int, str, str]: + def _call_cmout_stderr(self, args: T.List[str], build_dir: str, env) -> TYPE_result: + cmd = self.cmakebin.get_command() + args + proc = S.Popen(cmd, stdout=S.PIPE, stderr=S.PIPE, cwd=build_dir, env=env) + + # stdout and stderr MUST be read at the same time to avoid pipe + # blocking issues. The easiest way to do this is with a separate + # thread for one of the pipes. + def print_stdout(): + while True: + line = proc.stdout.readline() + if not line: + break + mlog.log(line.decode(errors='ignore').strip('\n')) + proc.stdout.close() + + t = Thread(target=print_stdout) + t.start() + + try: + # Read stderr line by line and log non trace lines + raw_trace = '' + tline_start_reg = re.compile(r'^\s*(.*\.(cmake|txt))\(([0-9]+)\):\s*(\w+)\(.*$') + inside_multiline_trace = False + while True: + line = proc.stderr.readline() + if not line: + break + line = line.decode(errors='ignore') + if tline_start_reg.match(line): + raw_trace += line + inside_multiline_trace = not line.endswith(' )\n') + elif inside_multiline_trace: + raw_trace += line + else: + mlog.warning(line.strip('\n')) + + finally: + proc.stderr.close() + t.join() + proc.wait() + + return proc.returncode, None, raw_trace + + def _call_cmout(self, args: T.List[str], build_dir: str, env) -> TYPE_result: + cmd = self.cmakebin.get_command() + args + proc = S.Popen(cmd, stdout=S.PIPE, stderr=S.STDOUT, cwd=build_dir, env=env) + while True: + line = proc.stdout.readline() + if not line: + break + mlog.log(line.decode(errors='ignore').strip('\n')) + proc.stdout.close() + proc.wait() + return proc.returncode, None, None + + def _call_quiet(self, args: T.List[str], build_dir: str, env) -> TYPE_result: os.makedirs(build_dir, exist_ok=True) cmd = self.cmakebin.get_command() + args - ret = subprocess.run(cmd, env=env, cwd=build_dir, close_fds=False, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=False) + ret = S.run(cmd, env=env, cwd=build_dir, close_fds=False, + stdout=S.PIPE, stderr=S.PIPE, universal_newlines=False) rc = ret.returncode out = ret.stdout.decode(errors='ignore') err = ret.stderr.decode(errors='ignore') @@ -148,21 +212,30 @@ class CMakeExecutor: mlog.debug("Called `{}` in {} -> {}".format(call, build_dir, rc)) return rc, out, err - def call(self, args: T.List[str], build_dir: str, env=None, disable_cache: bool = False): + def _call_impl(self, args: T.List[str], build_dir: str, env) -> TYPE_result: + if not self.print_cmout: + return self._call_quiet(args, build_dir, env) + else: + if self.always_capture_stderr: + return self._call_cmout_stderr(args, build_dir, env) + else: + return self._call_cmout(args, build_dir, env) + + def call(self, args: T.List[str], build_dir: str, env=None, disable_cache: bool = False) -> TYPE_result: if env is None: env = os.environ if disable_cache: - return self._call_real(args, build_dir, env) + return self._call_impl(args, build_dir, env) # First check if cached, if not call the real cmake function cache = CMakeExecutor.class_cmake_cache key = self._cache_key(args, build_dir, env) if key not in cache: - cache[key] = self._call_real(args, build_dir, env) + cache[key] = self._call_impl(args, build_dir, env) return cache[key] - def call_with_fake_build(self, args: T.List[str], build_dir: str, env=None): + def call_with_fake_build(self, args: T.List[str], build_dir: str, env=None) -> TYPE_result: # First check the cache cache = CMakeExecutor.class_cmake_cache key = self._cache_key(args, build_dir, env) @@ -282,7 +355,7 @@ set(CMAKE_SIZEOF_VOID_P "{}") def executable_path(self) -> str: return self.cmakebin.get_path() - def get_command(self): + def get_command(self) -> T.List[str]: return self.cmakebin.get_command() def machine_choice(self) -> MachineChoice: |
