1# Copyright 2023 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4"""Refactors BUILD.gn files for our Annotation Processor -> .srcjar migration. 5 61) Finds all generate_jni() targets 72) Finds all android_library() targets with that use ":jni_processor" 83) Compares lists of sources between them 94) Removes the annotation_processor_deps entry 105) Adds the generate_jni target as a srcjar_dep 116) Updates visibility of generate_jni to allow the dep 12 13This script has already done its job, but is left as an example of using gn_ast. 14""" 15 16import argparse 17import sys 18 19import gn_ast 20 21_PROCESSOR_DEP = '//base/android/jni_generator:jni_processor' 22 23 24class RefactorException(Exception): 25 pass 26 27 28def find_processor_assignment(target): 29 for assignment in target.block.find_assignments( 30 'annotation_processor_deps'): 31 processors = assignment.list_value.literals 32 if _PROCESSOR_DEP in processors: 33 return assignment 34 return None 35 36 37def find_all_sources(target, build_file): 38 ret = [] 39 40 def helper(assignments): 41 for assignment in assignments: 42 if assignment.operation not in ('=', '+='): 43 raise RefactorException( 44 f'{target.name}: sources has a {assignment.operation}.') 45 46 value = assignment.value 47 if value.is_identifier(): 48 helper(build_file.block.find_assignments(value.node_value)) 49 elif not value.is_list(): 50 raise RefactorException(f'{target.name}: sources not a list.') 51 else: 52 ret.extend(value.literals) 53 54 helper(target.block.find_assignments('sources')) 55 return ret 56 57 58def find_matching_jni_target(library_target, jni_target_to_sources, 59 build_file): 60 all_sources = set(find_all_sources(library_target, build_file)) 61 matches = [] 62 for jni_target_name, jni_sources in jni_target_to_sources.items(): 63 if all(s in all_sources for s in jni_sources): 64 matches.append(jni_target_name) 65 if len(matches) == 1: 66 return matches[0] 67 if len(matches) > 1: 68 raise RefactorException( 69 f'{library_target.name}: Matched multiple generate_jni().') 70 if jni_target_to_sources: 71 raise RefactorException( 72 f'{library_target.name}: No matching generate_jni().') 73 raise RefactorException('No sources found for generate_jni().') 74 75 76def fix_visibility(target): 77 for assignment in target.block.find_assignments('visibility'): 78 if not assignment.value.is_list(): 79 continue 80 list_value = assignment.list_value 81 for value in list(list_value.literals): 82 if value.startswith(':'): 83 list_value.remove_literal(value) 84 list_value.add_literal(':*') 85 86 87def refactor(lib_target, jni_target): 88 assignments = lib_target.block.find_assignments('srcjar_deps') 89 srcjar_deps = assignments[0] if assignments else None 90 if srcjar_deps is None: 91 srcjar_deps = gn_ast.AssignmentWrapper.create_list('srcjar_deps') 92 first_source_assignment = lib_target.block.find_assignments( 93 'sources')[0] 94 lib_target.block.add_child(srcjar_deps, before=first_source_assignment) 95 elif not srcjar_deps.value.is_list(): 96 raise RefactorException( 97 f'{lib_target.name}: srcjar_deps is not a list.') 98 srcjar_deps.list_value.add_literal(f':{jni_target.name}') 99 100 processor_assignment = find_processor_assignment(lib_target) 101 processors = processor_assignment.list_value.literals 102 if len(processors) == 1: 103 lib_target.block.remove_child(processor_assignment.node) 104 else: 105 processor_assignment.list_value.remove_literal(_PROCESSOR_DEP) 106 107 fix_visibility(jni_target) 108 109 110def analyze(build_file): 111 targets = build_file.targets 112 jni_targets = [t for t in targets if t.type == 'generate_jni'] 113 lib_targets = [t for t in targets if find_processor_assignment(t)] 114 115 if len(jni_targets) == 0 and len(lib_targets) == 0: 116 return 117 # Match up target when there are only one, even when targets use variables 118 # for list values. 119 if len(jni_targets) == 1 and len(lib_targets) == 1: 120 refactor(lib_targets[0], jni_targets[0]) 121 return 122 123 jni_target_to_sources = { 124 t.name: find_all_sources(t, build_file) 125 for t in jni_targets 126 } 127 for lib_target in lib_targets: 128 jni_target_name = find_matching_jni_target(lib_target, 129 jni_target_to_sources, 130 build_file) 131 jni_target = build_file.targets_by_name[jni_target_name] 132 refactor(lib_target, jni_target) 133 134 135def main(): 136 parser = argparse.ArgumentParser() 137 parser.add_argument('path') 138 args = parser.parse_args() 139 try: 140 build_file = gn_ast.BuildFile.from_file(args.path) 141 analyze(build_file) 142 if build_file.write_changes(): 143 print(f'{args.path}: Changes applied.') 144 else: 145 print(f'{args.path}: No changes necessary.') 146 except RefactorException as e: 147 print(f'{args.path}: {e}') 148 sys.exit(1) 149 except Exception: 150 print('Failure on', args.path) 151 raise 152 153 154if __name__ == '__main__': 155 main() 156