#!/usr/bin/env python3
# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Command-line tool for generating modularization stats."""
import argparse
import json
from typing import Dict, List
import class_dependency
import count_cycles
import graph
import os
import package_dependency
import print_dependencies_helper
import serialization
import sys
_SRC_PATH = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..', '..'))
sys.path.append(
os.path.join(_SRC_PATH, 'tools', 'android', 'modularization', 'loc'))
import modularization_loc_stat as loc_stat
CLASSES_TO_COUNT_INBOUND = ['ChromeActivity', 'ChromeTabbedActivity']
def _copy_metadata(metadata: Dict) -> Dict[str, str]:
if metadata is None:
return {}
return {f'meta_{key}': value for key, value in metadata.items()}
def _generate_graph_sizes(
class_graph: class_dependency.JavaClassDependencyGraph,
package_graph: package_dependency.JavaPackageDependencyGraph
) -> Dict[str, int]:
return {
'class_nodes': class_graph.num_nodes,
'class_edges': class_graph.num_edges,
'package_nodes': package_graph.num_nodes,
'package_edges': package_graph.num_edges
}
def _generate_inbound_stats(
class_graph: class_dependency.JavaClassDependencyGraph,
class_names: List[str]) -> Dict[str, int]:
valid_class_names = \
print_dependencies_helper.get_valid_classes_from_class_list(
class_graph, class_names)
result = {}
for class_name, valid_class_name in zip(class_names, valid_class_names):
node: class_dependency.JavaClass = class_graph.get_node_by_key(
valid_class_name)
result[f'inbound_{class_name}'] = len(node.inbound)
return result
def _generate_package_cycle_stats(
package_graph: package_dependency.JavaPackageDependencyGraph
) -> Dict[str, int]:
all_cycles = count_cycles.find_cycles(package_graph, 4)
cycles_size_2 = len(all_cycles[2])
cycles_size_up_to_4 = sum(map(len, all_cycles[2:]))
return {
'package_cycles_size_equals_2': cycles_size_2,
'package_cycles_size_up_to_4': cycles_size_up_to_4
}
def _generate_chrome_java_size(
class_graph: class_dependency.JavaClassDependencyGraph
) -> Dict[str, int]:
count = 0
class_node: class_dependency.JavaClass
for class_node in class_graph.nodes:
if '//chrome/android:chrome_java' in class_node.build_targets:
count += 1
return {'chrome_java_class_count': count}
def _generate_loc_stats(git_dir: str) -> Dict[str, object]:
start_date, end_date = loc_stat.GetDateRange(past_days=7)
loc_result_json: str = loc_stat.GenerateLOCStats(start_date,
end_date,
quiet=True,
json_format=True,
git_dir=git_dir)
loc_result: Dict = json.loads(loc_result_json)
loc_modularized = loc_result.get(loc_stat.KEY_LOC_MODULARIZED, 0)
loc_chrome_android = loc_result.get(loc_stat.KEY_LOC_LEGACY, 0)
total = loc_modularized + loc_chrome_android
percentage_modularized: float = loc_modularized / total if total > 0 else 0
return {
'loc_modularized': loc_modularized,
'loc_chrome_android': loc_chrome_android,
'loc_modularized_percentage': percentage_modularized,
'loc_start_date': loc_result.get(loc_stat.KEY_START_DATE, ''),
'loc_end_date': loc_result.get(loc_stat.KEY_END_DATE, ''),
}
def main():
arg_parser = argparse.ArgumentParser(
description='Given a JSON dependency graph, output a JSON with a '
'number of metrics to track progress of modularization.')
required_arg_group = arg_parser.add_argument_group('required arguments')
required_arg_group.add_argument(
'-f',
'--file',
required=True,
help='Path to the JSON file containing the dependency graph. '
'See the README on how to generate this file.')
arg_parser.add_argument(
'--git-dir',
type=str,
help='Root directory of the git repo to look into. '
'If not specified, use the current directory.')
arg_parser.add_argument(
'-o',
'--output',
help='File to write the result json to. In not specified, outputs to '
'stdout.')
arguments = arg_parser.parse_args()
class_graph, package_graph, graph_metadata = \
serialization.load_class_and_package_graphs_from_file(arguments.file)
stats = {}
stats.update(_copy_metadata(graph_metadata))
stats.update(_generate_graph_sizes(class_graph, package_graph))
stats.update(_generate_inbound_stats(class_graph,
CLASSES_TO_COUNT_INBOUND))
stats.update(_generate_package_cycle_stats(package_graph))
stats.update(_generate_chrome_java_size(class_graph))
stats.update(_generate_loc_stats(arguments.git_dir))
if arguments.output:
with open(arguments.output, 'w') as f:
json.dump(stats, f, sort_keys=True)
else:
print(json.dumps(stats, sort_keys=True))
if __name__ == '__main__':
main()