# Copyright 2023 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Refactors BUILD.gn files for our Annotation Processor -> .srcjar migration. 1) Finds all generate_jni() targets 2) Finds all android_library() targets with that use ":jni_processor" 3) Compares lists of sources between them 4) Removes the annotation_processor_deps entry 5) Adds the generate_jni target as a srcjar_dep 6) Updates visibility of generate_jni to allow the dep This script has already done its job, but is left as an example of using gn_ast. """ import argparse import sys import gn_ast _PROCESSOR_DEP = '//base/android/jni_generator:jni_processor' class RefactorException(Exception): pass def find_processor_assignment(target): for assignment in target.block.find_assignments( 'annotation_processor_deps'): processors = assignment.list_value.literals if _PROCESSOR_DEP in processors: return assignment return None def find_all_sources(target, build_file): ret = [] def helper(assignments): for assignment in assignments: if assignment.operation not in ('=', '+='): raise RefactorException( f'{target.name}: sources has a {assignment.operation}.') value = assignment.value if value.is_identifier(): helper(build_file.block.find_assignments(value.node_value)) elif not value.is_list(): raise RefactorException(f'{target.name}: sources not a list.') else: ret.extend(value.literals) helper(target.block.find_assignments('sources')) return ret def find_matching_jni_target(library_target, jni_target_to_sources, build_file): all_sources = set(find_all_sources(library_target, build_file)) matches = [] for jni_target_name, jni_sources in jni_target_to_sources.items(): if all(s in all_sources for s in jni_sources): matches.append(jni_target_name) if len(matches) == 1: return matches[0] if len(matches) > 1: raise RefactorException( f'{library_target.name}: Matched multiple generate_jni().') if jni_target_to_sources: raise RefactorException( f'{library_target.name}: No matching generate_jni().') raise RefactorException('No sources found for generate_jni().') def fix_visibility(target): for assignment in target.block.find_assignments('visibility'): if not assignment.value.is_list(): continue list_value = assignment.list_value for value in list(list_value.literals): if value.startswith(':'): list_value.remove_literal(value) list_value.add_literal(':*') def refactor(lib_target, jni_target): assignments = lib_target.block.find_assignments('srcjar_deps') srcjar_deps = assignments[0] if assignments else None if srcjar_deps is None: srcjar_deps = gn_ast.AssignmentWrapper.create_list('srcjar_deps') first_source_assignment = lib_target.block.find_assignments( 'sources')[0] lib_target.block.add_child(srcjar_deps, before=first_source_assignment) elif not srcjar_deps.value.is_list(): raise RefactorException( f'{lib_target.name}: srcjar_deps is not a list.') srcjar_deps.list_value.add_literal(f':{jni_target.name}') processor_assignment = find_processor_assignment(lib_target) processors = processor_assignment.list_value.literals if len(processors) == 1: lib_target.block.remove_child(processor_assignment.node) else: processor_assignment.list_value.remove_literal(_PROCESSOR_DEP) fix_visibility(jni_target) def analyze(build_file): targets = build_file.targets jni_targets = [t for t in targets if t.type == 'generate_jni'] lib_targets = [t for t in targets if find_processor_assignment(t)] if len(jni_targets) == 0 and len(lib_targets) == 0: return # Match up target when there are only one, even when targets use variables # for list values. if len(jni_targets) == 1 and len(lib_targets) == 1: refactor(lib_targets[0], jni_targets[0]) return jni_target_to_sources = { t.name: find_all_sources(t, build_file) for t in jni_targets } for lib_target in lib_targets: jni_target_name = find_matching_jni_target(lib_target, jni_target_to_sources, build_file) jni_target = build_file.targets_by_name[jni_target_name] refactor(lib_target, jni_target) def main(): parser = argparse.ArgumentParser() parser.add_argument('path') args = parser.parse_args() try: build_file = gn_ast.BuildFile.from_file(args.path) analyze(build_file) if build_file.write_changes(): print(f'{args.path}: Changes applied.') else: print(f'{args.path}: No changes necessary.') except RefactorException as e: print(f'{args.path}: {e}') sys.exit(1) except Exception: print('Failure on', args.path) raise if __name__ == '__main__': main()