#!/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 where 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 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='', 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 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 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): entry: os.DirEntry = entry 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 = {} 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"): stats['missing examples']['value'] += 1 elif rel_path.startswith("tests"): stats['missing tests']['value'] += 1 elif rel_path.startswith(os.path.join("src", "plugins")): stats['missing plugins']['value'] += 1 elif rel_path.startswith("src"): stats['missing src']['value'] += 1 for stat in stats: if stats[stat]['value'] > 0: stats[stat]['percentage'] = round(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("{:<40}: {} ({}%)".format(stats[stat]['label'], stats[stat]['value'], stats[stat]['percentage'])) print("\n{:<40}: {:.10f} seconds".format("Scan time", scan_time)) print("{:<40}: {:.10f} seconds".format("Total script time", script_time)) 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()