diff options
Diffstat (limited to 'util/cmake/pro_conversion_rate.py')
-rwxr-xr-x | util/cmake/pro_conversion_rate.py | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/util/cmake/pro_conversion_rate.py b/util/cmake/pro_conversion_rate.py new file mode 100755 index 0000000000..3c0c7e3070 --- /dev/null +++ b/util/cmake/pro_conversion_rate.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the plugins of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:GPL-EXCEPT$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 3 as published by the Free Software +## Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +from __future__ import annotations + +""" +This utility script shows statistics about +converted .pro -> CMakeLists.txt files. + +To execute: python3 pro_conversion_rate.py <src dir> +where <src dir> can be any qt source directory. For better statistics, +specify a module root source dir (like ./qtbase or ./qtsvg). + +""" + +from argparse import ArgumentParser + +import os +import typing +from typing import Dict, Union +from timeit import default_timer + + +def _parse_commandline(): + parser = ArgumentParser(description="Find pro files for which there are no CMakeLists.txt.") + parser.add_argument( + "source_directory", metavar="<src dir>", type=str, help="The source directory" + ) + + return parser.parse_args() + + +class Blacklist: + """ Class to check if a certain dir_name / dir_path is blacklisted """ + + def __init__(self, names: typing.List[str], path_parts: typing.List[str]): + self.names = names + self.path_parts = path_parts + + # The lookup algorithm + self.lookup = self.is_blacklisted_part + self.tree = None + + try: + # If package is available, use Aho-Corasick algorithm, + from ahocorapy.keywordtree import KeywordTree # type: ignore + + self.tree = KeywordTree(case_insensitive=True) + + for p in self.path_parts: + self.tree.add(p) + self.tree.finalize() + + self.lookup = self.is_blacklisted_part_aho + except ImportError: + pass + + def is_blacklisted(self, dir_name: str, dir_path: str) -> bool: + # First check if exact dir name is blacklisted. + if dir_name in self.names: + return True + + # Check if a path part is blacklisted (e.g. util/cmake) + return self.lookup(dir_path) + + def is_blacklisted_part(self, dir_path: str) -> bool: + if any(part in dir_path for part in self.path_parts): + return True + return False + + def is_blacklisted_part_aho(self, dir_path: str) -> bool: + return self.tree.search(dir_path) is not None # type: ignore + + +def recursive_scan(path: str, extension: str, result_paths: typing.List[str], blacklist: Blacklist): + """ Find files ending with a certain extension, filtering out blacklisted entries """ + try: + for entry in os.scandir(path): + if entry.is_file() and entry.path.endswith(extension): + result_paths.append(entry.path) + elif entry.is_dir(): + if blacklist.is_blacklisted(entry.name, entry.path): + continue + recursive_scan(entry.path, extension, result_paths, blacklist) + except Exception as e: + print(e) + + +def check_for_cmake_project(pro_path: str) -> bool: + pro_dir_name = os.path.dirname(pro_path) + cmake_project_path = os.path.join(pro_dir_name, "CMakeLists.txt") + return os.path.exists(cmake_project_path) + + +def compute_stats( + src_path: str, + pros_with_missing_project: typing.List[str], + total_pros: int, + existing_pros: int, + missing_pros: int, +) -> dict: + stats: Dict[str, Dict[str, Union[str, int, float]]] = {} + stats["total projects"] = {"label": "Total pro files found", "value": total_pros} + stats["existing projects"] = { + "label": "Existing CMakeLists.txt files found", + "value": existing_pros, + } + stats["missing projects"] = { + "label": "Missing CMakeLists.txt files found", + "value": missing_pros, + } + stats["missing examples"] = {"label": "Missing examples", "value": 0} + stats["missing tests"] = {"label": "Missing tests", "value": 0} + stats["missing src"] = {"label": "Missing src/**/**", "value": 0} + stats["missing plugins"] = {"label": "Missing plugins", "value": 0} + + for p in pros_with_missing_project: + rel_path = os.path.relpath(p, src_path) + if rel_path.startswith("examples"): + assert isinstance(stats["missing examples"]["value"], int) + stats["missing examples"]["value"] += 1 + elif rel_path.startswith("tests"): + assert isinstance(stats["missing tests"]["value"], int) + stats["missing tests"]["value"] += 1 + elif rel_path.startswith(os.path.join("src", "plugins")): + assert isinstance(stats["missing plugins"]["value"], int) + stats["missing plugins"]["value"] += 1 + elif rel_path.startswith("src"): + assert isinstance(stats["missing src"]["value"], int) + stats["missing src"]["value"] += 1 + + for stat in stats: + if int(stats[stat]["value"]) > 0: + stats[stat]["percentage"] = round(float(stats[stat]["value"]) * 100 / total_pros, 2) + return stats + + +def print_stats( + src_path: str, + pros_with_missing_project: typing.List[str], + stats: dict, + scan_time: float, + script_time: float, +): + + if stats["total projects"]["value"] == 0: + print("No .pro files found. Did you specify a correct source path?") + return + + if stats["total projects"]["value"] == stats["existing projects"]["value"]: + print("All projects were converted.") + else: + print("Missing CMakeLists.txt files for the following projects: \n") + + for p in pros_with_missing_project: + rel_path = os.path.relpath(p, src_path) + print(rel_path) + + print("\nStatistics: \n") + + for stat in stats: + if stats[stat]["value"] > 0: + print( + f"{stats[stat]['label']:<40}: {stats[stat]['value']} ({stats[stat]['percentage']}%)" + ) + + print(f"\n{'Scan time':<40}: {scan_time:.10f} seconds") + print(f"{'Total script time':<40}: {script_time:.10f} seconds") + + +def main(): + args = _parse_commandline() + src_path = os.path.abspath(args.source_directory) + pro_paths = [] + + extension = ".pro" + + blacklist_names = ["config.tests", "doc", "3rdparty", "angle"] + blacklist_path_parts = [os.path.join("util", "cmake")] + + script_start_time = default_timer() + blacklist = Blacklist(blacklist_names, blacklist_path_parts) + + scan_time_start = default_timer() + recursive_scan(src_path, extension, pro_paths, blacklist) + scan_time_end = default_timer() + scan_time = scan_time_end - scan_time_start + + total_pros = len(pro_paths) + + pros_with_missing_project = [] + for pro_path in pro_paths: + if not check_for_cmake_project(pro_path): + pros_with_missing_project.append(pro_path) + + missing_pros = len(pros_with_missing_project) + existing_pros = total_pros - missing_pros + + stats = compute_stats( + src_path, pros_with_missing_project, total_pros, existing_pros, missing_pros + ) + script_end_time = default_timer() + script_time = script_end_time - script_start_time + + print_stats(src_path, pros_with_missing_project, stats, scan_time, script_time) + + +if __name__ == "__main__": + main() |