#!/usr/bin/env python3 # # Copyright (C) 2022 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """A script to produce a csv report of all modules of a given type. There is one output row per module of the input type, each column corresponds to one of the fields of the _ModuleTypeInfo named tuple described below. The script allows to ignore certain dependency edges based on the target module name, or the dependency tag name. Usage: ./bp2build-module-dep-infos.py -m --ignore_by_name --ignore_by_tag """ import argparse import collections import csv import dependency_analysis import sys _ModuleTypeInfo = collections.namedtuple( "_ModuleTypeInfo", [ # map of module type to the set of properties used by modules # of the given type in the dependency tree. "type_to_properties", # [java modules only] list of source file extensions used by this module. "java_source_extensions", ]) _DependencyRelation = collections.namedtuple("_DependencyRelation", [ "transitive_dependency", "top_level_module", ]) def _get_java_source_extensions(module): out = set() if "Module" not in module: return out if "Java" not in module["Module"]: return out if "SourceExtensions" not in module["Module"]["Java"]: return out if module["Module"]["Java"]["SourceExtensions"]: out.update(module["Module"]["Java"]["SourceExtensions"]) return out def _get_set_properties(module): set_properties = set() if "Module" not in module: return set_properties if "Android" not in module["Module"]: return set_properties if "SetProperties" not in module["Module"]["Android"]: return set_properties for prop in module["Module"]["Android"]["SetProperties"]: set_properties.add(prop["Name"]) return set_properties def _should_ignore(module, ignored_names): return (dependency_analysis.is_windows_variation(module) or module["Name"] in ignored_names or dependency_analysis.ignore_kind(module["Type"])) def _update_infos(module_name, type_infos, module_graph_map, ignored_dep_names): module = module_graph_map[module_name] if _should_ignore(module, ignored_dep_names) or module_name in type_infos: return for dep in module["Deps"]: dep_name = dep["Name"] if dep_name == module_name: continue _update_infos(dep_name, type_infos, module_graph_map, ignored_dep_names) java_source_extensions = _get_java_source_extensions(module) type_to_properties = collections.defaultdict(set) if module["Type"]: type_to_properties[module["Type"]].update(_get_set_properties(module)) for dep in module["Deps"]: dep_name = dep["Name"] if _should_ignore(module_graph_map[dep_name], ignored_dep_names): continue if dep_name == module_name: continue for dep_type, dep_type_properties in type_infos[dep_name].type_to_properties.items(): type_to_properties[dep_type].update(dep_type_properties) java_source_extensions.update(type_infos[dep_name].java_source_extensions) type_infos[module_name] = _ModuleTypeInfo( type_to_properties=type_to_properties, java_source_extensions=java_source_extensions) def module_type_info_from_json(module_graph, module_type, ignored_dep_names): """Builds a map of module name to _ModuleTypeInfo for each module of module_type. Dependency edges pointing to modules in ignored_dep_names are not followed. """ module_graph_map = dict() module_stack = [] for module in module_graph: # Windows variants have incomplete dependency information in the json module graph. if dependency_analysis.is_windows_variation(module): continue module_graph_map[module["Name"]] = module if module["Type"] == module_type: module_stack.append(module["Name"]) # dictionary of module name to _ModuleTypeInfo. type_infos = {} for module_name in module_stack: # post-order traversal of the dependency graph builds the type_infos # dictionary from the leaves so that common dependencies are visited # only once. _update_infos(module_name, type_infos, module_graph_map, ignored_dep_names) return { name: info for name, info in type_infos.items() if module_graph_map[name]["Type"] == module_type } def main(): parser = argparse.ArgumentParser(description="") parser.add_argument("--module_type", "-m", help="name of Soong module type.") parser.add_argument( "--ignore_by_name", type=str, default="", required=False, help="Comma-separated list. When building the tree of transitive dependencies, will not follow dependency edges pointing to module names listed by this flag." ) args = parser.parse_args() module_type = args.module_type ignore_by_name = args.ignore_by_name module_graph = dependency_analysis.get_json_module_type_info(module_type) type_infos = module_type_info_from_json(module_graph, module_type, ignore_by_name.split(",")) writer = csv.writer(sys.stdout) writer.writerow([ "module name", "properties", "java source extensions", ]) for module, module_type_info in type_infos.items(): writer.writerow([ module, ("[\"%s\"]" % '"\n"'.join([ "%s: %s" % (mtype, ",".join(properties)) for mtype, properties in module_type_info.type_to_properties.items() ]) if len(module_type_info.type_to_properties) else "[]"), ("[\"%s\"]" % '", "'.join(module_type_info.java_source_extensions) if len(module_type_info.java_source_extensions) else "[]"), ]) if __name__ == "__main__": main()