diff --git a/edk2toolext/environment/reporttypes/component_report.py b/edk2toolext/environment/reporttypes/component_report.py index 574171f4..0c6f5031 100644 --- a/edk2toolext/environment/reporttypes/component_report.py +++ b/edk2toolext/environment/reporttypes/component_report.py @@ -13,6 +13,14 @@ from edk2toollib.database import Edk2DB +COMPONENT_QUERY = """ +SELECT id, path +FROM instanced_inf +WHERE + (path LIKE ? OR ? LIKE '%' || path || '%') + AND env = ? +""" + LIBRARY_QUERY = """ SELECT instanced_inf.id, @@ -26,7 +34,22 @@ AND junction.table2 = 'instanced_inf' AND junction.key1 = ? AND junction.env = ? - AND instanced_inf.component = ? + AND instanced_inf.component = ?; +""" + +FLAT_LIBRARY_QUERY = """ +SELECT class, path +FROM instanced_inf +WHERE + component = ? + AND path != component; +""" + +ID_QUERY = """ +SELECT id +FROM environment +ORDER BY date +DESC LIMIT 1; """ class ComponentDumpReport: @@ -37,7 +60,7 @@ def report_info(self): Returns: (str, str): A tuple of (name, description) """ - return ("component-libs", "Dumps the library instances used by a component.") + return ("component-libs", "Dumps the library instances used by component.") def add_cli_options(self, parserobj: ArgumentParser): """Configure command line arguments for this report.""" @@ -50,7 +73,8 @@ def add_cli_options(self, parserobj: ArgumentParser): help="Flatten the list of libraries used in the component.") parserobj.add_argument("-s", "--sort", dest="sort", action="store_true", help="Sort the libraries listed in alphabetical order.") - parserobj.add_argument("-e", "--env", dest="env_id", action="store", help="The environment id to generate the report for.") + parserobj.add_argument("-e", "--env", dest="env_id", action="store", + help="The environment id to generate the report for.") def run_report(self, db: Edk2DB, args: Namespace) -> None: """Runs the report.""" @@ -63,18 +87,19 @@ def run_report(self, db: Edk2DB, args: Namespace) -> None: self.depth = args.depth self.sort = args.sort - self.db = db + self.conn = db.connection - self.env_id = args.env_id or db.connection.execute("SELECT id FROM environment ORDER BY date DESC LIMIT 1;").fetchone()[0] + self.env_id = args.env_id or self.conn.execute(ID_QUERY).fetchone()[0] self.component = PurePath(args.component).as_posix() - id, inf_path = self.db.connection.execute("SELECT id, path FROM instanced_inf WHERE (path LIKE ? OR ? LIKE '%' || path || '%') AND env = ?", (f'%{self.component}%', self.component, self.env_id)).fetchone() + id, inf_path = self.conn.execute( + COMPONENT_QUERY, (f'%{self.component}%', self.component, self.env_id)).fetchone() # Print in flat format if args.flatten: return self.print_libraries_flat(inf_path) # Print in recursive format - libraries = self.db.connection.execute(LIBRARY_QUERY, (id, self.env_id, self.component)).fetchall() + libraries = self.conn.execute(LIBRARY_QUERY, (id, self.env_id, self.component)).fetchall() if self.sort: libraries = sorted(libraries, key=lambda x: x[1]) @@ -93,7 +118,7 @@ def print_libraries_recursive(self, library: Tuple[str, str, str], visited: list if library_instance is None: return - libraries = self.db.connection.execute(LIBRARY_QUERY, (id, self.env_id, self.component)) + libraries = self.conn.execute(LIBRARY_QUERY, (id, self.env_id, self.component)) if self.sort: libraries = sorted(libraries, key=lambda x: x[1]) @@ -107,7 +132,7 @@ def print_libraries_recursive(self, library: Tuple[str, str, str], visited: list def print_libraries_flat(self, component): """Prints the libraries used in a provided component.""" - libraries = self.db.connection.execute("SELECT class, path FROM instanced_inf WHERE component = ? AND path != component", (component,)).fetchall() + libraries = self.conn.execute(FLAT_LIBRARY_QUERY, (component,)).fetchall() length = max(len(item[0]) for item in libraries) if self.sort: diff --git a/edk2toolext/environment/reporttypes/coverage_report.py b/edk2toolext/environment/reporttypes/coverage_report.py index 317eccd9..35ed2dae 100644 --- a/edk2toolext/environment/reporttypes/coverage_report.py +++ b/edk2toolext/environment/reporttypes/coverage_report.py @@ -12,11 +12,37 @@ import xml.etree.ElementTree as ET from argparse import ArgumentParser, Namespace from pathlib import Path +import os from edk2toollib.database import Edk2DB from edk2toolext.environment.reporttypes.base_report import Report +SOURCE_QUERY = """ +SELECT inf.path, inf.library_class, junction.key2 as source +FROM + inf + LEFT JOIN junction ON inf.path = junction.key1 +WHERE + junction.table1 = 'inf' + AND junction.table2 = 'source' + AND junction.env = ?; +""" + +PACKAGE_PATH_QUERY = """ +SELECT value +FROM environment_values +WHERE + key = 'PACKAGES_PATH' + AND id = ?; +""" + +ID_QUERY = """ +SELECT id +FROM environment +ORDER BY date +DESC LIMIT 1; +""" class CoverageReport(Report): """A report ingests a cobertura.xml file and organizes it by INF.""" @@ -86,11 +112,20 @@ def _build_file_dict(self, xml_path: str) -> dict: return file_dict def _get_inf_cov(self, files: dict, db: Edk2DB, library_only: bool): - inf_table = db.table("inf") - env_table = db.table("environment") - packages_path = env_table.all()[-1]["PACKAGES_PATH"].split(";") - if library_only: - inf_table = inf_table.search(Query().LIBRARY_CLASS != "") + env_id, = db.connection.execute(ID_QUERY).fetchone() + pp_list, = db.connection.execute(PACKAGE_PATH_QUERY, (env_id,)).fetchone() + pp_list = pp_list.split(os.pathsep) + + # Build dictionary containing source files for each INF + # If library_only, filter out INFs that do not have a library_class + entry_dict = {} + for inf, library_class, source in db.connection.execute(SOURCE_QUERY, (env_id,)): + if library_only and library_class == "": + continue + if inf not in entry_dict: + entry_dict[inf] = [source] + else: + entry_dict[inf].append(source) root = ET.Element("coverage") @@ -98,20 +133,19 @@ def _get_inf_cov(self, files: dict, db: Edk2DB, library_only: bool): # Set the sources so that reports can find the right paths. source = ET.SubElement(sources, "source") source.text = self.args.workspace - for pp in packages_path: + for pp in pp_list: source = ET.SubElement(sources, "source") source.text = pp packages = ET.SubElement(root, "packages") - for entry in inf_table: - if not entry["SOURCES_USED"]: + for path, source_list in entry_dict.items(): + if not source_list: continue - inf = ET.SubElement(packages, "package", path=entry["PATH"], name=Path(entry["PATH"]).name) + inf = ET.SubElement(packages, "package", path=path, name=Path(path).name) classes = ET.SubElement(inf, "classes") found = False - for source in entry["SOURCES_USED"]: - source = Path(entry["PATH"]).parent / source - match = next((key for key in files.keys() if source.is_relative_to(key)), None) + for source in source_list: + match = next((key for key in files.keys() if Path(source).is_relative_to(key)), None) if match is not None: found = True classes.append(files[match]) diff --git a/edk2toolext/environment/reporttypes/usage_report.py b/edk2toolext/environment/reporttypes/usage_report.py index 67f6225c..bc802115 100644 --- a/edk2toolext/environment/reporttypes/usage_report.py +++ b/edk2toolext/environment/reporttypes/usage_report.py @@ -1,4 +1,13 @@ - +# @file usage_report.py +# A report that generates an html report about which repositories INFs (That are consumed for a platform) originate +# from +# +## +# Copyright (c) Microsoft Corporation +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +## +"""A report that generates an html report about which repositories INFs origin from.""" import io import logging from argparse import ArgumentParser, Namespace @@ -50,6 +59,20 @@ inf_list.path """ +VERSION_QUERY = """ +SELECT version +FROM environment +WHERE id = ?; +""" + +ID_QUERY = """ +SELECT id +FROM environment +ORDER BY date +DESC LIMIT 1; +""" + + class UsageReport(Report): """A report that generates a INF usage report for a specific build.""" def report_info(self): @@ -62,8 +85,11 @@ def report_info(self): def add_cli_options(self, parserobj: ArgumentParser): """Configure command line arguments for this report.""" - parserobj.add_argument("-e", "-env", dest="env_id", action="store", help = "The environment id to generate the report for. Defaults to the latest environment.") - parserobj.add_argument("-o", "-output", dest="output", action="store", help = "The output file to write the report to. Defaults to 'usage_report.html'.", default=None) + parserobj.add_argument("-e", "-env", dest="env_id", action="store", + help = "The environment id to generate the report for. Defaults to the latest " + "environment.") + parserobj.add_argument("-o", "-output", dest="output", action="store", default=None, + help = "The output file to write the report to. Defaults to 'usage_report.html'.") def run_report(self, db: Edk2DB, args: Namespace): """Generate the Usage report.""" @@ -76,7 +102,7 @@ def run_report(self, db: Edk2DB, args: Namespace): print(" Run the following command: `pip install jinja2 plotly`") exit(-1) - env_id = args.env_id or db.connection.execute("SELECT id FROM environment ORDER BY date DESC LIMIT 1;").fetchone()[0] + env_id = args.env_id or db.connection.execute(ID_QUERY).fetchone()[0] # Vars for html template env = Environment(loader=FileSystemLoader(templates.__path__)) @@ -84,7 +110,7 @@ def run_report(self, db: Edk2DB, args: Namespace): # This is the data that gets passed to the html template data = { - "version": db.connection.execute("SELECT version FROM environment WHERE id = ?;", (env_id,)).fetchone()[0], + "version": db.connection.execute(VERSION_QUERY, (env_id,)).fetchone()[0], "env": self._get_env_vars(db.connection, env_id), "inf_list": set(), } diff --git a/edk2toolext/invocables/edk2_parse.py b/edk2toolext/invocables/edk2_parse.py index e8240a30..03a9421f 100644 --- a/edk2toolext/invocables/edk2_parse.py +++ b/edk2toolext/invocables/edk2_parse.py @@ -112,7 +112,6 @@ def Go(self): db_path = Path(self.GetWorkspaceRoot()) / self.GetLoggingFolderRelativeToRoot() / DB_NAME pathobj = Edk2Path(self.GetWorkspaceRoot(), self.GetPackagesPath()) env = shell_environment.GetBuildVars() - logging.info("HELLO") if self.clear: db_path.unlink(missing_ok=True)