1#!/usr/bin/env python3 2# 3# Copyright (C) 2022 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16"""A script to produce a csv report of all modules of a given type. 17 18There is one output row per module of the input type, each column corresponds 19to one of the fields of the _ModuleTypeInfo named tuple described below. 20The script allows to ignore certain dependency edges based on the target module 21name, or the dependency tag name. 22 23Usage: 24 ./bp2build-module-dep-infos.py -m <module type> 25 --ignore-by-name <modules to ignore> 26 27""" 28 29import argparse 30import collections 31import csv 32import dependency_analysis 33import sys 34 35_ModuleTypeInfo = collections.namedtuple( 36 "_ModuleTypeInfo", 37 [ 38 # map of module type to the set of properties used by modules 39 # of the given type in the dependency tree. 40 "type_to_properties", 41 # [java modules only] list of source file extensions used by this module. 42 "java_source_extensions", 43 ], 44) 45 46def _get_java_source_extensions(module): 47 out = set() 48 if "Module" not in module: 49 return out 50 if "Java" not in module["Module"]: 51 return out 52 if "SourceExtensions" not in module["Module"]["Java"]: 53 return out 54 if module["Module"]["Java"]["SourceExtensions"]: 55 out.update(module["Module"]["Java"]["SourceExtensions"]) 56 return out 57 58 59def module_type_info_from_json( 60 module_graph, module_type, ignored_dep_names, ignore_java_auto_deps 61): 62 """Builds a map of module name to _ModuleTypeInfo for each module of module_type. 63 64 Dependency edges pointing to modules in ignored_dep_names are not followed. 65 """ 66 67 modules_of_type = set() 68 69 def filter_by_type(json): 70 if json["Type"] == module_type: 71 modules_of_type.add(json["Name"]) 72 return True 73 return False 74 75 # dictionary of module name to _ModuleTypeInfo. 76 77 type_infos = {} 78 79 def update_infos(module, deps): 80 module_name = module["Name"] 81 info = type_infos.get( 82 module_name, 83 _ModuleTypeInfo( 84 java_source_extensions=set(), 85 type_to_properties=collections.defaultdict(set), 86 )) 87 88 java_source_extensions = _get_java_source_extensions(module) 89 90 if module["Type"]: 91 info.type_to_properties[module["Type"]].update( 92 dependency_analysis.get_property_names(module)) 93 94 for dep_name in deps: 95 for dep_type, dep_type_properties in type_infos[ 96 dep_name].type_to_properties.items(): 97 info.type_to_properties[dep_type].update(dep_type_properties) 98 java_source_extensions.update( 99 type_infos[dep_name].java_source_extensions) 100 101 info.java_source_extensions.update(java_source_extensions) 102 # for a module, collect all properties and java source extensions specified by 103 # transitive dependencies and the module itself 104 type_infos[module_name] = info 105 106 dependency_analysis.visit_json_module_graph_post_order( 107 module_graph, ignored_dep_names, ignore_java_auto_deps, filter_by_type, update_infos) 108 109 return { 110 name: info for name, info in type_infos.items() if name in modules_of_type 111 } 112 113 114def _write_output(file_handle, type_infos): 115 writer = csv.writer(file_handle) 116 writer.writerow([ 117 "module name", 118 "properties", 119 "java source extensions", 120 ]) 121 for module, module_type_info in type_infos.items(): 122 writer.writerow([ 123 module, 124 ("[\"%s\"]" % '"\n"'.join([ 125 "%s: %s" % (mtype, ",".join(properties)) for mtype, properties in 126 module_type_info.type_to_properties.items() 127 ]) if len(module_type_info.type_to_properties) else "[]"), 128 ("[\"%s\"]" % '", "'.join(module_type_info.java_source_extensions) 129 if len(module_type_info.java_source_extensions) else "[]"), 130 ]) 131 132 133def main(): 134 parser = argparse.ArgumentParser() 135 parser.add_argument("--module-type", "-m", help="name of Soong module type.") 136 parser.add_argument( 137 "--ignore-by-name", 138 default="", 139 help=( 140 "Comma-separated list. When building the tree of transitive" 141 " dependencies, will not follow dependency edges pointing to module" 142 " names listed by this flag." 143 ), 144 ) 145 parser.add_argument( 146 "--ignore-java-auto-deps", 147 action="store_true", 148 help="whether to ignore automatically added java deps", 149 ) 150 args = parser.parse_args() 151 152 module_type = args.module_type 153 ignore_by_name = args.ignore_by_name 154 155 module_graph = dependency_analysis.get_json_module_type_info(module_type) 156 type_infos = module_type_info_from_json( 157 module_graph, 158 module_type, 159 ignore_by_name.split(","), 160 args.ignore_java_auto_deps, 161 ) 162 163 _write_output(sys.stdout, type_infos) 164 165 166if __name__ == "__main__": 167 main() 168