• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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