The Meson Build System
http://mesonbuild.com/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
138 lines
4.9 KiB
138 lines
4.9 KiB
# SPDX-License-Identifier: Apache-2.0 |
|
# Copyright 2018 The Meson development team |
|
|
|
from __future__ import annotations |
|
|
|
import asyncio.subprocess |
|
import fnmatch |
|
import itertools |
|
import json |
|
import signal |
|
import sys |
|
from pathlib import Path |
|
|
|
from .. import mlog |
|
from ..compilers import lang_suffixes |
|
from ..mesonlib import quiet_git, join_args, determine_worker_count |
|
from ..mtest import complete_all |
|
import typing as T |
|
|
|
Info = T.TypeVar("Info") |
|
|
|
async def run_with_buffered_output(cmdlist: T.List[str]) -> int: |
|
"""Run the command in cmdlist, buffering the output so that it is |
|
not mixed for multiple child processes. Kill the child on |
|
cancellation.""" |
|
quoted_cmdline = join_args(cmdlist) |
|
p: T.Optional[asyncio.subprocess.Process] = None |
|
try: |
|
p = await asyncio.create_subprocess_exec(*cmdlist, |
|
stdin=asyncio.subprocess.DEVNULL, |
|
stdout=asyncio.subprocess.PIPE, |
|
stderr=asyncio.subprocess.STDOUT) |
|
stdo, _ = await p.communicate() |
|
except FileNotFoundError as e: |
|
print(mlog.blue('>>>'), quoted_cmdline, file=sys.stderr) |
|
print(mlog.red('not found:'), e.filename, file=sys.stderr) |
|
return 1 |
|
except asyncio.CancelledError: |
|
if p: |
|
p.kill() |
|
await p.wait() |
|
return p.returncode or 1 |
|
else: |
|
return 0 |
|
|
|
if stdo: |
|
print(mlog.blue('>>>'), quoted_cmdline, flush=True) |
|
sys.stdout.buffer.write(stdo) |
|
return p.returncode |
|
|
|
async def _run_workers(infos: T.Iterable[Info], |
|
fn: T.Callable[[Info], T.Iterable[T.Coroutine[None, None, int]]]) -> int: |
|
futures: T.List[asyncio.Future[int]] = [] |
|
semaphore = asyncio.Semaphore(determine_worker_count()) |
|
|
|
async def run_one(worker_coro: T.Coroutine[None, None, int]) -> int: |
|
try: |
|
async with semaphore: |
|
return await worker_coro |
|
except asyncio.CancelledError as e: |
|
worker_coro.throw(e) |
|
return await worker_coro |
|
|
|
def sigterm_handler() -> None: |
|
for f in futures: |
|
f.cancel() |
|
|
|
if sys.platform != 'win32': |
|
loop = asyncio.get_running_loop() |
|
loop.add_signal_handler(signal.SIGINT, sigterm_handler) |
|
loop.add_signal_handler(signal.SIGTERM, sigterm_handler) |
|
|
|
for i in infos: |
|
futures.extend((asyncio.ensure_future(run_one(x)) for x in fn(i))) |
|
if not futures: |
|
return 0 |
|
|
|
try: |
|
await complete_all(futures) |
|
except BaseException: |
|
for f in futures: |
|
f.cancel() |
|
raise |
|
|
|
return max(f.result() for f in futures if f.done() and not f.cancelled()) |
|
|
|
def parse_pattern_file(fname: Path) -> T.List[str]: |
|
patterns = [] |
|
try: |
|
with fname.open(encoding='utf-8') as f: |
|
for line in f: |
|
pattern = line.strip() |
|
if pattern and not pattern.startswith('#'): |
|
patterns.append(pattern) |
|
except FileNotFoundError: |
|
pass |
|
return patterns |
|
|
|
def all_clike_files(name: str, srcdir: Path, builddir: Path) -> T.Iterable[Path]: |
|
patterns = parse_pattern_file(srcdir / f'.{name}-include') |
|
globs: T.Union[T.List[T.List[Path]], T.List[T.Generator[Path, None, None]]] |
|
if patterns: |
|
globs = [srcdir.glob(p) for p in patterns] |
|
else: |
|
r, o = quiet_git(['ls-files'], srcdir) |
|
if r: |
|
globs = [[Path(srcdir, f) for f in o.splitlines()]] |
|
else: |
|
globs = [srcdir.glob('**/*')] |
|
patterns = parse_pattern_file(srcdir / f'.{name}-ignore') |
|
ignore = [str(builddir / '*')] |
|
ignore.extend([str(srcdir / p) for p in patterns]) |
|
suffixes = set(lang_suffixes['c']).union(set(lang_suffixes['cpp'])) |
|
suffixes.add('h') |
|
suffixes = {f'.{s}' for s in suffixes} |
|
for f in itertools.chain.from_iterable(globs): |
|
strf = str(f) |
|
if f.is_dir() or f.suffix not in suffixes or \ |
|
any(fnmatch.fnmatch(strf, i) for i in ignore): |
|
continue |
|
yield f |
|
|
|
def run_clang_tool(name: str, srcdir: Path, builddir: Path, fn: T.Callable[..., T.Coroutine[None, None, int]], *args: T.Any) -> int: |
|
if sys.platform == 'win32': |
|
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) |
|
|
|
def wrapper(path: Path) -> T.Iterable[T.Coroutine[None, None, int]]: |
|
yield fn(path, *args) |
|
return asyncio.run(_run_workers(all_clike_files(name, srcdir, builddir), wrapper)) |
|
|
|
def run_tool_on_targets(fn: T.Callable[[T.Dict[str, T.Any]], |
|
T.Iterable[T.Coroutine[None, None, int]]]) -> int: |
|
if sys.platform == 'win32': |
|
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) |
|
|
|
with open('meson-info/intro-targets.json', encoding='utf-8') as fp: |
|
targets = json.load(fp) |
|
return asyncio.run(_run_workers(targets, fn))
|
|
|