| #!/usr/bin/env python3 |
| # Copyright 2026 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """CodeCoverageInstaller: Verifies and installs code coverage dependencies. |
| |
| Automates the verification of: |
| 1. Chromium checkout. |
| 2. LLVM coverage tools (llvm-cov, llvm-profdata). |
| 3. Recipes code (chromium/tools/build). |
| 4. Service code (infra/infra). |
| """ |
| |
| import argparse |
| import os |
| import pathlib |
| import subprocess |
| import sys |
| import textwrap |
| |
| SRC_ROOT = pathlib.Path(__file__).resolve().parents[2] |
| |
| |
| def print_indented(text, level=1, file=None): |
| """Prints multi-line text with indentation. |
| |
| Args: |
| text: The string message to print. |
| level: Number of indentation levels (2 spaces per level). |
| file: Optional output file stream (defaults to sys.stdout). |
| """ |
| if file is None: |
| file = sys.stdout |
| indent = ' ' * level |
| for line in text.splitlines(): |
| print(f"{indent}{line}", file=file) |
| |
| |
| def run_command(args, cwd=None): |
| """Executes a subprocess command and prints captured output. |
| |
| Args: |
| args: Command argument list to run. |
| cwd: Optional working directory path for execution. |
| |
| Returns: |
| The integer return code of the subprocess execution. |
| """ |
| result = subprocess.run( |
| args, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT, |
| text=True, |
| cwd=cwd, |
| check=False, |
| ) |
| if result.stdout: |
| print_indented(result.stdout, level=2) |
| return result.returncode |
| |
| |
| def verify_chromium_checkout(src_root): |
| """Confirms that a valid Chromium checkout exists on disk. |
| |
| Args: |
| src_root: Path to the Chromium source repository root directory. |
| |
| Returns: |
| True if DEPS file is found indicating a valid checkout, False otherwise. |
| """ |
| print('Verifying Chromium checkout... ', end='', flush=True) |
| try: |
| deps_path = src_root / 'DEPS' |
| if deps_path.exists(): |
| print('OK') |
| return True |
| print('FAIL') |
| print_indented( |
| 'Error: Could not find DEPS file. Not a valid Chromium checkout.', |
| file=sys.stderr) |
| except Exception as e: |
| print('FAIL') |
| print_indented(f'Error verifying checkout: {e}', file=sys.stderr) |
| return False |
| |
| |
| def verify_llvm_tools(src_root): |
| """Confirms that LLVM coverage binaries are installed. |
| |
| Args: |
| src_root: Path to the Chromium source repository root directory. |
| |
| Returns: |
| True if llvm-cov and llvm-profdata binaries exist, False otherwise. |
| """ |
| print('Verifying LLVM coverage tools... ', end='', flush=True) |
| try: |
| llvm_bin_dir = (src_root / 'third_party' / 'llvm-build' / |
| 'Release+Asserts' / 'bin') |
| llvm_cov = llvm_bin_dir / 'llvm-cov' |
| llvm_profdata = llvm_bin_dir / 'llvm-profdata' |
| |
| if llvm_cov.exists() and llvm_profdata.exists(): |
| print('OK') |
| return True |
| |
| print('FAIL') |
| print_indented(textwrap.dedent("""\ |
| Error: LLVM coverage tools (llvm-cov, llvm-profdata) are missing. |
| To install them, add the following to your .gclient configuration: |
| "custom_vars": { |
| "checkout_clang_coverage_tools": True, |
| } |
| And then run: gclient runhooks"""), |
| file=sys.stderr) |
| except Exception as e: |
| print('FAIL') |
| print_indented(f'Error verifying LLVM tools: {e}', file=sys.stderr) |
| return False |
| |
| |
| def verify_recipes(infra_dir): |
| """Confirms that the code coverage recipes codebase exists. |
| |
| Args: |
| infra_dir: Path to the root chrome infra directory. |
| |
| Returns: |
| True if the code_coverage recipe module directory exists, False otherwise. |
| """ |
| print('Verifying code coverage recipe code... ', end='', flush=True) |
| recipe_module_path = (infra_dir / 'build' / 'recipes' / 'recipe_modules' / |
| 'code_coverage') |
| if recipe_module_path.exists(): |
| print('OK') |
| return True |
| print('FAIL') |
| return False |
| |
| |
| def verify_service(infra_dir): |
| """Confirms that the code coverage FindIt service codebase exists. |
| |
| Args: |
| infra_dir: Path to the root chrome infra directory. |
| |
| Returns: |
| True if the findit service directory exists, False otherwise. |
| """ |
| print('Verifying code coverage service code... ', end='', flush=True) |
| service_path = infra_dir / 'infra' / 'appengine' / 'findit' |
| if service_path.exists(): |
| print('OK') |
| return True |
| print('FAIL') |
| return False |
| |
| |
| def setup_infra(infra_dir): |
| """Sets up or syncs the infra_superproject repository checkout. |
| |
| Args: |
| infra_dir: Target directory path where chrome infra is checked out. |
| |
| Returns: |
| True if gclient sync or fetch completed successfully, False otherwise. |
| """ |
| print_indented(f'Setting up infra_superproject in {infra_dir}...') |
| if not infra_dir.exists(): |
| infra_dir.mkdir(parents=True) |
| |
| gclient_file = infra_dir / '.gclient' |
| if gclient_file.exists(): |
| print_indented( |
| 'Existing gclient configuration found. Running gclient sync...') |
| code = run_command(['gclient', 'sync'], cwd=infra_dir) |
| if code == 0: |
| print_indented('gclient sync completed successfully.') |
| return True |
| print_indented(f'Error: gclient sync failed with exit code {code}.', |
| file=sys.stderr) |
| else: |
| print_indented('Running fetch infra_superproject...') |
| code = run_command(['fetch', 'infra_superproject'], cwd=infra_dir) |
| if code == 0: |
| print_indented('fetch infra_superproject completed successfully.') |
| return True |
| print_indented( |
| f'Error: fetch infra_superproject failed with exit code {code}.', |
| file=sys.stderr) |
| return False |
| |
| |
| def verify_gemini_md(src_root): |
| """Confirms global code coverage prompt template is imported in GEMINI.md. |
| |
| Args: |
| src_root: Path to the Chromium source repository root directory. |
| |
| Returns: |
| True if GEMINI.md was successfully verified or updated, False on error. |
| """ |
| print( |
| 'Verifying global coverage agent rules in GEMINI.md... ', |
| end='', |
| flush=True, |
| ) |
| gemini_md = src_root / 'GEMINI.md' |
| target_line = '@agents/prompts/templates/code_coverage.md' |
| try: |
| if gemini_md.exists(): |
| content = gemini_md.read_text(encoding='utf-8') |
| if target_line in content: |
| print('OK') |
| return True |
| with gemini_md.open('a', encoding='utf-8') as f: |
| if not content.endswith('\n') and content: |
| f.write('\n') |
| f.write(f'{target_line}\n') |
| print('OK (Appended)') |
| return True |
| gemini_md.write_text(f'{target_line}\n', encoding='utf-8') |
| print('OK (Created)') |
| return True |
| except Exception as e: |
| print('FAIL') |
| print_indented(f'Error updating GEMINI.md: {e}', file=sys.stderr) |
| return False |
| |
| |
| def main(): |
| """Parses command line arguments and runs all verification steps.""" |
| parser = argparse.ArgumentParser( |
| description= |
| 'Verifies and initializes Code Coverage development environment.') |
| parser.add_argument( |
| '--infra-dir', |
| required=True, |
| type=pathlib.Path, |
| help='Path to the directory containing (or to checkout) chrome infra ' |
| 'repositories.') |
| args = parser.parse_args() |
| infra_dir = args.infra_dir.resolve() |
| |
| if not verify_chromium_checkout(SRC_ROOT): |
| sys.exit(1) |
| |
| if not verify_llvm_tools(SRC_ROOT): |
| sys.exit(1) |
| |
| recipes_ok = verify_recipes(infra_dir) |
| service_ok = verify_service(infra_dir) |
| |
| if not (recipes_ok and service_ok): |
| if not setup_infra(infra_dir): |
| sys.exit(1) |
| # Re-verify |
| if not verify_recipes(infra_dir) or not verify_service(infra_dir): |
| print_indented('Error: Verification failed after setup.', file=sys.stderr) |
| sys.exit(1) |
| |
| if not verify_gemini_md(SRC_ROOT): |
| print_indented( |
| 'Error: Could not verify or initialize GEMINI.md configuration.', |
| file=sys.stderr) |
| sys.exit(1) |
| |
| print('\nCode Coverage environment successfully verified and initialized') |
| |
| |
| if __name__ == '__main__': |
| main() |