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 --ignore_by_tag <dependency tags to ignore> 27 28""" 29 30import argparse 31import collections 32import csv 33import dependency_analysis 34import sys 35 36_ModuleTypeInfo = collections.namedtuple( 37 "_ModuleTypeInfo", 38 [ 39 # map of module type to the set of properties used by modules 40 # of the given type in the dependency tree. 41 "type_to_properties", 42 # [java modules only] list of source file extensions used by this module. 43 "java_source_extensions", 44 ]) 45 46_DependencyRelation = collections.namedtuple("_DependencyRelation", [ 47 "transitive_dependency", 48 "top_level_module", 49]) 50 51 52def _get_java_source_extensions(module): 53 out = set() 54 if "Module" not in module: 55 return out 56 if "Java" not in module["Module"]: 57 return out 58 if "SourceExtensions" not in module["Module"]["Java"]: 59 return out 60 if module["Module"]["Java"]["SourceExtensions"]: 61 out.update(module["Module"]["Java"]["SourceExtensions"]) 62 return out 63 64 65def _get_set_properties(module): 66 set_properties = set() 67 if "Module" not in module: 68 return set_properties 69 if "Android" not in module["Module"]: 70 return set_properties 71 if "SetProperties" not in module["Module"]["Android"]: 72 return set_properties 73 for prop in module["Module"]["Android"]["SetProperties"]: 74 set_properties.add(prop["Name"]) 75 return set_properties 76 77 78def _should_ignore(module, ignored_names): 79 return (dependency_analysis.is_windows_variation(module) or 80 module["Name"] in ignored_names or 81 dependency_analysis.ignore_kind(module["Type"])) 82 83def _update_infos(module_name, type_infos, module_graph_map, ignored_dep_names): 84 module = module_graph_map[module_name] 85 if _should_ignore(module, ignored_dep_names) or module_name in type_infos: 86 return 87 for dep in module["Deps"]: 88 dep_name = dep["Name"] 89 if dep_name == module_name: 90 continue 91 _update_infos(dep_name, type_infos, module_graph_map, ignored_dep_names) 92 93 java_source_extensions = _get_java_source_extensions(module) 94 type_to_properties = collections.defaultdict(set) 95 if module["Type"]: 96 type_to_properties[module["Type"]].update(_get_set_properties(module)) 97 for dep in module["Deps"]: 98 dep_name = dep["Name"] 99 if _should_ignore(module_graph_map[dep_name], ignored_dep_names): 100 continue 101 if dep_name == module_name: 102 continue 103 for dep_type, dep_type_properties in type_infos[dep_name].type_to_properties.items(): 104 type_to_properties[dep_type].update(dep_type_properties) 105 java_source_extensions.update(type_infos[dep_name].java_source_extensions) 106 type_infos[module_name] = _ModuleTypeInfo( 107 type_to_properties=type_to_properties, 108 java_source_extensions=java_source_extensions) 109 110 111def module_type_info_from_json(module_graph, module_type, ignored_dep_names): 112 """Builds a map of module name to _ModuleTypeInfo for each module of module_type. 113 114 Dependency edges pointing to modules in ignored_dep_names are not followed. 115 """ 116 module_graph_map = dict() 117 module_stack = [] 118 for module in module_graph: 119 # Windows variants have incomplete dependency information in the json module graph. 120 if dependency_analysis.is_windows_variation(module): 121 continue 122 module_graph_map[module["Name"]] = module 123 if module["Type"] == module_type: 124 module_stack.append(module["Name"]) 125 # dictionary of module name to _ModuleTypeInfo. 126 type_infos = {} 127 for module_name in module_stack: 128 # post-order traversal of the dependency graph builds the type_infos 129 # dictionary from the leaves so that common dependencies are visited 130 # only once. 131 _update_infos(module_name, type_infos, module_graph_map, ignored_dep_names) 132 133 return { 134 name: info 135 for name, info in type_infos.items() 136 if module_graph_map[name]["Type"] == module_type 137 } 138 139 140def main(): 141 parser = argparse.ArgumentParser(description="") 142 parser.add_argument("--module_type", "-m", help="name of Soong module type.") 143 parser.add_argument( 144 "--ignore_by_name", 145 type=str, 146 default="", 147 required=False, 148 help="Comma-separated list. When building the tree of transitive dependencies, will not follow dependency edges pointing to module names listed by this flag." 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(module_graph, module_type, 157 ignore_by_name.split(",")) 158 writer = csv.writer(sys.stdout) 159 writer.writerow([ 160 "module name", 161 "properties", 162 "java source extensions", 163 ]) 164 for module, module_type_info in type_infos.items(): 165 writer.writerow([ 166 module, 167 ("[\"%s\"]" % '"\n"'.join([ 168 "%s: %s" % (mtype, ",".join(properties)) for mtype, properties in 169 module_type_info.type_to_properties.items() 170 ]) if len(module_type_info.type_to_properties) else "[]"), 171 ("[\"%s\"]" % '", "'.join(module_type_info.java_source_extensions) 172 if len(module_type_info.java_source_extensions) else "[]"), 173 ]) 174 175 176if __name__ == "__main__": 177 main() 178