| # SPDX-License-Identifier: Apache-2.0 |
| # Copyright 2024 The Meson development team |
| |
| from __future__ import annotations |
| |
| import sys, os, subprocess, shutil |
| import pathlib |
| import typing as T |
| |
| if T.TYPE_CHECKING: |
| import argparse |
| |
| from ..mesonlib import get_meson_command |
| |
| # Note: when adding arguments, please also add them to the completion |
| # scripts in $MESONSRC/data/shell-completions/ |
| def add_arguments(parser: 'argparse.ArgumentParser') -> None: |
| parser.add_argument('--intermediaries', |
| default=False, |
| action='store_true', |
| help='Check intermediate files.') |
| parser.add_argument('mesonargs', nargs='*', |
| help='Arguments to pass to "meson setup".') |
| |
| IGNORE_PATTERNS = ('.ninja_log', |
| '.ninja_deps', |
| 'meson-private', |
| 'meson-logs', |
| 'meson-info', |
| ) |
| |
| INTERMEDIATE_EXTENSIONS = ('.gch', |
| '.pch', |
| '.o', |
| '.obj', |
| '.class', |
| ) |
| |
| class ReproTester: |
| def __init__(self, options: T.Any): |
| self.args = options.mesonargs |
| self.meson = get_meson_command()[:] |
| self.builddir = pathlib.Path('buildrepro') |
| self.storagedir = pathlib.Path('buildrepro.1st') |
| self.issues: T.List[str] = [] |
| self.check_intermediaries = options.intermediaries |
| |
| def run(self) -> int: |
| if not os.path.isfile('meson.build'): |
| sys.exit('This command needs to be run at your project source root.') |
| self.disable_ccache() |
| self.cleanup() |
| self.build() |
| self.check_output() |
| self.print_results() |
| if not self.issues: |
| self.cleanup() |
| return len(self.issues) |
| |
| def disable_ccache(self) -> None: |
| os.environ['CCACHE_DISABLE'] = '1' |
| |
| def cleanup(self) -> None: |
| if self.builddir.exists(): |
| shutil.rmtree(self.builddir) |
| if self.storagedir.exists(): |
| shutil.rmtree(self.storagedir) |
| |
| def build(self) -> None: |
| setup_command: T.Sequence[str] = self.meson + ['setup', str(self.builddir)] + self.args |
| build_command: T.Sequence[str] = self.meson + ['compile', '-C', str(self.builddir)] |
| subprocess.check_call(setup_command) |
| subprocess.check_call(build_command) |
| self.builddir.rename(self.storagedir) |
| subprocess.check_call(setup_command) |
| subprocess.check_call(build_command) |
| |
| def ignore_file(self, fstr: str) -> bool: |
| for p in IGNORE_PATTERNS: |
| if p in fstr: |
| return True |
| if not self.check_intermediaries: |
| if fstr.endswith(INTERMEDIATE_EXTENSIONS): |
| return True |
| return False |
| |
| def check_contents(self, fromdir: str, todir: str, check_contents: bool) -> None: |
| import filecmp |
| frompath = fromdir + '/' |
| topath = todir + '/' |
| for fromfile in pathlib.Path(fromdir).glob('**/*'): |
| if not fromfile.is_file(): |
| continue |
| fstr = fromfile.as_posix() |
| if self.ignore_file(fstr): |
| continue |
| assert fstr.startswith(frompath) |
| tofile = pathlib.Path(fstr.replace(frompath, topath, 1)) |
| if not tofile.exists(): |
| self.issues.append(f'Missing file: {tofile}') |
| elif check_contents: |
| if not filecmp.cmp(fromfile, tofile, shallow=False): |
| self.issues.append(f'File contents differ: {fromfile}') |
| |
| def print_results(self) -> None: |
| if self.issues: |
| print('Build differences detected') |
| for i in self.issues: |
| print(i) |
| else: |
| print('No differences detected.') |
| |
| def check_output(self) -> None: |
| self.check_contents('buildrepro', 'buildrepro.1st', True) |
| self.check_contents('buildrepro.1st', 'buildrepro', False) |
| |
| def run(options: T.Any) -> None: |
| rt = ReproTester(options) |
| try: |
| sys.exit(rt.run()) |
| except Exception as e: |
| print(e) |
| sys.exit(1) |