#!/usr/bin/env python3 # Copyright 2022 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file.
import argparse import dataclasses import functools import json import logging import multiprocessing import os import pathlib import subprocess import sys from typing import List, Optional, Set
def _search(args: argparse.Namespace, build_filepaths: List[str],
root: pathlib.Path):
name_query = args.name
path_query = args.path if name_query:
logging.info(f'Searching dep names using: {name_query}') if path_query:
logging.info(f'Searching paths using: {path_query}') with multiprocessing.Pool() as pool:
pool.map(
functools.partial(_search_deps, name_query, path_query, root),
build_filepaths,
)
def _split(args: argparse.Namespace, build_filepaths: List[str],
root: pathlib.Path) -> List[OperationResult]:
num_total = len(build_filepaths)
results = [] with multiprocessing.Pool() as pool:
tasks = {
filepath: pool.apply_async(
_split_deps,
(args.existing, args.new, root, filepath, args.dryrun)) for filepath in build_filepaths
} for idx, filepath in enumerate(tasks.keys()):
relpath = os.path.relpath(filepath, start=root)
logging.info('[%d/%d] Checking %s', idx, num_total, relpath)
operation_result = tasks[filepath].get() if operation_result:
logging.info(operation_result)
results.append(operation_result) return results
def _get_project_json_contents(out_dir: str) -> str:
project_json_path = os.path.join(out_dir, 'project.json') with open(project_json_path) as f: return f.read()
def _calculate_targets_for_file(relpath: str, arg_extra_targets: List[str],
all_targets: Set[str]) -> Optional[List[str]]: if os.path.basename(relpath) != 'BUILD.gn': # Build all targets when we are dealing with build files that might be # imported by other build files (e.g. config.gni or other_name.gn). return []
dirpath = os.path.dirname(relpath)
file_extra_targets = [] for full_target_name in all_targets:
target_dir, short_target_name = full_target_name.split(':', 1) # __ is used for sub-targets in GN, only focus on top-level ones. Also # skip targets using other toolchains, e.g. # base:feature_list_buildflags(//build/toolchain/linux:clang_x64) if (target_dir == dirpath and'__'notin short_target_name and'('notin short_target_name):
file_extra_targets.append(full_target_name)
targets = arg_extra_targets + file_extra_targets return targets orNone
if args.output_directory:
constants.SetOutputDirectory(args.output_directory)
constants.CheckOutputDirectory()
out_dir: str = constants.GetOutDirectory()
args_gn_path = os.path.join(out_dir, 'args.gn') ifnot os.path.exists(args_gn_path): raise Exception(f'No args.gn in out directory {out_dir}') with open(args_gn_path) as f: # Although the target may compile fine, bytecode checks are necessary # for correctness at runtime. assert'android_static_analysis = "on"'in f.read(), ( 'Static analysis must be on to ensure correctness.') # TODO: Ensure that the build server is not running.
if args.all_java_deps: assertnot args.dep, '--all-java-target does not support passing deps.' assert args.file, '--all-java-target requires passing --file.'
logging.info(f'Finding java deps under {out_dir}.')
all_java_deps = build_utils.CheckOutput([
str(_SRC_PATH / 'build' / 'android' / 'list_java_targets.py'), '--gn-labels', '-C', out_dir
]).split('\n')
logging.info(f'Found {len(all_java_deps)} java deps.')
args.dep += all_java_deps else: assert args.dep, 'At least one explicit dep is required.'
project_json_contents = _get_project_json_contents(out_dir)
project_json = json.loads(project_json_contents) # The input file names have a // prefix. (e.g. //android_webview/BUILD.gn)
known_build_files = set(
name[2:] for name in project_json['build_settings']['gen_input_files']) # Remove the // prefix for target names so ninja can build them.
known_target_names = set(name[2:] for name in project_json['targets'].keys())
unknown_targets = [
t for t in args.extra_build_targets if t notin known_target_names
] assertnot unknown_targets, f'Cannot build {unknown_targets} in {out_dir}.'
logging.info('Building all targets in preparation for removing deps') # Avoid capturing stdout/stderr to see the progress of the full build.
subprocess.run(['autoninja', '-C', out_dir], check=True)
results = [] for idx, filepath in enumerate(build_filepaths): # Since removal can take a long time, provide an easy way to resume the # command if something fails. try: # When resuming, the first build file is the one that is being # resumed. Avoid inline mode skipping it since it's already started # to be processed and the first dep may already have been removed. if args.resume_from and idx == 0and args.inline_mode:
logging.info(f'Resuming: skipping inline mode for {filepath}.')
should_inline = False else:
should_inline = args.inline_mode
relpath = os.path.relpath(filepath, start=root)
logging.info('[%d/%d] Checking %s', idx, num_total, relpath) if relpath notin known_build_files:
operation_result = OperationResult(
path=relpath,
skipped=True,
skip_reason='Not in the list of known build files.') else:
targets = _calculate_targets_for_file(relpath,
args.extra_build_targets,
known_target_names) if targets isNone:
operation_result = OperationResult(
path=relpath,
skipped=True,
skip_reason='Could not find any valid targets.') else:
operation_result = _remove_deps(
deps=args.dep,
out_dir=out_dir,
root=root,
path=filepath,
dryrun=args.dryrun,
targets=targets,
inline_mode=should_inline,
target_name_filter=args.target_name_filter) if operation_result:
logging.info(operation_result)
results.append(operation_result) # Use blank except: to show this for KeyboardInterrupt as well. except:
logging.error(
f'Encountered error while processing {filepath}. Append the ' 'following args to resume from this file once the error is '
f'fixed:\n\n--resume-from {filepath}\n') raise return results
common_args_parser = argparse.ArgumentParser(add_help=False)
common_args_parser.add_argument( '-n', '--dryrun',
action='store_true',
help='Show which files would be updated but avoid changing them.')
common_args_parser.add_argument('-v', '--verbose',
action='store_true',
help='Used to print ninjalog.')
common_args_parser.add_argument('-q', '--quiet',
action='store_true',
help='Used to print less logging.')
common_args_parser.add_argument('--file',
help='Run on a specific build file.')
common_args_parser.add_argument( '--resume-from',
help='Skip files before this build file path (debugging).')
subparsers = parser.add_subparsers(
required=True, help='Use subcommand -h to see full usage.')
add_parser = subparsers.add_parser( 'add',
parents=[common_args_parser],
help='Add one or more deps to a specific target (pass the path to the ' 'BUILD.gn via --file for faster results). The target **must** ' 'have a deps variable defined, even if it is an empty [].')
add_parser.add_argument('--target', help='The name of the target.')
add_parser.add_argument('--deps',
nargs='+',
help='The name(s) of the new dep(s).')
add_parser.set_defaults(command=_add)
search_parser = subparsers.add_parser( 'search',
parents=[common_args_parser],
help='Search for strings in build files. Each query is a regex string.'
)
search_parser.add_argument('--name',
help='This is checked against dep names.')
search_parser.add_argument( '--path', help='This checks the relative path of the build file.')
search_parser.set_defaults(command=_search)
split_parser = subparsers.add_parser( 'split',
parents=[common_args_parser],
help='Split one or more deps from an existing dep.')
split_parser.add_argument('existing', help='The dep to split from.')
split_parser.add_argument('new',
nargs='+',
help='One of the new deps to be added.')
split_parser.set_defaults(command=_split)
remove_parser = subparsers.add_parser( 'remove',
parents=[common_args_parser],
help='Remove one or more deps if the build still succeeds. Removing ' 'one dep at a time is recommended.')
remove_parser.add_argument( 'dep',
nargs='*',
help='One or more deps to be removed. Zero when other options are used.'
)
remove_parser.add_argument( '-C', '--output-directory',
metavar='OUT',
help='If outdir is not provided, will attempt to guess.')
remove_parser.add_argument( '--target-name-filter',
help='This will cause the script to only remove deps from targets that ' 'match the filter provided. The filter should be a valid python regex ' 'string and is used in a re.search on the full GN target names, e.g. ' 're.search(pattern, "//base:base_java").')
remove_parser.add_argument( '--all-java-deps',
action='store_true',
help='This will attempt to remove all known java deps. This option ' 'requires no explicit deps to be passed.')
remove_parser.add_argument( '--extra-build-targets',
metavar='T',
nargs='*',
default=[],
help='The set of extra targets to compile after each dep removal. This ' 'is in addition to file-based targets that are automatically added.')
remove_parser.add_argument( '--inline-mode',
action='store_true',
help='Skip the build file if the first dep is not found and removed. ' 'This is especially useful when inlining deps so that a build file ' 'that does not contain the dep being inlined can be skipped. This ' 'mode assumes that the first dep is the one being inlined.')
remove_parser.set_defaults(command=_remove)
if args.resume_from:
resume_idx = None for idx, path in enumerate(build_filepaths): if path.endswith(args.resume_from):
resume_idx = idx break assert resume_idx isnotNone, f'Did not find {args.resume_from}.'
logging.info('Skipping %d build files with --resume-from.', resume_idx)
build_filepaths = build_filepaths[resume_idx:]
filtered_build_filepaths = [
p for p in build_filepaths ifnot utils.is_bad_gn_file(p, root)
]
num_total = len(filtered_build_filepaths) if num_total == 0:
logging.error(NO_VALID_GN_STR)
sys.exit(1)
logging.info('Running on %d valid build files.', num_total)
operation_results: List[OperationResult] = args.command(
args, filtered_build_filepaths, root) if operation_results isNone: return
ignored_operation_results = [r for r in operation_results if r.git_ignored]
skipped_operation_results = [r for r in operation_results if r.skipped]
num_ignored = len(ignored_operation_results)
num_skipped = len(skipped_operation_results)
num_updated = len(operation_results) - num_skipped
print(f'Checked {num_total}, updated {num_updated} ({num_ignored} of '
f'which are ignored by git under {root}), and skipped {num_skipped} ' 'build files.') if num_ignored:
print(f'\nThe following {num_ignored} files were ignored by git and ' 'may need separate CLs in their respective repositories:') for result in ignored_operation_results:
print(' ' + result.path)
if __name__ == '__main__':
main()
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-08-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.