• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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