Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # -*- coding: utf-8 -*- |
| 3 | # Copyright lowRISC contributors. |
| 4 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| 5 | # SPDX-License-Identifier: Apache-2.0 |
| 6 | """DIF Status Report Generator. |
| 7 | |
| 8 | This tool generates a status report for the DIFs by cross referencing the git |
| 9 | commit history of each DIF with that of the HW it actuates to provide OpenTitan |
| 10 | developers with information about what DIFs require updating. |
| 11 | |
| 12 | To display usage run: |
| 13 | ./check_dif_statuses.py --help |
| 14 | """ |
| 15 | |
| 16 | import argparse |
| 17 | import collections |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 18 | import io |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 19 | import itertools |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 20 | import json |
| 21 | import logging |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 22 | import re |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 23 | import subprocess |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 24 | import sys |
| 25 | from contextlib import redirect_stdout |
| 26 | from enum import Enum |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 27 | from pathlib import Path |
Timothy Trippel | ec46639 | 2021-10-28 00:27:26 +0000 | [diff] [blame] | 28 | from typing import List, Set |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 29 | |
| 30 | import enlighten |
| 31 | import gitfame |
| 32 | import hjson |
| 33 | import pydriller |
| 34 | from tabulate import tabulate |
| 35 | from termcolor import colored |
| 36 | |
Timothy Trippel | 1d21f07 | 2021-10-28 23:42:59 +0000 | [diff] [blame] | 37 | from make_new_dif.ip import IPS_USING_IPGEN |
| 38 | |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 39 | # Maintain a list of IPs that only exist in the top-level area. |
| 40 | # |
| 41 | # Note that there are several templated IPs that are auto-generated in the |
| 42 | # top-level area as well, but since the bulk of the code (including the |
| 43 | # template) lives in the hw/ip area, we do not need to consider them. |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 44 | # These IPs are slowly being migrated to use the `ipgen` tooling, and are |
Timothy Trippel | 1d21f07 | 2021-10-28 23:42:59 +0000 | [diff] [blame] | 45 | # defined in the IPS_USING_IPGEN list in the make_new_dif.ip module imported |
| 46 | # above. |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 47 | _TOP_LEVEL_IPS = {"ast", "sensor_ctrl"} |
| 48 | |
| 49 | # Indicates that the DIF work has not yet started. |
| 50 | _NOT_STARTED = colored("NOT STARTED", "red") |
| 51 | |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 52 | # This file is $REPO_TOP/util/check_dif_statuses.py, so it takes two parent() |
Timothy Trippel | 76dd646 | 2021-10-27 22:26:20 +0000 | [diff] [blame] | 53 | # calls to get back to the top. |
| 54 | REPO_TOP = Path(__file__).resolve().parent.parent |
| 55 | |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 56 | # Define the DIF library relative to REPO_TOP. |
| 57 | DIFS_RELATIVE_PATH = Path("sw/device/lib/dif") |
| 58 | |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 59 | |
| 60 | class _OTComponent(Enum): |
| 61 | """Type of OpenTitan component.""" |
| 62 | DIF = 1 |
| 63 | HW = 2 |
| 64 | |
| 65 | |
| 66 | class DIFStatus: |
| 67 | """Holds all DIF status information for displaying. |
| 68 | |
| 69 | Attributes: |
| 70 | dif_name (str): Full name of the DIF including the IP name. |
| 71 | ip (str): Name of the IP the DIF is associated with. |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 72 | dif_path (Path): Path to the DIF code (relative to REPO_TOP). |
Timothy Trippel | ec46639 | 2021-10-28 00:27:26 +0000 | [diff] [blame] | 73 | hw_path (Path): Path to the HW RTL associated with this DIF. |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 74 | dif_last_modified (datetime): Date and time the DIF was last modified. |
| 75 | hw_last_modified (datetime): Date and time the HW was last modified. |
| 76 | dif_main_contributors (List[str]): List of emails of DIF contributors. |
| 77 | hw_main_constributors (List[str]): List of emails of HW contributors. |
| 78 | lifecycle_state (str): Lifecycle state string (e.g., S0, S1, ...). |
| 79 | num_functions_defined (int): Number of API functions defined. |
| 80 | num_functions_implemented (int): Number of API functions implemented. |
| 81 | api_complete (bool): Indicates if DIF implements all defined functions. |
| 82 | funcs_unimplemented (Set[str]): Set of unimplemted DIF functions. |
| 83 | |
| 84 | """ |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 85 | def __init__(self, top_level, dif_name): |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 86 | """Mines metadata to populate this DIFStatus object. |
| 87 | |
| 88 | Args: |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 89 | top_level: Name of the top level design. |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 90 | dif_name: Full name of the DIF including the IP name. |
| 91 | |
| 92 | Raises: |
| 93 | ValueError: Raised if DIF name does not start with "dif_". |
| 94 | """ |
| 95 | # Get DIF/IP names and path. |
| 96 | if not dif_name.startswith("dif_"): |
| 97 | raise ValueError("DIF name should start with \"dif_\".") |
| 98 | self.dif_name = dif_name |
| 99 | self.ip = self.dif_name[4:] |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 100 | self.dif_path = DIFS_RELATIVE_PATH / dif_name |
| 101 | self.dif_autogen_path = (DIFS_RELATIVE_PATH / |
| 102 | f"autogen/{dif_name}_autogen") |
Timothy Trippel | a7cd9a2 | 2021-08-12 21:03:49 +0000 | [diff] [blame] | 103 | |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 104 | # Check if header file exists - if not then its not even begun. |
Timothy Trippel | 1d21f07 | 2021-10-28 23:42:59 +0000 | [diff] [blame] | 105 | has_started = self.dif_path.with_suffix(".h").is_file() |
| 106 | |
| 107 | # Get (relative) HW RTL path. |
| 108 | if self.ip in IPS_USING_IPGEN: |
| 109 | self.hw_path = Path(f"hw/ip_templates/{self.ip}") |
| 110 | elif self.ip in _TOP_LEVEL_IPS: |
| 111 | self.hw_path = Path(f"hw/{top_level}/ip/{self.ip}") |
| 112 | else: |
| 113 | self.hw_path = Path(f"hw/ip/{self.ip}") |
Timothy Trippel | a7cd9a2 | 2021-08-12 21:03:49 +0000 | [diff] [blame] | 114 | |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 115 | # Indicates DIF API completeness. |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 116 | self.num_functions_defined = -1 |
| 117 | self.num_functions_implemented = -1 |
| 118 | self.api_complete = False |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 119 | |
| 120 | # Determine last date HW was updated. |
| 121 | self.hw_last_modified = self._get_last_commit_date( |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 122 | [self.hw_path / "rtl"], [""]) |
Timothy Trippel | a7cd9a2 | 2021-08-12 21:03:49 +0000 | [diff] [blame] | 123 | |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 124 | # Determine the main contributor of the HW. |
| 125 | self.hw_main_contributors = self._get_main_contributor_emails( |
| 126 | _OTComponent.HW) |
| 127 | if has_started: |
| 128 | # Determine last date DIF was updated. |
| 129 | self.dif_last_modified = self._get_last_commit_date( |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 130 | [self.dif_path, self.dif_autogen_path], [".h", ".c"]) |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 131 | # Determine the main contributor of the DIF. |
| 132 | self.dif_main_contributors = self._get_main_contributor_emails( |
| 133 | _OTComponent.DIF) |
| 134 | # Determine lifecycle state |
| 135 | self.lifecycle_state = self._get_dif_lifecycle_state() |
| 136 | # Determine DIF API completeness. |
| 137 | self.funcs_unimplemented = self._get_funcs_unimplemented() |
| 138 | else: |
Timothy Trippel | 1d21f07 | 2021-10-28 23:42:59 +0000 | [diff] [blame] | 139 | # Set DIF status data to indicate it has not started. |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 140 | self.dif_last_modified = "-" |
| 141 | self.dif_main_contributors = [_NOT_STARTED] |
| 142 | self.lifecycle_state = "-" |
| 143 | self.funcs_unimplemented = [_NOT_STARTED] |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 144 | |
| 145 | def _get_dif_lifecycle_state(self): |
Timothy Trippel | ec46639 | 2021-10-28 00:27:26 +0000 | [diff] [blame] | 146 | hjson_filename = self.hw_path / f"data/{self.ip}.prj.hjson" |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 147 | with open(hjson_filename, "r") as life_f: |
| 148 | lifecycle_data = hjson.load(life_f) |
| 149 | # If there are multiple revisions, grab the latest. |
| 150 | if "revisions" in lifecycle_data: |
| 151 | lifecycle_data = lifecycle_data["revisions"][-1] |
| 152 | if "dif_stage" in lifecycle_data: |
| 153 | return lifecycle_data["dif_stage"] |
| 154 | return "-" |
| 155 | |
| 156 | def _get_main_contributor_emails(self, component): |
| 157 | # Get contributor stats for HW or DIF (SW) and sort by LOC. |
| 158 | if component == _OTComponent.DIF: |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 159 | stats = self._get_contributors( |
| 160 | [self.dif_path, self.dif_autogen_path], [".h", ".c"]) |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 161 | else: |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 162 | stats = self._get_contributors([self.hw_path / "rtl"], [""]) |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 163 | sorted_stats = sorted(stats.items(), key=lambda x: x[1], reverse=True) |
| 164 | # If the second contributor has contributed at least 10% as much as the |
| 165 | # first contributor, include both second and first contributors. |
| 166 | contributor_1_email, contributor_1_loc = sorted_stats[0] |
| 167 | if len(sorted_stats) > 1: |
| 168 | contributor_2_email, contributor_2_loc = sorted_stats[1] |
| 169 | if (float(contributor_2_loc) / float(contributor_1_loc)) > 0.1: |
| 170 | return [contributor_1_email, contributor_2_email] |
| 171 | return [contributor_1_email] |
| 172 | |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 173 | def _get_contributors(self, file_paths, exts): |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 174 | contributor_stats = collections.defaultdict(int) |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 175 | for file_path, ext in itertools.product(file_paths, exts): |
Timothy Trippel | 1d21f07 | 2021-10-28 23:42:59 +0000 | [diff] [blame] | 176 | full_file_path = file_path.with_suffix(ext) |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 177 | output = io.StringIO() |
Timothy Trippel | ec46639 | 2021-10-28 00:27:26 +0000 | [diff] [blame] | 178 | try: |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 179 | # Use gitfame to fetch commit stats, captured from STDOUT. |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 180 | with redirect_stdout(output): |
| 181 | gitfame.main(args=[ |
| 182 | f"--incl={full_file_path}", "-s", "-e", "--log=ERROR", |
| 183 | "--format=json" |
| 184 | ]) |
Timothy Trippel | ec46639 | 2021-10-28 00:27:26 +0000 | [diff] [blame] | 185 | except FileNotFoundError: |
| 186 | logging.error(f"(contributors) file path ({full_file_path}) " |
| 187 | "does not exist.") |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 188 | sys.exit(1) |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 189 | gitfame_commit_stats = json.loads(output.getvalue()) |
| 190 | for contributor_stat in gitfame_commit_stats["data"]: |
| 191 | contributor = contributor_stat[0] |
| 192 | loc = contributor_stat[1] |
| 193 | if loc == 0: |
| 194 | break |
| 195 | contributor_stats[contributor] += loc |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 196 | return contributor_stats |
| 197 | |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 198 | def _get_last_commit_date(self, file_paths, exts): |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 199 | last_dif_commit_date = None |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 200 | for file_path, ext in itertools.product(file_paths, exts): |
Timothy Trippel | 1d21f07 | 2021-10-28 23:42:59 +0000 | [diff] [blame] | 201 | full_file_path = file_path.with_suffix(ext) |
Timothy Trippel | ec46639 | 2021-10-28 00:27:26 +0000 | [diff] [blame] | 202 | try: |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 203 | repo = pydriller.Repository( |
Timothy Trippel | 76dd646 | 2021-10-27 22:26:20 +0000 | [diff] [blame] | 204 | str(REPO_TOP), filepath=full_file_path).traverse_commits() |
Timothy Trippel | ec46639 | 2021-10-28 00:27:26 +0000 | [diff] [blame] | 205 | except FileNotFoundError: |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 206 | logging.error( |
| 207 | f"(date) file path ({full_file_path}) does not exist.") |
| 208 | sys.exit(1) |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 209 | for commit in repo: |
| 210 | if last_dif_commit_date is None: |
| 211 | last_dif_commit_date = commit.author_date |
| 212 | else: |
| 213 | last_dif_commit_date = max(last_dif_commit_date, |
| 214 | commit.author_date) |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 215 | return last_dif_commit_date.strftime("%Y-%m-%d %H:%M:%S") |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 216 | |
| 217 | def _get_funcs_unimplemented(self): |
| 218 | defined_funcs = self._get_defined_funcs() |
| 219 | implemented_funcs = self._get_implemented_funcs() |
| 220 | self.num_functions_defined = len(defined_funcs) |
| 221 | self.num_functions_implemented = len(implemented_funcs) |
| 222 | self.api_complete = bool(defined_funcs and |
| 223 | defined_funcs == implemented_funcs) |
| 224 | return defined_funcs - implemented_funcs |
| 225 | |
| 226 | def _get_defined_funcs(self): |
Timothy Trippel | ec46639 | 2021-10-28 00:27:26 +0000 | [diff] [blame] | 227 | header_file = self.dif_path.with_suffix(".h") |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 228 | autogen_header_file = self.dif_autogen_path.with_suffix(".h") |
Timothy Trippel | a7cd9a2 | 2021-08-12 21:03:49 +0000 | [diff] [blame] | 229 | defined_funcs = self._get_funcs(header_file) |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 230 | defined_funcs |= self._get_funcs(autogen_header_file) |
Timothy Trippel | a7cd9a2 | 2021-08-12 21:03:49 +0000 | [diff] [blame] | 231 | return defined_funcs |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 232 | |
| 233 | def _get_implemented_funcs(self): |
Timothy Trippel | ec46639 | 2021-10-28 00:27:26 +0000 | [diff] [blame] | 234 | c_file = self.dif_path.with_suffix(".c") |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 235 | c_autogen_file = self.dif_autogen_path.with_suffix(".c") |
| 236 | # The autogenerated header should always exist if the DIF has been |
| 237 | # started. |
| 238 | implemented_funcs = self._get_funcs(c_autogen_file) |
| 239 | # However, the manually-implemented header may not exist yet. |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 240 | # If no .c file exists --> All functions are undefined. |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 241 | if c_file.is_file(): |
| 242 | implemented_funcs |= self._get_funcs(c_file) |
| 243 | return implemented_funcs |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 244 | |
| 245 | def _get_funcs(self, file_path): |
Timothy Trippel | 76dd646 | 2021-10-27 22:26:20 +0000 | [diff] [blame] | 246 | func_pattern = re.compile(r"^dif_result_t (dif_.*)\(.*") |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 247 | funcs = set() |
| 248 | with open(file_path, "r") as fp: |
| 249 | for line in fp: |
| 250 | result = func_pattern.search(line) |
| 251 | if result is not None: |
| 252 | funcs.add(result.group(1)) |
| 253 | return funcs |
| 254 | |
Timothy Trippel | a7cd9a2 | 2021-08-12 21:03:49 +0000 | [diff] [blame] | 255 | |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 256 | def get_list_of_difs(shared_headers: List[str]) -> Set[str]: |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 257 | """Get a list of the root filenames of the DIFs. |
| 258 | |
| 259 | Args: |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 260 | shared_headers: Header file(s) shared amongst DIFs. |
| 261 | |
| 262 | Returns: |
Timothy Trippel | ec46639 | 2021-10-28 00:27:26 +0000 | [diff] [blame] | 263 | difs: Set of IP DIF library names. |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 264 | """ |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 265 | dif_headers = sorted(DIFS_RELATIVE_PATH.glob("*.h")) |
Timothy Trippel | ec46639 | 2021-10-28 00:27:26 +0000 | [diff] [blame] | 266 | difs = list(map(lambda s: Path(s).resolve().stem, dif_headers)) |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 267 | for header in shared_headers: |
| 268 | if header in difs: |
| 269 | difs.remove(header) |
| 270 | return difs |
| 271 | |
| 272 | |
| 273 | def print_status_table(dif_statuses: List[DIFStatus], |
| 274 | table_format: str) -> None: |
| 275 | """Print a table of DIF status information to STDOUT. |
| 276 | |
| 277 | Args: |
| 278 | dif_statuses: List of DIFStatus objects containing metadata about DIF |
| 279 | development states. |
| 280 | |
| 281 | Returns: |
| 282 | None |
| 283 | """ |
| 284 | # Build the table. |
| 285 | rows = [] |
| 286 | headers = [ |
| 287 | "IP", "DIF Updated", "HW Updated", "DIF Contributor*", |
| 288 | "HW Contributor*", "Functions\nDefined", "Functions\nImplemented", |
| 289 | "Stage" |
| 290 | ] |
| 291 | for dif_status in dif_statuses: |
| 292 | # Color code last modified dates. |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 293 | # Limit the last modified strings to 10 characters to only print the |
| 294 | # date (YYYY-MM-DD). |
| 295 | hw_last_modified = dif_status.hw_last_modified[:10] |
| 296 | dif_last_modified = dif_status.dif_last_modified[:10] |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 297 | if dif_status.hw_last_modified > dif_status.dif_last_modified: |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 298 | hw_last_modified = colored(hw_last_modified, "yellow") |
| 299 | dif_last_modified = colored(dif_last_modified, "yellow") |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 300 | # Color code API complete status. |
| 301 | if dif_status.api_complete: |
| 302 | num_funcs_defined = colored(dif_status.num_functions_defined, |
| 303 | "green") |
| 304 | num_funcs_implemented = colored( |
| 305 | dif_status.num_functions_implemented, "green") |
| 306 | else: |
| 307 | num_funcs_defined = colored(dif_status.num_functions_defined, |
| 308 | "red") |
| 309 | num_funcs_implemented = colored( |
| 310 | dif_status.num_functions_implemented, "red") |
| 311 | |
| 312 | # Add row to table (printing one contributor email per line). |
| 313 | rows.append([ |
| 314 | dif_status.ip, dif_last_modified, hw_last_modified, |
| 315 | "\n".join(dif_status.dif_main_contributors), |
| 316 | "\n".join(dif_status.hw_main_contributors), num_funcs_defined, |
| 317 | num_funcs_implemented, dif_status.lifecycle_state |
| 318 | ]) |
| 319 | |
| 320 | # Print the table and legend. |
| 321 | print("DIF Statuses:") |
| 322 | print(tabulate(rows, headers, tablefmt=table_format)) |
| 323 | print("""*Only the top two contributors (by LOC) """ |
| 324 | """for each component are listed.""") |
| 325 | print(colored("Yellow", "yellow"), |
| 326 | "\t= HW has been updated since the DIF.") |
| 327 | print( |
| 328 | colored("Green", "green"), |
| 329 | """\t= DIF API, as defined in the current header file, is complete. """ |
| 330 | """Note, the header file may lack necessary API functionality.""") |
| 331 | print(colored("Red", "red"), |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 332 | ("\t= DIF API is incomplete, as defined in the header file or the " |
| 333 | "work has not yet begun.")) |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 334 | |
| 335 | |
Timothy Trippel | 1d21f07 | 2021-10-28 23:42:59 +0000 | [diff] [blame] | 336 | def print_unimplemented_difs(dif_statuses: List[DIFStatus], |
| 337 | table_format: str) -> None: |
Timothy Trippel | a7cd9a2 | 2021-08-12 21:03:49 +0000 | [diff] [blame] | 338 | """Print a table of specific functions names DIF functions to STDOUT. |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 339 | |
| 340 | Args: |
| 341 | dif_statuses: List of DIFStatus objects containing metadata about DIF |
| 342 | development states. |
Timothy Trippel | a7cd9a2 | 2021-08-12 21:03:49 +0000 | [diff] [blame] | 343 | table_format: Format of output table to print. See tabulate module. |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 344 | |
| 345 | Returns: |
| 346 | None |
| 347 | """ |
Timothy Trippel | a7cd9a2 | 2021-08-12 21:03:49 +0000 | [diff] [blame] | 348 | # Build and print table. |
Timothy Trippel | 1d21f07 | 2021-10-28 23:42:59 +0000 | [diff] [blame] | 349 | print("Unimplemented Functions:") |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 350 | rows = [] |
| 351 | headers = ["IP", "Function"] |
| 352 | for dif_status in dif_statuses: |
Timothy Trippel | 1d21f07 | 2021-10-28 23:42:59 +0000 | [diff] [blame] | 353 | if not dif_status.api_complete: |
| 354 | rows.append( |
| 355 | [dif_status.ip, "\n".join(dif_status.funcs_unimplemented)]) |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 356 | print(tabulate(rows, headers, tablefmt=table_format)) |
| 357 | |
| 358 | |
| 359 | def main(argv): |
Timothy Trippel | a7cd9a2 | 2021-08-12 21:03:49 +0000 | [diff] [blame] | 360 | # Process args and set logging level. |
| 361 | # TODO: parallelize data scraping so its much faster |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 362 | parser = argparse.ArgumentParser( |
| 363 | prog="check_dif_statuses", |
| 364 | formatter_class=argparse.RawDescriptionHelpFormatter) |
| 365 | parser.add_argument( |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 366 | "--top-hjson", |
Timothy Trippel | 76dd646 | 2021-10-27 22:26:20 +0000 | [diff] [blame] | 367 | help="""Path to the top-level HJSON configuration file relative to |
| 368 | REPO_TOP.""") |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 369 | parser.add_argument( |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 370 | "--show-unimplemented", |
| 371 | action="store_true", |
| 372 | help="""Show unimplemented functions for each incomplete DIF.""") |
| 373 | parser.add_argument("--table-format", |
| 374 | type=str, |
| 375 | choices=["grid", "github", "pipe"], |
| 376 | default="grid", |
| 377 | help="""Format to print status tables in.""") |
| 378 | args = parser.parse_args(argv) |
Timothy Trippel | a7cd9a2 | 2021-08-12 21:03:49 +0000 | [diff] [blame] | 379 | logging.basicConfig(level=logging.WARNING) |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 380 | |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 381 | # Make sure to call this script from REPO_TOP. |
Timothy Trippel | 1d21f07 | 2021-10-28 23:42:59 +0000 | [diff] [blame] | 382 | if Path.cwd() != REPO_TOP: |
| 383 | logging.error(f"Must call script from \"$REPO_TOP\": {REPO_TOP}") |
| 384 | sys.exit(1) |
| 385 | |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 386 | if args.top_hjson: |
| 387 | # Get the list of IP blocks by invoking the topgen tool. |
Timothy Trippel | ec46639 | 2021-10-28 00:27:26 +0000 | [diff] [blame] | 388 | topgen_tool = REPO_TOP / "util/topgen.py" |
| 389 | top_hjson = REPO_TOP / args.top_hjson |
| 390 | top_level = top_hjson.stem |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 391 | # yapf: disable |
| 392 | topgen_process = subprocess.run([topgen_tool, "-t", top_hjson, |
Timothy Trippel | 76dd646 | 2021-10-27 22:26:20 +0000 | [diff] [blame] | 393 | "--get_blocks", "-o", REPO_TOP], |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 394 | universal_newlines=True, |
| 395 | stdout=subprocess.PIPE, |
| 396 | check=True) |
| 397 | # yapf: enable |
| 398 | # All DIF names are prefixed with `dif_`. |
| 399 | difs = {f"dif_{dif.strip()}" for dif in topgen_process.stdout.split()} |
| 400 | else: |
| 401 | # Get list of all DIF basenames. |
| 402 | # TODO: automatically get the list below by cross referencing DIF names |
| 403 | # with IP block names. Hardcoded for now. |
| 404 | print("WARNING: It is recommended to pass the --top-hjson switch to " |
| 405 | "get a more accurate representation of the DIF progress. The " |
| 406 | "list of IPs for which no DIF sources exist is unknown.") |
Timothy Trippel | e3f8a82 | 2021-09-17 06:09:28 +0000 | [diff] [blame] | 407 | shared_headers = ["dif_base"] |
Srikrishna Iyer | 05fc3e1 | 2021-08-20 09:50:21 -0700 | [diff] [blame] | 408 | top_level = "top_earlgrey" |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 409 | difs = get_list_of_difs(shared_headers) |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 410 | |
| 411 | # Get DIF statuses (while displaying a progress bar). |
| 412 | dif_statuses = [] |
| 413 | progress_bar = enlighten.Counter(total=len(difs), |
| 414 | desc="Analyzing statuses of DIFs ...", |
| 415 | unit="DIFs") |
| 416 | for dif in difs: |
Timothy Trippel | 7f327d8 | 2021-10-29 02:26:39 +0000 | [diff] [blame] | 417 | dif_statuses.append(DIFStatus(top_level, dif)) |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 418 | progress_bar.update() |
| 419 | |
| 420 | # Build table and print it to STDOUT. |
| 421 | print_status_table(dif_statuses, args.table_format) |
| 422 | if args.show_unimplemented: |
Timothy Trippel | 1d21f07 | 2021-10-28 23:42:59 +0000 | [diff] [blame] | 423 | print_unimplemented_difs(dif_statuses, args.table_format) |
Timothy Trippel | b93cc6a | 2021-07-30 02:34:38 +0000 | [diff] [blame] | 424 | |
| 425 | |
| 426 | if __name__ == "__main__": |
| 427 | main(sys.argv[1:]) |