tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

jni_refactor.py (5352B)


      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 
      6 1) Finds all generate_jni() targets
      7 2) Finds all android_library() targets with that use ":jni_processor"
      8 3) Compares lists of sources between them
      9 4) Removes the annotation_processor_deps entry
     10 5) Adds the generate_jni target as a srcjar_dep
     11 6) Updates visibility of generate_jni to allow the dep
     12 
     13 This script has already done its job, but is left as an example of using gn_ast.
     14 """
     15 
     16 import argparse
     17 import sys
     18 
     19 import gn_ast
     20 
     21 _PROCESSOR_DEP = '//base/android/jni_generator:jni_processor'
     22 
     23 
     24 class RefactorException(Exception):
     25    pass
     26 
     27 
     28 def 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 
     37 def 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 
     58 def 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 
     76 def 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 
     87 def 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 
    110 def 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 
    135 def 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 
    154 if __name__ == '__main__':
    155    main()