blob: 2389522fe5b1da3aa641852db26929ed3ea9203d [file]
#!/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()