diff options
author | Cristián Maureira-Fredes <cristian.maureira-fredes@qt.io> | 2021-04-07 00:24:49 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2021-04-07 20:07:09 +0000 |
commit | 386f79a2b3725bd6158bfc668772d6b84cf0c1b0 (patch) | |
tree | dd783cc3a7a9dc6194ec3b1073b06522907d3b15 | |
parent | 10a232fc48b04ed50abd7ff9b023cbc5b690dbd3 (diff) |
tools: update missing_binding
Updating the tool to be focused on Qt 6.x.
Moving the long structures to a config file,
moving around functions and formating the code.
Also, moving the script to a separate directory
to be able to have a 'requirements.txt' file which
can fetch all the required packages for the comparison
Change-Id: I7a52c33a609d0faff01c20977aa1509ebe6dc058
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
(cherry picked from commit bce03cee3bd654bebf693ef5c4e7016d539c38f8)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r-- | tools/missing_bindings.py | 456 | ||||
-rw-r--r-- | tools/missing_bindings/config.py | 173 | ||||
-rw-r--r-- | tools/missing_bindings/main.py | 330 | ||||
-rw-r--r-- | tools/missing_bindings/requirements.txt | 8 |
4 files changed, 511 insertions, 456 deletions
diff --git a/tools/missing_bindings.py b/tools/missing_bindings.py deleted file mode 100644 index 63314c1ab..000000000 --- a/tools/missing_bindings.py +++ /dev/null @@ -1,456 +0,0 @@ -############################################################################# -## -## Copyright (C) 2017 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## 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 Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## 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-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# - -# This script is used to generate a summary of missing types / classes -# which are present in C++ Qt5, but are missing in PySide6. -# -# Required packages: bs4 -# Installed via: pip install bs4 -# -# The script uses beautiful soup 4 to parse out the class names from -# the online Qt documentation. It then tries to import the types from -# PySide6. -# -# Example invocation of script: -# python missing_bindings.py --qt-version 5.9 -w all -# --qt-version - specify which version of qt documentation to load. -# -w - if PyQt5 is an installed package, check if the tested -# class also exists there. - -try: - import urllib.request as urllib2 -except ImportError: - import urllib2 - -import argparse -from bs4 import BeautifulSoup -from collections import OrderedDict -from time import gmtime, strftime -import sys -import os.path - -modules_to_test = OrderedDict() - -# Essentials -modules_to_test['QtCore'] = 'qtcore-module.html' -modules_to_test['QtGui'] = 'qtgui-module.html' -modules_to_test['QtMultimedia'] = 'qtmultimedia-module.html' -modules_to_test['QtMultimediaWidgets'] = 'qtmultimediawidgets-module.html' -modules_to_test['QtNetwork'] = 'qtnetwork-module.html' -modules_to_test['QtQml'] = 'qtqml-module.html' -modules_to_test['QtQuick'] = 'qtquick-module.html' -modules_to_test['QtQuickWidgets'] = 'qtquickwidgets-module.html' -modules_to_test['QtSql'] = 'qtsql-module.html' -modules_to_test['QtTest'] = 'qttest-module.html' -modules_to_test['QtWidgets'] = 'qtwidgets-module.html' - -# Addons -modules_to_test['Qt3DCore'] = 'qt3dcore-module.html' -modules_to_test['Qt3DInput'] = 'qt3dinput-module.html' -modules_to_test['Qt3DLogic'] = 'qt3dlogic-module.html' -modules_to_test['Qt3DRender'] = 'qt3drender-module.html' -modules_to_test['Qt3DAnimation'] = 'qt3danimation-module.html' -modules_to_test['Qt3DExtras'] = 'qt3dextras-module.html' -modules_to_test['QtConcurrent'] = 'qtconcurrent-module.html' -#modules_to_test['QtNetworkAuth'] = 'qtnetworkauth-module.html' -modules_to_test['QtHelp'] = 'qthelp-module.html' -modules_to_test['QtLocation'] = 'qtlocation-module.html' -modules_to_test['QtPrintSupport'] = 'qtprintsupport-module.html' -modules_to_test['QtScxml'] = 'qtscxml-module.html' -#modules_to_test['QtSpeech'] = 'qtspeech-module.html' -modules_to_test['QtSvg'] = 'qtsvg-module.html' -modules_to_test['QtUiTools'] = 'qtuitools-module.html' -modules_to_test['QtWebChannel'] = 'qtwebchannel-module.html' -modules_to_test['QtWebEngine'] = 'qtwebengine-module.html' -modules_to_test['QtWebEngineCore'] = 'qtwebenginecore-module.html' -modules_to_test['QtWebEngineWidgets'] = 'qtwebenginewidgets-module.html' -modules_to_test['QtWebSockets'] = 'qtwebsockets-module.html' -modules_to_test['QtMacExtras'] = 'qtmacextras-module.html' -modules_to_test['QtX11Extras'] = 'qtx11extras-module.html' -modules_to_test['QtWinExtras'] = 'qtwinextras-module.html' -modules_to_test['QtXml'] = 'qtxml-module.html' -modules_to_test['QtXmlPatterns'] = 'qtxmlpatterns-module.html' -modules_to_test['QtCharts'] = 'qtcharts-module.html' -modules_to_test['QtDataVisualization'] = 'qtdatavisualization-module.html' -modules_to_test['QtOpenGL'] = 'qtopengl-module.html' -modules_to_test['QtPositioning'] = 'qtpositioning-module.html' -modules_to_test['QtRemoteObjects'] = 'qtremoteobjects-module.html' -modules_to_test['QtScriptTools'] = 'qtscripttools-module.html' -modules_to_test['QtSensors'] = 'qtsensors-module.html' -modules_to_test['QtSerialPort'] = 'qtserialport-module.html' -types_to_ignore = set() -# QtCore -types_to_ignore.add('QFlag') -types_to_ignore.add('QFlags') -types_to_ignore.add('QGlobalStatic') -types_to_ignore.add('QDebug') -types_to_ignore.add('QDebugStateSaver') -types_to_ignore.add('QMetaObject.Connection') -types_to_ignore.add('QPointer') -types_to_ignore.add('QAssociativeIterable') -types_to_ignore.add('QSequentialIterable') -types_to_ignore.add('QStaticPlugin') -types_to_ignore.add('QChar') -types_to_ignore.add('QLatin1Char') -types_to_ignore.add('QHash') -types_to_ignore.add('QMultiHash') -types_to_ignore.add('QLinkedList') -types_to_ignore.add('QList') -types_to_ignore.add('QMap') -types_to_ignore.add('QMultiMap') -types_to_ignore.add('QMap.key_iterator') -types_to_ignore.add('QPair') -types_to_ignore.add('QQueue') -types_to_ignore.add('QScopedArrayPointer') -types_to_ignore.add('QScopedPointer') -types_to_ignore.add('QScopedValueRollback') -types_to_ignore.add('QMutableSetIterator') -types_to_ignore.add('QSet') -types_to_ignore.add('QSet.const_iterator') -types_to_ignore.add('QSet.iterator') -types_to_ignore.add('QExplicitlySharedDataPointer') -types_to_ignore.add('QSharedData') -types_to_ignore.add('QSharedDataPointer') -types_to_ignore.add('QEnableSharedFromThis') -types_to_ignore.add('QSharedPointer') -types_to_ignore.add('QWeakPointer') -types_to_ignore.add('QStack') -types_to_ignore.add('QLatin1String') -types_to_ignore.add('QString') -types_to_ignore.add('QStringRef') -types_to_ignore.add('QStringList') -types_to_ignore.add('QStringMatcher') -types_to_ignore.add('QVarLengthArray') -types_to_ignore.add('QVector') -types_to_ignore.add('QFutureIterator') -types_to_ignore.add('QHashIterator') -types_to_ignore.add('QMutableHashIterator') -types_to_ignore.add('QLinkedListIterator') -types_to_ignore.add('QMutableLinkedListIterator') -types_to_ignore.add('QListIterator') -types_to_ignore.add('QMutableListIterator') -types_to_ignore.add('QMapIterator') -types_to_ignore.add('QMutableMapIterator') -types_to_ignore.add('QSetIterator') -types_to_ignore.add('QMutableVectorIterator') -types_to_ignore.add('QVectorIterator') - -# QtGui -types_to_ignore.add('QIconEnginePlugin') -types_to_ignore.add('QImageIOPlugin') -types_to_ignore.add('QGenericPlugin') -types_to_ignore.add('QGenericPluginFactory') -types_to_ignore.add('QGenericMatrix') -types_to_ignore.add('QOpenGLExtraFunctions') -types_to_ignore.add('QOpenGLFunctions') -types_to_ignore.add('QOpenGLFunctions_1_0') -types_to_ignore.add('QOpenGLFunctions_1_1') -types_to_ignore.add('QOpenGLFunctions_1_2') -types_to_ignore.add('QOpenGLFunctions_1_3') -types_to_ignore.add('QOpenGLFunctions_1_4') -types_to_ignore.add('QOpenGLFunctions_1_5') -types_to_ignore.add('QOpenGLFunctions_2_0') -types_to_ignore.add('QOpenGLFunctions_2_1') -types_to_ignore.add('QOpenGLFunctions_3_0') -types_to_ignore.add('QOpenGLFunctions_3_1') -types_to_ignore.add('QOpenGLFunctions_3_2_Compatibility') -types_to_ignore.add('QOpenGLFunctions_3_2_Core') -types_to_ignore.add('QOpenGLFunctions_3_3_Compatibility') -types_to_ignore.add('QOpenGLFunctions_3_3_Core') -types_to_ignore.add('QOpenGLFunctions_4_0_Compatibility') -types_to_ignore.add('QOpenGLFunctions_4_0_Core') -types_to_ignore.add('QOpenGLFunctions_4_1_Compatibility') -types_to_ignore.add('QOpenGLFunctions_4_1_Core') -types_to_ignore.add('QOpenGLFunctions_4_2_Compatibility') -types_to_ignore.add('QOpenGLFunctions_4_2_Core') -types_to_ignore.add('QOpenGLFunctions_4_3_Compatibility') -types_to_ignore.add('QOpenGLFunctions_4_3_Core') -types_to_ignore.add('QOpenGLFunctions_4_4_Compatibility') -types_to_ignore.add('QOpenGLFunctions_4_4_Core') -types_to_ignore.add('QOpenGLFunctions_4_5_Compatibility') -types_to_ignore.add('QOpenGLFunctions_4_5_Core') -types_to_ignore.add('QOpenGLFunctions_ES2') - -# QtWidgets -types_to_ignore.add('QItemEditorCreator') -types_to_ignore.add('QStandardItemEditorCreator') -types_to_ignore.add('QStylePlugin') - -# QtSql -types_to_ignore.add('QSqlDriverCreator') -types_to_ignore.add('QSqlDriverPlugin') - -qt_documentation_website_prefixes = OrderedDict() -qt_documentation_website_prefixes['5.6'] = 'http://doc.qt.io/qt-5.6/' -qt_documentation_website_prefixes['5.8'] = 'http://doc.qt.io/qt-5.8/' -qt_documentation_website_prefixes['5.9'] = 'http://doc.qt.io/qt-5.9/' -qt_documentation_website_prefixes['5.10'] = 'http://doc.qt.io/qt-5.10/' -qt_documentation_website_prefixes['5.11'] = 'http://doc.qt.io/qt-5.11/' -qt_documentation_website_prefixes['5.11'] = 'http://doc.qt.io/qt-5.11/' -qt_documentation_website_prefixes['5.12'] = 'http://doc.qt.io/qt-5.12/' -qt_documentation_website_prefixes['5.13'] = 'http://doc.qt.io/qt-5.13/' -qt_documentation_website_prefixes['5.14'] = 'http://doc.qt.io/qt-5.14/' -qt_documentation_website_prefixes['5.15'] = 'http://doc.qt.io/qt-5/' -qt_documentation_website_prefixes['dev'] = 'http://doc-snapshots.qt.io/qt5-dev/' - - -def qt_version_to_doc_prefix(version): - if version in qt_documentation_website_prefixes: - return qt_documentation_website_prefixes[version] - else: - raise RuntimeError("The specified qt version is not supported") - - -def create_doc_url(module_doc_page_url, version): - return qt_version_to_doc_prefix(version) + module_doc_page_url - -parser = argparse.ArgumentParser() -parser.add_argument("module", - default='all', - choices=list(modules_to_test.keys()).append('all'), - nargs='?', - type=str, - help="the Qt module for which to get the missing types") -parser.add_argument("--qt-version", - "-v", - default='5.15', - choices=['5.6', '5.9', '5.11', '5.12', '5.13', '5.14', '5.15', 'dev'], - type=str, - dest='version', - help="the Qt version to use to check for types") -parser.add_argument("--which-missing", - "-w", - default='all', - choices=['all', 'in-pyqt', 'not-in-pyqt'], - type=str, - dest='which_missing', - help="Which missing types to show (all, or just those " - "that are not present in PyQt)") - -args = parser.parse_args() - -if hasattr(args, "module") and args.module != 'all': - saved_value = modules_to_test[args.module] - modules_to_test.clear() - modules_to_test[args.module] = saved_value - -pyside_package_name = "PySide6" -pyqt_package_name = "PyQt5" - -total_missing_types_count = 0 -total_missing_types_count_compared_to_pyqt = 0 -total_missing_modules_count = 0 - -wiki_file = open('missing_bindings_for_wiki_qt_io.txt', 'w') -wiki_file.truncate() - - -def log(*pargs, **kw): - print(*pargs) - - computed_str = '' - for arg in pargs: - computed_str += str(arg) - - style = 'text' - if 'style' in kw: - style = kw['style'] - - if style == 'heading1': - computed_str = '= ' + computed_str + ' =' - elif style == 'heading5': - computed_str = '===== ' + computed_str + ' =====' - elif style == 'with_newline': - computed_str += '\n' - elif style == 'bold_colon': - computed_str = computed_str.replace(':', ":'''") - computed_str += "'''" - computed_str += '\n' - elif style == 'error': - computed_str = "''" + computed_str.strip('\n') + "''\n" - elif style == 'text_with_link': - computed_str = computed_str - elif style == 'code': - computed_str = ' ' + computed_str - elif style == 'end': - return - - print(computed_str, file=wiki_file) - -log('PySide6 bindings for Qt {}'.format(args.version), style='heading1') - -log("""Using Qt version {} documentation to find public API Qt types and test -if the types are present in the PySide6 package.""".format(args.version)) - -log("""Results are usually stored at -https://wiki.qt.io/PySide6_Missing_Bindings -so consider taking the contents of the generated -missing_bindings_for_wiki_qt_io.txt file and updating the linked wiki page.""", -style='end') - -log("""Similar report: -https://gist.github.com/ethanhs/6c626ca4e291f3682589699296377d3a""", -style='text_with_link') - -python_executable = os.path.basename(sys.executable or '') -command_line_arguments = ' '.join(sys.argv) -report_date = strftime("%Y-%m-%d %H:%M:%S %Z", gmtime()) - -log(""" -This report was generated by running the following command: - {} {} -on the following date: - {} -""".format(python_executable, command_line_arguments, report_date)) - -for module_name in modules_to_test.keys(): - log(module_name, style='heading5') - - url = create_doc_url(modules_to_test[module_name], args.version) - log('Documentation link: {}\n'.format(url), style='text_with_link') - - # Import the tested module - try: - pyside_tested_module = getattr(__import__(pyside_package_name, - fromlist=[module_name]), module_name) - except Exception as e: - log('\nCould not load {}.{}. Received error: {}. Skipping.\n'.format( - pyside_package_name, module_name, str(e).replace("'", '')), - style='error') - total_missing_modules_count += 1 - continue - - try: - pyqt_module_name = module_name - if module_name == "QtCharts": - pyqt_module_name = module_name[:-1] - - pyqt_tested_module = getattr(__import__(pyqt_package_name, - fromlist=[pyqt_module_name]), pyqt_module_name) - except Exception as e: - log("\nCould not load {}.{} for comparison. " - "Received error: {}.\n".format(pyqt_package_name, module_name, - str(e).replace("'", '')), style='error') - - # Get C++ class list from documentation page. - page = urllib2.urlopen(url) - soup = BeautifulSoup(page, 'html.parser') - - # Extract the Qt type names from the documentation classes table - links = soup.body.select('.annotated a') - types_on_html_page = [] - - for link in links: - link_text = link.text - link_text = link_text.replace('::', '.') - if link_text not in types_to_ignore: - types_on_html_page.append(link_text) - - log('Number of types in {}: {}'.format(module_name, - len(types_on_html_page)), style='bold_colon') - - missing_types_count = 0 - missing_types_compared_to_pyqt = 0 - missing_types = [] - for qt_type in types_on_html_page: - try: - pyside_qualified_type = 'pyside_tested_module.' - - if "QtCharts" == module_name: - pyside_qualified_type += 'QtCharts.' - elif "DataVisualization" in module_name: - pyside_qualified_type += 'QtDataVisualization.' - - pyside_qualified_type += qt_type - eval(pyside_qualified_type) - except: - missing_type = qt_type - missing_types_count += 1 - total_missing_types_count += 1 - - is_present_in_pyqt = False - try: - pyqt_qualified_type = 'pyqt_tested_module.' - - if "Charts" in module_name: - pyqt_qualified_type += 'QtCharts.' - elif "DataVisualization" in module_name: - pyqt_qualified_type += 'QtDataVisualization.' - - pyqt_qualified_type += qt_type - eval(pyqt_qualified_type) - missing_type += " (is present in PyQt5)" - missing_types_compared_to_pyqt += 1 - total_missing_types_count_compared_to_pyqt += 1 - is_present_in_pyqt = True - except: - pass - - if args.which_missing == 'all': - missing_types.append(missing_type) - elif args.which_missing == 'in-pyqt' and is_present_in_pyqt: - missing_types.append(missing_type) - elif (args.which_missing == 'not-in-pyqt' and - not is_present_in_pyqt): - missing_types.append(missing_type) - - if len(missing_types) > 0: - log('Missing types in {}:'.format(module_name), style='with_newline') - missing_types.sort() - for missing_type in missing_types: - log(missing_type, style='code') - log('') - - log('Number of missing types: {}'.format(missing_types_count), - style='bold_colon') - if len(missing_types) > 0: - log('Number of missing types that are present in PyQt5: {}' - .format(missing_types_compared_to_pyqt), style='bold_colon') - log('End of missing types for {}\n'.format(module_name), style='end') - else: - log('', style='end') - -log('Summary', style='heading5') -log('Total number of missing types: {}'.format(total_missing_types_count), - style='bold_colon') -log('Total number of missing types that are present in PyQt5: {}' - .format(total_missing_types_count_compared_to_pyqt), style='bold_colon') -log('Total number of missing modules: {}' - .format(total_missing_modules_count), style='bold_colon') -wiki_file.close() diff --git a/tools/missing_bindings/config.py b/tools/missing_bindings/config.py new file mode 100644 index 000000000..23a733463 --- /dev/null +++ b/tools/missing_bindings/config.py @@ -0,0 +1,173 @@ +############################################################################# +## +## Copyright (C) 2021 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $QT_BEGIN_LICENSE:LGPL$ +## 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 Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## 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-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + + +modules_to_test = { + # 6.0 + 'QtCore': 'qtcore-module.html', + 'QtGui': 'qtgui-module.html', + 'QtNetwork': 'qtnetwork-module.html', + 'QtQml': 'qtqml-module.html', + 'QtQuick': 'qtquick-module.html', + 'QtQuickWidgets': 'qtquickwidgets-module.html', + 'QtQuickControls2': 'qtquickcontrols2-module.html', + #QtQuick3D - no python bindings + 'QtSql': 'qtsql-module.html', + 'QtWidgets': 'qtwidgets-module.html', + 'QtConcurrent': 'qtconcurrent-module.html', + #QtDBUS - no python bindings + 'QtHelp': 'qthelp-module.html', + 'QtOpenGL': 'qtopengl-module.html', + 'QtPrintSupport': 'qtprintsupport-module.html', + 'QtSvg': 'qtsvg-module.html', + 'QtUiTools': 'qtuitools-module.html', + 'QtXml': 'qtxml-module.html', + 'QtTest': 'qttest-module.html', + #'QtXmlPatterns': 'qtxmlpatterns-module.html', # in Qt5 compat + 'Qt3DCore': 'qt3dcore-module.html', + 'Qt3DInput': 'qt3dinput-module.html', + 'Qt3DLogic': 'qt3dlogic-module.html', + 'Qt3DRender': 'qt3drender-module.html', + 'Qt3DAnimation': 'qt3danimation-module.html', + 'Qt3DExtras': 'qt3dextras-module.html', + #'QtNetworkAuth': 'qtnetworkauth-module.html', # no python bindings + #'QtCoAp' -- TODO + #'QtMqtt' -- TODO + #'QtOpcUA' -- TODO + + # 6.1 + #'QtScxml': 'qtscxml-module.html', + #'QtCharts': 'qtcharts-module.html', + #'QtDataVisualization': 'qtdatavisualization-module.html', + + # 6.2 + #'QtPositioning': 'qtpositioning-module.html', + #'QtMultimedia': 'qtmultimedia-module.html', + #'QtRemoteObjects': 'qtremoteobjects-module.html', + #'QtSensors': 'qtsensors-module.html', + #'QtSerialPort': 'qtserialport-module.html', + #'QtWebChannel': 'qtwebchannel-module.html', + #'QtWebEngine': 'qtwebengine-module.html', + #'QtWebEngineCore': 'qtwebenginecore-module.html', + #'QtWebEngineWidgets': 'qtwebenginewidgets-module.html', + #'QtWebSockets': 'qtwebsockets-module.html', + + # 6.x + #'QtSpeech': 'qtspeech-module.html', + #'QtMultimediaWidgets': 'qtmultimediawidgets-module.html', + #'QtLocation': 'qtlocation-module.html', + + # Not in 6 + #'QtScriptTools': 'qtscripttools-module.html', + #'QtMacExtras': 'qtmacextras-module.html', + #'QtX11Extras': 'qtx11extras-module.html', + #'QtWinExtras': 'qtwinextras-module.html', +} + +types_to_ignore = { + # QtCore + 'QFlag', + 'QFlags', + 'QGlobalStatic', + 'QDebug', + 'QDebugStateSaver', + 'QMetaObject.Connection', + 'QPointer', + 'QAssociativeIterable', + 'QSequentialIterable', + 'QStaticPlugin', + 'QChar', + 'QLatin1Char', + 'QHash', + 'QMultiHash', + 'QLinkedList', + 'QList', + 'QMap', + 'QMultiMap', + 'QMap.key_iterator', + 'QPair', + 'QQueue', + 'QScopedArrayPointer', + 'QScopedPointer', + 'QScopedValueRollback', + 'QMutableSetIterator', + 'QSet', + 'QSet.const_iterator', + 'QSet.iterator', + 'QExplicitlySharedDataPointer', + 'QSharedData', + 'QSharedDataPointer', + 'QEnableSharedFromThis', + 'QSharedPointer', + 'QWeakPointer', + 'QStack', + 'QLatin1String', + 'QString', + 'QStringRef', + 'QStringList', + 'QStringMatcher', + 'QVarLengthArray', + 'QVector', + 'QFutureIterator', + 'QHashIterator', + 'QMutableHashIterator', + 'QLinkedListIterator', + 'QMutableLinkedListIterator', + 'QListIterator', + 'QMutableListIterator', + 'QMapIterator', + 'QMutableMapIterator', + 'QSetIterator', + 'QMutableVectorIterator', + 'QVectorIterator', + # QtGui + 'QIconEnginePlugin', + 'QImageIOPlugin', + 'QGenericPlugin', + 'QGenericPluginFactory', + 'QGenericMatrix', + 'QOpenGLExtraFunctions', + # QtWidgets + 'QItemEditorCreator', + 'QStandardItemEditorCreator', + 'QStylePlugin', + # QtSql + 'QSqlDriverCreator', + 'QSqlDriverPlugin', +} diff --git a/tools/missing_bindings/main.py b/tools/missing_bindings/main.py new file mode 100644 index 000000000..7390687ff --- /dev/null +++ b/tools/missing_bindings/main.py @@ -0,0 +1,330 @@ +############################################################################# +## +## Copyright (C) 2021 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $QT_BEGIN_LICENSE:LGPL$ +## 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 Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## 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-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +# This script is used to generate a summary of missing types / classes +# which are present in C++ Qt6, but are missing in PySide6. +# +# Required packages: bs4 +# Installed via: pip install bs4 +# +# The script uses beautiful soup 4 to parse out the class names from +# the online Qt documentation. It then tries to import the types from +# PySide6. +# +# Example invocation of script: +# python missing_bindings.py --qt-version 6.0 -w all +# --qt-version - specify which version of qt documentation to load. +# -w - if PyQt6 is an installed package, check if the tested +# class also exists there. + +import argparse +import os.path +import sys +from textwrap import dedent +from time import gmtime, strftime +from urllib import request + +from bs4 import BeautifulSoup + +from config import modules_to_test, types_to_ignore + +qt_documentation_website_prefixes = { + "6.0": "http://doc.qt.io/qt-6/", + "dev": "http://doc-snapshots.qt.io/qt5-dev/", +} + + +def qt_version_to_doc_prefix(version): + if version in qt_documentation_website_prefixes: + return qt_documentation_website_prefixes[version] + else: + raise RuntimeError("The specified qt version is not supported") + + +def create_doc_url(module_doc_page_url, version): + return qt_version_to_doc_prefix(version) + module_doc_page_url + + +def get_parser(): + parser = argparse.ArgumentParser() + parser.add_argument( + "module", + default="all", + choices=list(modules_to_test.keys()).append("all"), + nargs="?", + type=str, + help="the Qt module for which to get the missing types", + ) + parser.add_argument( + "--qt-version", + "-v", + default="6.0", + choices=["6.0", "dev"], + type=str, + dest="version", + help="the Qt version to use to check for types", + ) + parser.add_argument( + "--which-missing", + "-w", + default="all", + choices=["all", "in-pyqt", "not-in-pyqt"], + type=str, + dest="which_missing", + help="Which missing types to show (all, or just those " "that are not present in PyQt)", + ) + return parser + + +def wikilog(*pargs, **kw): + print(*pargs) + + computed_str = "" + for arg in pargs: + computed_str += str(arg) + + style = "text" + if "style" in kw: + style = kw["style"] + + if style == "heading1": + computed_str = "= " + computed_str + " =" + elif style == "heading5": + computed_str = "===== " + computed_str + " =====" + elif style == "with_newline": + computed_str += "\n" + elif style == "bold_colon": + computed_str = computed_str.replace(":", ":'''") + computed_str += "'''" + computed_str += "\n" + elif style == "error": + computed_str = "''" + computed_str.strip("\n") + "''\n" + elif style == "text_with_link": + computed_str = computed_str + elif style == "code": + computed_str = " " + computed_str + elif style == "end": + return + + print(computed_str, file=wiki_file) + + +if __name__ == "__main__": + parser = get_parser() + args = parser.parse_args() + + if hasattr(args, "module") and args.module != "all": + saved_value = modules_to_test[args.module] + modules_to_test.clear() + modules_to_test[args.module] = saved_value + + pyside_package_name = "PySide6" + pyqt_package_name = "PyQt6" + + total_missing_types_count = 0 + total_missing_types_count_compared_to_pyqt = 0 + total_missing_modules_count = 0 + + wiki_file = open("missing_bindings_for_wiki_qt_io.txt", "w") + wiki_file.truncate() + + wikilog(f"PySide6 bindings for Qt {args.version}", style="heading1") + + wikilog( + f"Using Qt version {args.version} documentation to find public " + "API Qt types and test if the types are present in the PySide6 " + "package." + ) + + wikilog( + dedent( + """\ + Results are usually stored at + https://wiki.qt.io/PySide6_Missing_Bindings + so consider taking the contents of the generated + missing_bindings_for_wiki_qt_io.txt + file and updating the linked wiki page.""" + ), + style="end", + ) + + wikilog( + "Similar report:\n" "https://gist.github.com/ethanhs/6c626ca4e291f3682589699296377d3a", + style="text_with_link", + ) + + python_executable = os.path.basename(sys.executable or "") + command_line_arguments = " ".join(sys.argv) + report_date = strftime("%Y-%m-%d %H:%M:%S %Z", gmtime()) + + wikilog( + dedent( + f""" + This report was generated by running the following command: + {python_executable} {command_line_arguments} + on the following date: + {report_date} + """ + ) + ) + + for module_name in modules_to_test.keys(): + wikilog(module_name, style="heading5") + + url = create_doc_url(modules_to_test[module_name], args.version) + wikilog(f"Documentation link: {url}\n", style="text_with_link") + + # Import the tested module + try: + pyside_tested_module = getattr( + __import__(pyside_package_name, fromlist=[module_name]), module_name + ) + except Exception as e: + e_str = str(e).replace('"', "") + wikilog( + f"\nCould not load {pyside_package_name}.{module_name}. " + f"Received error: {e_str}. Skipping.\n", + style="error", + ) + total_missing_modules_count += 1 + continue + + try: + pyqt_module_name = module_name + if module_name == "QtCharts": + pyqt_module_name = module_name[:-1] + + pyqt_tested_module = getattr( + __import__(pyqt_package_name, fromlist=[pyqt_module_name]), pyqt_module_name + ) + except Exception as e: + e_str = str(e).replace("'", "") + wikilog( + f"\nCould not load {pyqt_package_name}.{module_name} for comparison. " + f"Received error: {e_str}.\n", + style="error", + ) + + # Get C++ class list from documentation page. + page = request.urlopen(url) + soup = BeautifulSoup(page, "html.parser") + + # Extract the Qt type names from the documentation classes table + links = soup.body.select(".annotated a") + types_on_html_page = [] + + for link in links: + link_text = link.text + link_text = link_text.replace("::", ".") + if link_text not in types_to_ignore: + types_on_html_page.append(link_text) + + wikilog(f"Number of types in {module_name}: {len(types_on_html_page)}", style="bold_colon") + + missing_types_count = 0 + missing_types_compared_to_pyqt = 0 + missing_types = [] + for qt_type in types_on_html_page: + try: + pyside_qualified_type = "pyside_tested_module." + + if "QtCharts" == module_name: + pyside_qualified_type += "QtCharts." + elif "DataVisualization" in module_name: + pyside_qualified_type += "QtDataVisualization." + + pyside_qualified_type += qt_type + eval(pyside_qualified_type) + except: + missing_type = qt_type + missing_types_count += 1 + total_missing_types_count += 1 + + is_present_in_pyqt = False + try: + pyqt_qualified_type = "pyqt_tested_module." + + if "Charts" in module_name: + pyqt_qualified_type += "QtCharts." + elif "DataVisualization" in module_name: + pyqt_qualified_type += "QtDataVisualization." + + pyqt_qualified_type += qt_type + eval(pyqt_qualified_type) + missing_type += " (is present in PyQt6)" + missing_types_compared_to_pyqt += 1 + total_missing_types_count_compared_to_pyqt += 1 + is_present_in_pyqt = True + except: + pass + + if args.which_missing == "all": + missing_types.append(missing_type) + elif args.which_missing == "in-pyqt" and is_present_in_pyqt: + missing_types.append(missing_type) + elif args.which_missing == "not-in-pyqt" and not is_present_in_pyqt: + missing_types.append(missing_type) + + if len(missing_types) > 0: + wikilog(f"Missing types in {module_name}:", style="with_newline") + missing_types.sort() + for missing_type in missing_types: + wikilog(missing_type, style="code") + wikilog("") + + wikilog(f"Number of missing types: {missing_types_count}", style="bold_colon") + if len(missing_types) > 0: + wikilog( + "Number of missing types that are present in PyQt6: " + f"{missing_types_compared_to_pyqt}", + style="bold_colon", + ) + wikilog(f"End of missing types for {module_name}\n", style="end") + else: + wikilog("", style="end") + + wikilog("Summary", style="heading5") + wikilog(f"Total number of missing types: {total_missing_types_count}", style="bold_colon") + wikilog( + "Total number of missing types that are present in PyQt6: " + f"{total_missing_types_count_compared_to_pyqt}", + style="bold_colon", + ) + wikilog(f"Total number of missing modules: {total_missing_modules_count}", style="bold_colon") + wiki_file.close() diff --git a/tools/missing_bindings/requirements.txt b/tools/missing_bindings/requirements.txt new file mode 100644 index 000000000..732522d26 --- /dev/null +++ b/tools/missing_bindings/requirements.txt @@ -0,0 +1,8 @@ +beautifulsoup4 + +# PySide +PySide6 + +# PyQt +PyQt6 +PyQt6-3D |