From 3607f50d7f5bfa33806a4ed48d4fb773f35ee537 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Wed, 8 Jan 2020 17:06:58 +0100 Subject: 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. --- mesonbuild/cmake/executor.py | 95 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 11 deletions(-) (limited to 'mesonbuild/cmake/executor.py') 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: -- cgit v1.2.3