1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
build / gn_ast / jni_refactor.py [blame]
# 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()