blob: 95cc1b72a1f4a75eced6b36d492dc6208f9ee4d8 [file] [log] [blame]
#!/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 <module type>
--ignore_by_name <modules to ignore>
--ignore_by_tag <dependency tags to ignore>
"""
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()