From 526c00455799d0399669872855475228ee74e7eb Mon Sep 17 00:00:00 2001 From: iakov Date: Thu, 8 Feb 2024 17:16:33 +0300 Subject: [PATCH] Merge MeVisLab upstream as of 20240206 (#19) Merge PythonQt from MeVisLab upstream 20240207 --- .github/dependabot.yml | 6 + .github/workflows/build.yml | 123 +++-- .github/workflows/build_latest.yml | 126 ++++++ .github/workflows/builddocs.yml | 32 ++ .gitignore | 9 +- .vimrc | 1 + README | 31 -- README.md | 8 +- build/common.prf | 22 +- build_python | 17 - extensions/PythonQt_QtAll/PythonQt_QtAll.pro | 59 ++- generator/abstractmetabuilder.cpp | 443 ++++++++----------- generator/abstractmetabuilder.h | 12 +- generator/abstractmetalang.cpp | 9 +- generator/abstractmetalang.h | 8 +- generator/generator.pri | 2 +- generator/main.cpp | 203 ++++++++- generator/main.h | 147 ------ generator/parser/ast.h | 10 +- generator/parser/binder.cpp | 66 ++- generator/parser/binder.h | 1 + generator/parser/codemodel.cpp | 22 +- generator/parser/codemodel.h | 17 +- generator/parser/compiler_utils.cpp | 1 - generator/parser/declarator_compiler.cpp | 3 + generator/parser/declarator_compiler.h | 7 +- generator/parser/lexer.cpp | 12 + generator/parser/name_compiler.cpp | 6 - generator/parser/parser.cpp | 427 ++++++++++++------ generator/parser/parser.h | 5 +- generator/parser/rpp/pp-engine-bits.h | 20 +- generator/parser/rpp/pp-engine.h | 3 + generator/parser/rpp/pp-macro-expander.h | 12 +- generator/parser/rpp/pp-qt-configuration | 7 + generator/parser/tokens.cpp | 1 + generator/parser/tokens.h | 1 + generator/parser/type_compiler.cpp | 5 - generator/parser/type_compiler.h | 1 - generator/qtscript_masterinclude.h | 16 + generator/setupgenerator.cpp | 51 +-- generator/shellgenerator.cpp | 37 +- generator/shellgenerator.h | 14 +- generator/shellheadergenerator.cpp | 34 +- generator/shellimplgenerator.cpp | 23 +- generator/typesystem.cpp | 149 +++++-- generator/typesystem.h | 30 +- generator/typesystem_core.xml | 306 ++++++++++++- generator/typesystem_gui.xml | 119 +++-- generator/typesystem_multimedia.xml | 53 ++- generator/typesystem_network.xml | 37 +- generator/typesystem_opengl.xml | 5 +- generator/typesystem_qml.xml | 30 +- generator/typesystem_quick.xml | 13 +- generator/typesystem_sql.xml | 2 +- generator/typesystem_svg.xml | 5 +- generator/typesystem_uitools.xml | 2 +- generator/typesystem_webenginewidgets.xml | 38 +- generator/typesystem_webkit.xml | 2 +- generator/typesystem_xml.xml | 6 +- generator/typesystem_xmlpatterns.xml | 5 +- src/PythonQt.cpp | 73 ++- src/PythonQt.h | 3 + src/PythonQtClassInfo.cpp | 62 ++- src/PythonQtClassInfo.h | 10 + src/PythonQtConversion.cpp | 159 ++++++- src/PythonQtConversion.h | 20 +- src/PythonQtDoc.h | 66 ++- src/PythonQtMisc.h | 24 + src/src.pro | 2 +- tests/PythonQtTests.cpp | 18 +- with_pyenv | 45 -- 71 files changed, 2322 insertions(+), 1022 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build_latest.yml create mode 100644 .github/workflows/builddocs.yml create mode 100644 .vimrc delete mode 100644 README delete mode 100644 build_python delete mode 100644 generator/main.h delete mode 100755 with_pyenv diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..7a968a8d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: monthly diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c6edf8dc..ec7fde42 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,12 +15,13 @@ jobs: ubuntu: strategy: fail-fast: false - matrix: - container: - - 'ubuntu:20.04' -# - 'ubuntu:22.04' + matrix: + pythonqtall-config: ['PythonQtCore PythonQtGui PythonQtMultimedia'] + container_version: + - '20.04' + - '22.04' runs-on: ubuntu-latest - container: ${{ matrix.container }} + container: ubuntu:${{ matrix.container_version }} steps: - name: Install Qt run: | @@ -46,7 +47,7 @@ jobs: apt-get clean - name: Checkout PythonQt - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build PythonQt run: | @@ -54,11 +55,15 @@ jobs: echo ======= SYSTEM INFO ======== uname -a; gcc --version | grep "gcc"; python3 --version; qmake --version echo ============================ - qmake -r PythonQt.pro CONFIG+=release CONFIG+=sanitizer CONFIG+=sanitize_undefined CONFIG+=generator CONFIG+=tests \ + qmake -r PythonQt.pro CONFIG+=release CONFIG+=force_debug_info \ + PYTHONQTALL_CONFIG="${{ matrix.pythonqtall-config }}" \ + CONFIG+=sanitizer CONFIG+=sanitize_undefined CONFIG+=sanitize_address \ + CONFIG+=generator CONFIG+=tests \ PYTHON_VERSION=$(python3 --version | cut -d " " -f 2 | cut -d "." -f1,2) \ PYTHON_DIR=$(which python3 | xargs dirname | xargs dirname) - make -j 2 - UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \ + make -j $(nproc) + PYTHONDEVMODE=1 PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=error PYTHONMALLOC=malloc_debug \ + UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \ make check TESTARGS="-platform offscreen" - name: Generate Wrappers @@ -67,31 +72,33 @@ jobs: mkdir /usr/include/qt5; ln -s /usr/include/x86_64-linux-gnu/qt5 /usr/include/qt5/include export QTDIR=/usr/include/qt5 cd generator + UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \ ./pythonqt_generator - name: Upload Wrappers - uses: actions/upload-artifact@v3 - if: ${{ matrix.container }} == 'ubuntu:20.04' - with: - name: wrappers_ubuntu_20_04 - path: generated_cpp - - - name: Upload Wrappers - uses: actions/upload-artifact@v3 - if: ${{ matrix.container }} == 'ubuntu:22.04' + uses: actions/upload-artifact@v4 with: - name: wrappers_ubuntu_22_04 + name: wrappers_ubuntu_${{ matrix.container_version }} path: generated_cpp - centos: + oldschool: strategy: fail-fast: false matrix: - container: - - 'centos:7' - configuration: ['debug', 'release'] + container_os: ['centos'] + container_os_version: ['7'] + container_os_python_package: ['python-debug'] + configuration: ['debug', 'release'] + pythonqtall-config: ['PythonQtCore PythonQtGui PythonQtMultimedia'] + include: + - container_os: 'rockylinux' + container_os_version: '9' + container_os_python_package: 'python3-devel' + configuration: 'release' + pythonqtall-config: 'PythonQtCore PythonQtGui PythonQtMultimedia' + runs-on: ubuntu-latest - container: ${{ matrix.container }} + container: '${{ matrix.container_os }}:${{ matrix.container_os_version }}' steps: - name: Install Qt run: | @@ -99,7 +106,7 @@ jobs: yum groupinstall "Development Tools" -y yum install -y \ which \ - python-debug \ + ${{ matrix.container_os_python_package }} \ qt5-qtbase-* \ qt5-qttools* \ qt5-qtsvg \ @@ -115,12 +122,21 @@ jobs: run: | export QT_SELECT=qt5 echo ======= SYSTEM INFO ======== - uname -a; gcc --version | grep "gcc"; python --version; qmake-qt5 --version + which python 2>/dev/null && export PYTHON_VERSION_SUFFIX= || export PYTHON_VERSION_SUFFIX=3 + uname -a; gcc --version | grep "gcc"; python${PYTHON_VERSION_SUFFIX} --version; qmake-qt5 --version echo ============================ - qmake-qt5 -r PythonQt.pro CONFIG+=${{ matrix.configuration }} CONFIG+=generator CONFIG+=tests \ - PYTHON_VERSION=$(python --version | cut -d " " -f 2 | cut -d "." -f1,2) \ - PYTHON_DIR=$(which python | xargs dirname | xargs dirname) - make -j 2 && make check TESTARGS="-platform offscreen" + export PYTHON_VERSION_SHORT=`python${PYTHON_VERSION_SUFFIX} --version | cut -d " " -f 2 | cut -d "." -f1,2` + if [[ `echo ${PYTHON_VERSION_SHORT} | wc -w` = 0 ]]; then export PYTHON_VERSION_SHORT=2.7; fi + export PYTHON_DIR=`which python${PYTHON_VERSION_SUFFIX} | xargs dirname | xargs dirname` + echo PYTHON_VERSION_SHORT=${PYTHON_VERSION_SHORT} + echo PYTHON_DIR=${PYTHON_DIR} + qmake-qt5 -r PythonQt.pro CONFIG+=${{ matrix.configuration }} \ + PYTHONQTALL_CONFIG="${{ matrix.pythonqtall-config }}" \ + CONFIG+=generator CONFIG+=tests \ + "PYTHON_VERSION=${PYTHON_VERSION_SHORT}" "PYTHON_DIR=${PYTHON_DIR}" + make -j $(nproc) && \ + PYTHONDEVMODE=1 PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=error PYTHONMALLOC=malloc_debug \ + make check TESTARGS="-platform offscreen" - name: Generate Wrappers run: | @@ -133,7 +149,7 @@ jobs: - name: Upload Wrappers uses: actions/upload-artifact@v3 with: - name: wrappers_centos7 + name: wrappers_${{ matrix.container_os }}-${{ matrix.container_os_version }}_${{ matrix.configuration }} path: generated_cpp macOS: @@ -141,9 +157,9 @@ jobs: fail-fast: false matrix: macos-version: ['11'] - python-version: ['3.9'] - qt-version: ['5.12.*'] - configuration: ['release'] + python-version: ['3.6'] + qt-version: ['5.9.*'] + configuration: ['release','debug'] include: - macos-version: '12' python-version: '3.9' @@ -167,12 +183,12 @@ jobs: archives: 'qtmultimedia qtmacextras qtbase qttools' - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '${{ matrix.python-version }}' - name: Checkout PythonQt - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Detect exact versions id : versions @@ -202,13 +218,16 @@ jobs: for i in "python${{ steps.versions.outputs.PYTHON_VERSION_SHORT }}-embed" "python${{ steps.versions.outputs.PYTHON_VERSION_SHORT }}" \ "python${PYTHON_VERSION_MAJOR}-embed" "python${PYTHON_VERSION_MAJOR}" do if pkg-config --exists "$i"; then PYTHON_PKGCONFIG_NAME="$i"; break; fi; done - qmake CONFIG+=${{ matrix.configuration }} CONFIG+=sanitizer CONFIG+=sanitize_undefined CONFIG+=generator CONFIG+=tests \ + qmake CONFIG+=${{ matrix.configuration }} CONFIG+=sanitizer CONFIG+=sanitize_undefined CONFIG+=sanitize_address \ + PYTHONQTALL_CONFIG="${{ matrix.pythonqtall-config }}" \ + CONFIG+=sanitize_undefined CONFIG+=generator CONFIG+=tests \ PYTHON_VERSION=${{ steps.versions.outputs.PYTHON_VERSION_SHORT }} \ PYTHON_DIR="$pythonLocation" \ PKGCONFIG+=$PYTHON_PKGCONFIG_NAME \ -r PythonQt.pro - make -j 2 - UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \ + make -j $(nproc) + PYTHONDEVMODE=1 PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=error PYTHONMALLOC=malloc_debug \ + UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \ make check TESTARGS="-platform offscreen" - name: Generate Wrappers @@ -216,10 +235,11 @@ jobs: run: | cd generator # workaround to allow to find the Qt include dirs for installed standard qt packages - QTDIR=-UNDEFINED- ./pythonqt_generator --include-paths=$Qt5_Dir/lib + UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \ + QTDIR=-UNDEFINED- ./pythonqt_generator --qt-version=${{ steps.versions.outputs.QT_VERSION_FULL }} --include-paths=$Qt5_Dir/lib - name: Upload Wrappers - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: wrappers_macos${{ steps.versions.outputs.MACOS_VERSION_SHORT }}_qt${{ steps.versions.outputs.QT_VERSION_SHORT }} path: generated_cpp @@ -229,10 +249,10 @@ jobs: fail-fast: false matrix: qt-arch: ['win64_mingw73'] - python-version: ['3.11'] + python-version: ['3.10'] qt-version: ['5.12.*'] python-arch: ['x64'] - pythonqtall-config: [''] + pythonqtall-config: ['PythonQtCore PythonQtGui PythonQtMultimedia'] # msvc-toolset: ['14.0'] include: # - qt-arch: 'win64_msvc2017_64' @@ -263,20 +283,21 @@ jobs: steps: - name: Checkout PythonQt - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Reset PATH - uses: egor-tensin/cleanup-path@v3 + uses: egor-tensin/cleanup-path@v4 - name: Install MSVC++ uses: ilammy/msvc-dev-cmd@v1 if: ${{ contains(matrix.qt-arch, 'msvc') }} with: - arch: ${{ matrix.python-arch }} + arch: amd64${{ contains(matrix.python-arch, 'x86') && '_x86' || '' }} - name: Install Qt uses: jurplel/install-qt-action@v3 with: + aqtversion: '==2.1.*' version: ${{ matrix.qt-version }} host: 'windows' target: 'desktop' @@ -286,7 +307,7 @@ jobs: tools: ${{ contains(matrix.qt-arch, 'mingw') && format('tools_mingw,qt.tools.{0}0', matrix.qt-arch) || '' }} - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '${{ matrix.python-version }}' architecture: ${{ matrix.python-arch }} @@ -317,11 +338,15 @@ jobs: run: | qmake -query python --version - qmake CONFIG+=release CONFIG-=debug_and_release CONFIG-=debug_and_release_target CONFIG+=generator CONFIG+=tests ^ + qmake CONFIG+=release CONFIG-=debug_and_release CONFIG-=debug_and_release_target ^ + CONFIG+=generator CONFIG+=tests ^ "PYTHONQTALL_CONFIG=${{ matrix.pythonqtall-config }}" ^ "PYTHON_PATH=%pythonLocation%" ^ "PYTHON_VERSION=${{ steps.versions.outputs.PYTHON_VERSION_SHORT }}" ^ PythonQt.pro + set PYTHONDEVMODE=1 + set PYTHONASYNCIODEBUG=1 + set PYTHONWARNINGS=error mingw32-make -j 2 && mingw32-make check "TESTARGS=-platform offscreen" ^ || nmake && nmake check "TESTARGS=-platform offscreen" @@ -333,7 +358,7 @@ jobs: pythonqt_generator - name: Upload Wrappers - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: wrappers_${{ matrix.qt-arch }}_${{ steps.versions.outputs.QT_VERSION_SHORT }} path: generated_cpp diff --git a/.github/workflows/build_latest.yml b/.github/workflows/build_latest.yml new file mode 100644 index 00000000..2b2a5d25 --- /dev/null +++ b/.github/workflows/build_latest.yml @@ -0,0 +1,126 @@ +name: Check generated_cpp + +on: + push: + branches: + - master + pull_request: + +defaults: + run: + shell: bash + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: ['ubuntu', 'windows'] + qt-version: [ '5.12.*', '5.15.*'] + python-version: [ '3.12' ] + runs-on: ${{ matrix.os }}-latest + steps: + + - name: Install MSVC + if: ${{ matrix.os == 'windows' }} + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: amd64 + + - name: Install Qt ${{matrix.qt-version}} + uses: jurplel/install-qt-action@v3 + with: + version: ${{ matrix.qt-version }} + modules: ${{startsWith(matrix.qt-version, '6') && 'qt5compat qtscxml qtpositioning qtwebchannel qtmultimedia qtwebengine' || '' }} + arch: ${{ matrix.os == 'ubuntu' && 'gcc_64' || (startsWith(matrix.qt-version, '5.12') && 'win64_msvc2017_64' || 'win64_msvc2019_64') }} + archives: "qtmultimedia qtbase qttools qtdeclarative ${{ matrix.os == 'windows' && 'qtwinextras' || 'qtlinuxextras qtwayland icu' }}" + + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: '${{ matrix.python-version }}' + + - name: Checkout PythonQt + uses: actions/checkout@v4 + + - name: Set environment + id: setenv + run: | + QT_VERSION_MAJOR=$(cut -f 1 -d . <<< "${{matrix.qt-version}}") + echo "QT_VERSION_MAJOR=$QT_VERSION_MAJOR" >> $GITHUB_ENV + QT_VERSION_SHORT=$(cut -f 1,2 -d . <<< "${{matrix.qt-version}}") + echo "QT_VERSION_SHORT=$QT_VERSION_SHORT" >> $GITHUB_OUTPUT + QTDIR=$(eval echo "\$Qt${QT_VERSION_MAJOR}_DIR") + PYTHON_VERSION_FULL=$(python --version 2>&1 | cut -f 2 -d ' ') + PYTHON_VERSION_SHORT=$(cut -f 1,2 -d . <<< $PYTHON_VERSION_FULL) + echo "PYTHON_VERSION_SHORT=$PYTHON_VERSION_SHORT" >> $GITHUB_OUTPUT + echo "QTDIR=$QTDIR" >> $GITHUB_ENV + echo "$QTDIR/bin" >> $GITHUB_PATH + echo "$pythonLocation/bin" >> $GITHUB_PATH + + - name: Build generator Ubuntu + shell: bash + if: ${{ matrix.os == 'ubuntu' }} + run: | + cd generator + qmake -r generator.pro CONFIG+=release CONFIG-=debug_and_release CONFIG+=force_debug_info \ + CONFIG+=sanitizer CONFIG+=sanitize_undefined CONFIG+=sanitize_address + make -j $(nproc) + + - name: Build generator Windows + shell: cmd + if: ${{ matrix.os == 'windows' }} + run: | + cd generator + qmake CONFIG+=release CONFIG-=debug_and_release CONFIG-=debug_and_release_target generator.pro + nmake + + - name: Generate Wrappers + shell: bash + run: | + cd generator + if [[ ${{ matrix.os }} == 'windows' && ${{ matrix.qt-version }} =~ '5.1' ]]; then export QTDIR=$Qt5_Dir; fi + UBSAN_OPTIONS="halt_on_error=1" \ + ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \ + ./pythonqt_generator + + - name: Upload Wrappers + uses: actions/upload-artifact@v4 + with: + name: wrappers_${{ matrix.os }}_${{ steps.setenv.outputs.QT_VERSION_SHORT }} + path: generated_cpp + + - name: Build PythonQt Ubuntu + if: ${{ matrix.os == 'ubuntu' }} + run: | + echo ======= SYSTEM INFO ======== + uname -a; gcc --version | grep "gcc"; python3 --version; qmake --version + echo ============================ + qmake -r PythonQt.pro CONFIG+=release CONFIG+=force_debug_info \ + PYTHONQTALL_CONFIG="${{ matrix.pythonqtall-config }}" \ + CONFIG+=sanitizer CONFIG+=sanitize_undefined CONFIG+=sanitize_address \ + PYTHON_VERSION=$(python3 --version | cut -d " " -f 2 | cut -d "." -f1,2) \ + PYTHON_DIR=$(which python3 | xargs dirname | xargs dirname) + make -j $(nproc) + PYTHONDEVMODE=1 PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=error PYTHONMALLOC=malloc_debug \ + UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \ + make check TESTARGS="-platform offscreen" + + - name: Build PythonQt Windows + shell: cmd + if: ${{ matrix.os == 'windows' }} + run: | + qmake -query + python --version + qmake CONFIG+=release CONFIG-=debug_and_release CONFIG-=debug_and_release_target ^ + CONFIG+=sanitizer CONFIG+=sanitize_address ^ + "PYTHONQTALL_CONFIG=${{ matrix.pythonqtall-config }}" ^ + "PYTHON_PATH=%pythonLocation%" ^ + "PYTHON_VERSION=${{ steps.setenv.outputs.PYTHON_VERSION_SHORT }}" ^ + PythonQt.pro + nmake + set PYTHONDEVMODE=1 + set PYTHONASYNCIODEBUG=1 + set PYTHONWARNINGS=error + set "ASAN_OPTIONS=detect_leaks=0:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" + nmake check "TESTARGS=-platform offscreen" diff --git a/.github/workflows/builddocs.yml b/.github/workflows/builddocs.yml new file mode 100644 index 00000000..2921d135 --- /dev/null +++ b/.github/workflows/builddocs.yml @@ -0,0 +1,32 @@ +name: Build and Deploy Documentation + +on: + push: + branches: + - master + +defaults: + run: + shell: bash + +jobs: + build_docs: + runs-on: ubuntu-latest + steps: + - name: Checkout master + uses: actions/checkout@v4 + + - name: Install Doxygen + run: sudo apt-get install doxygen graphviz -y + + - name: Build documentation + run: | + cd doxygen + doxygen + touch html/.nojekyll + cd .. + + - name: Deploy to GitHub Pages + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: doxygen/html diff --git a/.gitignore b/.gitignore index 6f89bbdd..86ea24a9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,13 +2,13 @@ src/object_script.* src/pkg-config_wrapper.sh src/.build/* *~ - - # Compiled Object files *.slo *.lo *.o *.obj +*.pdb +*.cbt # By mistake (in pkgConfigExecutable function in features/qt_functions.prf) # qmake generates NUL file on Linux in ./build subdir @@ -52,6 +52,8 @@ ui_*.h *.jsc Makefile* *build-* +*resource.rc +*.FileListAbsolute.txt # CMake CMakeLists.txt.user @@ -91,6 +93,9 @@ Thumbs.db # Visual Studio Code .vscode/* +*.tlog + + # JetBrains tools .idea/* diff --git a/.vimrc b/.vimrc new file mode 100644 index 00000000..d5a9f893 --- /dev/null +++ b/.vimrc @@ -0,0 +1 @@ +set expandtab diff --git a/README b/README deleted file mode 100644 index 9aeab3ee..00000000 --- a/README +++ /dev/null @@ -1,31 +0,0 @@ -PythonQt --------- - -PythonQt is a dynamic Python (http://www.python.org) binding for Qt (http://qt.nokia.com). -It offers an easy way to embed the Python scripting language into -your Qt applications. It makes heavy use of the QMetaObject system and thus requires Qt4.x. - -Licensing of PythonQt ---------------------- -PythonQt is distributed under the LGPL 2.1 license. - -Licensing of Generator ----------------------- -The build system of PythonQt makes use of a patched version of the LGPL'ed QtScript generator, located in the "generator" directory. - -See the LICENSE.LGPL file in the generator subdirectory for details. -Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies) - -See http://qt.gitorious.org/qt-labs/qtscriptgenerator for the original project. -The PythonQt wrappers generated by the generator are distributed under the LGPL as well. - -The generated wrappers are pre-generated and checked-in for Qt 4.6.1, so you only need to build and run the -generator when you want to build additional wrappers or you want to upgrade/downgrade to another Qt version, but this requires updating the typesystems as well. - -Documentation -------------- - -More documentation is available at: - -http://pythonqt.sourceforge.net/ - diff --git a/README.md b/README.md index c9d52a99..1d058e36 100644 --- a/README.md +++ b/README.md @@ -36,14 +36,12 @@ updating the typesystems as well. ## Building on Windows with MinGW To build PythonQt, you need to set the environment variable `PYTHON_PATH` to -point to the root dir of the python installation and `PYTHON_LIB` to point to -the directory where the python lib file is located. Then you should set the +point to the root dir of the python installation. Then you should set the `PYTHON_VERSION` variable to the Python version number. When using the prebuild Python installer, this will be: ```cmd -set PYTHON_PATH=c:\Python311 -set PYTHON_LIB=c:\Python311\libs -set PYTHON_VERSION=3.11 +set PYTHON_PATH=c:\Python310 +set PYTHON_VERSION=3.10 ``` diff --git a/build/common.prf b/build/common.prf index 6eccc89d..4bc64ae7 100644 --- a/build/common.prf +++ b/build/common.prf @@ -36,20 +36,22 @@ PYTHONQT_GENERATED_PATH = $$PWD/../generated_cpp !exists($$PYTHONQT_GENERATED_PATH) { PYTHONQT_GENERATED_PATH = $$PWD/../generated_cpp_UNSUPPORTED_QT_VERSION - error(Unsupported Qt version for PythonQt: missing generated_cpp) + error("No generated sources exist for Qt$${QT_VERSION}") } greaterThan(QT_MAJOR_VERSION, 5) | greaterThan(QT_MINOR_VERSION, 9): CONFIG += c++11 -win32: CONFIG += skip_target_version_ext -gcc|win32-clang-msvc:QMAKE_CXXFLAGS += -Wno-deprecated-declarations -Wuninitialized -Winit-self -ansi -pedantic -win32-clang-msvc:QMAKE_CXXFLAGS += -Wno-unused-command-line-argument -#Do not issue warning to system includes -gcc:!isEmpty(QT_INSTALL_HEADERS): QMAKE_CXXFLAGS += -isystem $$[QT_INSTALL_HEADERS] + +!build_pass { + message("Qt version: Qt$${QT_VERSION}") + message("Using generated sources files from $${PYTHONQT_GENERATED_PATH}") +} + QMAKE_CXXFLAGS -= -Werror -Werror=pedantic -pedantic-errors -Werror=write-strings + clang:QMAKE_CXXFLAGS += -Wno-error -Wno-error=sometimes-uninitialized -Wno-unused-variable -Wno-sign-compare -Wno-error=unreachable-code -QMAKE_CXXFLAGS += -Wno-cast-qual \ +if(clang|gcc):QMAKE_CXXFLAGS += -Wno-cast-qual \ -Wno-conversion-null \ -Wno-sign-compare -Wno-unused-parameter -Wno-error=type-limits \ -Wno-error=parentheses -Wno-deprecated-declarations @@ -63,3 +65,9 @@ defined(enableFlagIfCan) { #Mingw 4.9 complains... win32:gcc:QMAKE_CXXFLAGS += -Wno-error=redundant-decls +win32: CONFIG += skip_target_version_ext +gcc|win32-clang-msvc:QMAKE_CXXFLAGS += -Wno-deprecated-declarations -Wuninitialized -Winit-self -pedantic -ansi +win32-clang-msvc:QMAKE_CXXFLAGS += -Wno-unused-command-line-argument +#Do not issue warning to system includes +gcc:!isEmpty(QT_INSTALL_HEADERS): QMAKE_CXXFLAGS += -isystem $$[QT_INSTALL_HEADERS] + diff --git a/build_python b/build_python deleted file mode 100644 index ccbf79a8..00000000 --- a/build_python +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -uevo pipefail -git submodule update --init python.git -cat >> python.git/Modules/Setup << EOF -*shared* -*static* -_random _randommodule.c # Random number generator -_datetime _datetimemodule.c # datetime accelerator -_struct _struct.c # binary structure packing/unpacking -math mathmodule.c _math.c # -lm # math library functions, e.g. sin -_csv _csv.c -EOF -cd python.git -./configure --disable-shared --disable-optimizations --disable-ipv6 --prefix=$PWD/../install.dir LDFLAGS="--static" -make LDFLAGS="-static" LINKFORSHARED=" " - diff --git a/extensions/PythonQt_QtAll/PythonQt_QtAll.pro b/extensions/PythonQt_QtAll/PythonQt_QtAll.pro index 09a4650b..1bae3cea 100644 --- a/extensions/PythonQt_QtAll/PythonQt_QtAll.pro +++ b/extensions/PythonQt_QtAll/PythonQt_QtAll.pro @@ -11,13 +11,17 @@ isEmpty( PYTHONQTALL_CONFIG ) { qtHaveModule(svg):CONFIG += PythonQtSvg qtHaveModule(sql):CONFIG += PythonQtSql qtHaveModule(network):CONFIG += PythonQtNetwork - qtHaveModule(opengl):CONFIG += PythonQtOpengl + lessThan(QT_MAJOR_VERSION, 6) { + # module is empty in Qt6 + qtHaveModule(opengl):CONFIG += PythonQtOpengl + } qtHaveModule(xml):CONFIG += PythonQtXml qtHaveModule(xmlpatterns):CONFIG += PythonQtXmlpatterns qtHaveModule(multimedia):CONFIG += PythonQtMultimedia qtHaveModule(qml):CONFIG += PythonQtQml qtHaveModule(quick):CONFIG += PythonQtQuick qtHaveModule(uitools):CONFIG += PythonQtUiTools + qtHaveModule(webenginewidgets):CONFIG += PythonQtWebEngineWidgets qtHaveModule(webkit):CONFIG += PythonQtWebKit } else { @@ -35,7 +39,7 @@ include ( ../../build/common.prf ) include ( ../../build/PythonQt.prf ) TARGET = $$replace(TARGET, PythonXY, Python$${PYTHON_VERSION}) -CONFIG += qt strict_c++ +CONFIG += qt strict_c++ msvc_mp !static:!staticlib { CONFIG += dll @@ -58,7 +62,7 @@ unix { QMAKE_PKGCONFIG_PREFIX = $$INSTALLBASE QMAKE_PKGCONFIG_LIBDIR = $$target.path QMAKE_PKGCONFIG_INCDIR = $$headers.path - QMAKE_PKGCONFIG_INCDIR += $$PREFIX/include/trikPythonQt + QMAKE_PKGCONFIG_INCDIR += $$PREFIX/include/trikPythonQt$${QT_MAJOR_VERSION} QMAKE_PKGCONFIG_VERSION = $$VERSION } @@ -77,63 +81,88 @@ defineTest(Xinclude) { } -PythonQtCore:Xinclude (com_trolltech_qt_core) { +PythonQtCore { DEFINES += PYTHONQT_WITH_CORE + Xinclude (com_trolltech_qt_core) QT += core } -PythonQtGui:Xinclude (com_trolltech_qt_gui) { +PythonQtGui { DEFINES += PYTHONQT_WITH_GUI + Xinclude (com_trolltech_qt_gui) QT += gui widgets printsupport } -PythonQtSvg:Xinclude (com_trolltech_qt_svg) { +PythonQtSvg { DEFINES += PYTHONQT_WITH_SVG + Xinclude (com_trolltech_qt_svg) QT +=svg + !lessThan(QT_MAJOR_VERSION,6): QT += svgwidgets } -PythonQtSql:Xinclude (com_trolltech_qt_sql) { +PythonQtSql { DEFINES += PYTHONQT_WITH_SQL + Xinclude (com_trolltech_qt_sql) QT += sql } -PythonQtNetwork:Xinclude (com_trolltech_qt_network) { +PythonQtNetwork { DEFINES += PYTHONQT_WITH_NETWORK + Xinclude (com_trolltech_qt_network) QT += network } -PythonQtOpengl:PythonQtCore: Xinclude (com_trolltech_qt_opengl) { +PythonQtOpengl { DEFINES += PYTHONQT_WITH_OPENGL QT += opengl + PythonQtCore: Xinclude (com_trolltech_qt_opengl) QT += xml } -PythonQtXmlpatterns:Xinclude (com_trolltech_qt_xmlpatterns) { +PythonQtXml { + DEFINES += PYTHONQT_WITH_XML + Xinclude (com_trolltech_qt_xml) + QT += xml +} + +PythonQtXmlpatterns { DEFINES += PYTHONQT_WITH_XMLPATTERNS + Xinclude (com_trolltech_qt_xmlpatterns) QT += xmlpatterns } -PythonQtMultimedia:Xinclude (com_trolltech_qt_multimedia) { +PythonQtMultimedia { DEFINES += PYTHONQT_WITH_MULTIMEDIA + Xinclude (com_trolltech_qt_multimedia) QT += multimedia multimediawidgets } -PythonQtQml:Xinclude (com_trolltech_qt_qml) { +PythonQtQml { DEFINES += PYTHONQT_WITH_QML + Xinclude (com_trolltech_qt_qml) QT += qml } -PythonQtQuick:Xinclude (com_trolltech_qt_quick) { +PythonQtQuick { DEFINES += PYTHONQT_WITH_QUICK + Xinclude (com_trolltech_qt_quick) QT += quick quickwidgets } -PythonQtUiTools:Xinclude (com_trolltech_qt_uitools) { +PythonQtUiTools { DEFINES += PYTHONQT_WITH_UITOOLS + Xinclude (com_trolltech_qt_uitools) QT += uitools } -PythonQtWebKit:Xinclude (com_trolltech_qt_webkit) { +PythonQtWebEngineWidgets { + DEFINES += PYTHONQT_WITH_WEBENGINEWIDGETS + Xinclude (com_trolltech_qt_webenginewidgets) + QT += webenginewidgets +} + +PythonQtWebKit { DEFINES += PYTHONQT_WITH_WEBKIT + Xinclude (com_trolltech_qt_webkit) QT += webkit webkitwidgets } diff --git a/generator/abstractmetabuilder.cpp b/generator/abstractmetabuilder.cpp index e7cb889b..4a886c42 100644 --- a/generator/abstractmetabuilder.cpp +++ b/generator/abstractmetabuilder.cpp @@ -391,6 +391,28 @@ void AbstractMetaBuilder::sortLists() } } +AbstractMetaClass* AbstractMetaBuilder::getGlobalNamespace(const TypeEntry* typeEntry) +{ + QString package = typeEntry->javaPackage(); + QString globalName = TypeDatabase::globalNamespaceClassName(typeEntry); + + AbstractMetaClass* global = m_meta_classes.findClass(package + "." + globalName); + if (!global) { + ComplexTypeEntry* gte = new NamespaceTypeEntry(globalName); + gte->setTargetLangPackage(typeEntry->javaPackage()); + gte->setCodeGeneration(typeEntry->codeGeneration()); + global = createMetaClass(); + global->setIsGlobalNamespace(true); + global->setTypeEntry(gte); + *global += AbstractMetaAttributes::Final; + *global += AbstractMetaAttributes::Public; + *global += AbstractMetaAttributes::Fake; + + m_meta_classes << global; + } + return global; +} + bool AbstractMetaBuilder::build() { Q_ASSERT(!m_file_name.isEmpty()); @@ -430,6 +452,15 @@ bool AbstractMetaBuilder::build() // Start the generation... + + // First automatically add all enums marked as QEnum into the TypeDatabase + // (if they don't contain an entry already). If there is an QEnum entry, + // the enum is obviously meant for scripting. + for (ClassModelItem item : typeMap.values()) { + autoAddQEnumsForClassItem(item); + } + + for (ClassModelItem item : typeMap.values()) { AbstractMetaClass *cls = traverseClass(item); addAbstractMetaClass(cls); @@ -451,36 +482,21 @@ bool AbstractMetaBuilder::build() AbstractMetaEnum *meta_enum = traverseEnum(item, 0, QSet()); if (meta_enum) { - QString package = meta_enum->typeEntry()->javaPackage(); - QString globalName = TypeDatabase::globalNamespaceClassName(meta_enum->typeEntry()); - - AbstractMetaClass *global = m_meta_classes.findClass(package + "." + globalName); - if (!global) { - ComplexTypeEntry *gte = new ObjectTypeEntry(globalName); - gte->setTargetLangPackage(meta_enum->typeEntry()->javaPackage()); - gte->setCodeGeneration(meta_enum->typeEntry()->codeGeneration()); - global = createMetaClass(); - global->setTypeEntry(gte); - *global += AbstractMetaAttributes::Final; - *global += AbstractMetaAttributes::Public; - *global += AbstractMetaAttributes::Fake; - - m_meta_classes << global; - } + AbstractMetaClass* global = getGlobalNamespace(meta_enum->typeEntry()); global->addEnum(meta_enum); - meta_enum->setEnclosingClass(global); - meta_enum->typeEntry()->setQualifier(globalName); // Global enums should be public despite not having public // identifiers so we'll fix the original attributes here. meta_enum->setOriginalAttributes(meta_enum->attributes()); - } - + // global enums have their own include + if (meta_enum->typeEntry()->include().isValid()) { + global->typeEntry()->addExtraInclude(meta_enum->typeEntry()->include()); + } + } } - // Go through all typedefs to see if we have defined any // specific typedefs to be used as classes. TypeAliasList typeAliases = m_dom->typeAliases(); @@ -593,7 +609,6 @@ bool AbstractMetaBuilder::build() } } - figureOutEnumValues(); checkFunctionModifications(); for (AbstractMetaClass *cls : m_meta_classes) { @@ -609,6 +624,33 @@ bool AbstractMetaBuilder::build() return true; } +void AbstractMetaBuilder::autoAddQEnumsForClassItem(ClassModelItem class_item) +{ + // also do this for sub-classes: + for (ClassModelItem sub_class : class_item->classMap().values()) { + autoAddQEnumsForClassItem(sub_class); + } + + auto qEnumDeclarations = class_item->qEnumDeclarations(); + for (EnumModelItem enum_item : class_item->enumMap().values()) { + if (enum_item) { + const auto& names = enum_item->qualifiedName(); + QString qualified_name = names.join("::"); + QString enum_name = enum_item->name(); + + bool hasQEnumDeclaration = qEnumDeclarations.contains(qualified_name) + || qEnumDeclarations.contains(enum_name); + + TypeEntry* type_entry = TypeDatabase::instance()->findType(qualified_name); + if (hasQEnumDeclaration && !type_entry) { + // automatically add enum type declared as Q_ENUM + type_entry = new EnumTypeEntry(QStringList(names.mid(0, names.size() - 1)).join("::"), names.last()); + TypeDatabase::instance()->addType(type_entry); + } + } + } +} + void AbstractMetaBuilder::addAbstractMetaClass(AbstractMetaClass *cls) { @@ -664,10 +706,20 @@ AbstractMetaClass *AbstractMetaBuilder::traverseNamespace(NamespaceModelItem nam .arg(meta_class->package()) .arg(namespace_item->name())); - traverseEnums(model_dynamic_cast(namespace_item), meta_class, namespace_item->enumsDeclarations()); + traverseEnums(model_dynamic_cast(namespace_item), meta_class, namespace_item->qEnumDeclarations()); traverseFunctions(model_dynamic_cast(namespace_item), meta_class); // traverseClasses(model_dynamic_cast(namespace_item)); + // collect all include files (since namespace items might come from different files) + QSet includeFiles; + for (const auto& item : namespace_item->enums()) { + includeFiles.insert(item->fileName()); + } + for (const auto& item : namespace_item->functions()) { + includeFiles.insert(item->fileName()); + } + // (should we do this for typeAliases and inner namespaces too?) + pushScope(model_dynamic_cast(namespace_item)); m_namespace_prefix = currentScope()->qualifiedName().join("::"); @@ -676,6 +728,7 @@ AbstractMetaClass *AbstractMetaBuilder::traverseNamespace(NamespaceModelItem nam for (ClassModelItem cls : classes) { AbstractMetaClass *mjc = traverseClass(cls); addAbstractMetaClass(mjc); + includeFiles.insert(cls->fileName()); } // Go through all typedefs to see if we have defined any @@ -705,225 +758,13 @@ AbstractMetaClass *AbstractMetaBuilder::traverseNamespace(NamespaceModelItem nam QFileInfo info(namespace_item->fileName()); type->setInclude(Include(Include::IncludePath, info.fileName())); } - - return meta_class; -} - -struct Operator -{ - enum Type { Plus, ShiftLeft, None }; - - Operator() : type(None) { } - - int calculate(int x) { - switch (type) { - case Plus: return x + value; - case ShiftLeft: return x << value; - case None: return x; - } - return x; - } - - Type type; - int value; -}; - - - -Operator findOperator(QString *s) { - const char *names[] = { - "+", - "<<" - }; - - for (int i=0; i 0) { - bool ok; - QString right = str.mid(splitPoint + name.length()); - Operator op; - op.value = right.toInt(&ok); - if (ok) { - op.type = Operator::Type(i); - *s = str.left(splitPoint).trimmed(); - return op; - } - } - } - return Operator(); -} - -int AbstractMetaBuilder::figureOutEnumValue(const QString &stringValue, - int oldValuevalue, - AbstractMetaEnum *meta_enum, - AbstractMetaFunction *meta_function) -{ - Q_UNUSED(meta_function) - if (stringValue.isEmpty()) - return oldValuevalue; - - QStringList stringValues = stringValue.split("|"); - - int returnValue = 0; - - bool matched = false; - - for (int i=0; i 0 && s.at(0) == QLatin1Char('0')) - v = s.toUInt(&ok, 0); - else - v = s.toInt(&ok); - - if (ok) { - matched = true; - - } else if (m_enum_values.contains(s)) { - v = m_enum_values[s]->value(); - matched = true; - - } else { - AbstractMetaEnumValue *ev = 0; - - if (meta_enum && (ev = meta_enum->values().find(s))) { - v = ev->value(); - matched = true; - - } else if (meta_enum && (ev = meta_enum->enclosingClass()->findEnumValue(s, meta_enum))) { - v = ev->value(); - matched = true; - - } else { - /* - if (meta_enum) - ReportHandler::warning("unhandled enum value: " + s + " in " - + meta_enum->enclosingClass()->name() + "::" - + meta_enum->name()); - else - ReportHandler::warning("unhandled enum value: Unknown enum"); - */ - } - } - - if (matched) - returnValue |= op.calculate(v); - } - - if (!matched) { - /* not helpful... - QString warn = QString("unmatched enum %1").arg(stringValue); - - if (meta_function != 0) { - warn += QString(" when parsing default value of '%1' in class '%2'") - .arg(meta_function->name()) - .arg(meta_function->implementingClass()->name()); - } - - ReportHandler::warning(warn); - */ - returnValue = oldValuevalue; - } - - return returnValue; -} - -void AbstractMetaBuilder::figureOutEnumValuesForClass(AbstractMetaClass *meta_class, - QSet *classes) -{ - AbstractMetaClass *base = meta_class->baseClass(); - - if (base != 0 && !classes->contains(base)) - figureOutEnumValuesForClass(base, classes); - - if (classes->contains(meta_class)) - return; - - AbstractMetaEnumList enums = meta_class->enums(); - for (AbstractMetaEnum *e : enums) { - if (!e) { - ReportHandler::warning("bad enum in class " + meta_class->name()); - continue; - } - AbstractMetaEnumValueList lst = e->values(); - int value = 0; - for (int i=0; istringValue(), value, e); - lst.at(i)->setValue(value); - value++; - } - - // Check for duplicate values... - EnumTypeEntry *ete = e->typeEntry(); - if (!ete->forceInteger()) { - QHash entries; - for (AbstractMetaEnumValue *v : lst) { - - bool vRejected = ete->isEnumValueRejected(v->name()); - - AbstractMetaEnumValue *current = entries.value(v->value()); - if (current) { - bool currentRejected = ete->isEnumValueRejected(current->name()); - if (!currentRejected && !vRejected) { - /* Removed because I don't see the sense of rejecting duplicate values... - ReportHandler::warning( - QString("duplicate enum values: %1::%2, %3 and %4 are %5, already rejected: (%6)") - .arg(meta_class->name()) - .arg(e->name()) - .arg(v->name()) - .arg(entries[v->value()]->name()) - .arg(v->value()) - .arg(ete->enumValueRejections().join(", "))); - continue; - */ - } - } - - if (!vRejected) - entries[v->value()] = v; - } - - // Entries now contain all the original entries, no - // rejected ones... Use this to generate the enumValueRedirection table. - for (AbstractMetaEnumValue *reject : lst) { - if (!ete->isEnumValueRejected(reject->name())) - continue; - - AbstractMetaEnumValue *used = entries.value(reject->value()); - if (!used) { - ReportHandler::warning( - QString::fromLatin1("Rejected enum has no alternative...: %1::%2") - .arg(meta_class->name()) - .arg(reject->name())); - continue; - } - ete->addEnumValueRedirection(reject->name(), used->name()); - } - - } + // namespace items might come from different include files: + for (const QString& oneIncludeFile : includeFiles) { + QFileInfo info(oneIncludeFile); + type->addExtraInclude(Include(Include::IncludePath, info.fileName())); } - - - *classes += meta_class; -} - - -void AbstractMetaBuilder::figureOutEnumValues() -{ - // Keep a set of classes that we already traversed. We use this to - // enforce that we traverse base classes prior to subclasses. - QSet classes; - for (AbstractMetaClass *c : m_meta_classes) { - figureOutEnumValuesForClass(c, &classes); - } + return meta_class; } @@ -956,6 +797,8 @@ AbstractMetaEnum *AbstractMetaBuilder::traverseEnum(EnumModelItem enum_item, Abs return 0; } + static_cast(type_entry)->setEnumClass(enum_item->isEnumClass()); + AbstractMetaEnum *meta_enum = createMetaEnum(); if ( enumsDeclarations.contains(qualified_name) || enumsDeclarations.contains(enum_name)) { @@ -1106,12 +949,12 @@ AbstractMetaClass *AbstractMetaBuilder::traverseClass(ClassModelItem class_item) meta_class->setTemplateArguments(template_args); meta_class->setHasActualDeclaration(class_item->hasActualDeclaration()); - parseQ_Property(meta_class, class_item->propertyDeclarations()); - traverseFunctions(model_dynamic_cast(class_item), meta_class); - traverseEnums(model_dynamic_cast(class_item), meta_class, class_item->enumsDeclarations()); + traverseEnums(model_dynamic_cast(class_item), meta_class, class_item->qEnumDeclarations()); traverseFields(model_dynamic_cast(class_item), meta_class); + parseQ_Property(meta_class, class_item->propertyDeclarations()); + // Inner classes { QList inner_classes = class_item->classMap().values(); @@ -1273,8 +1116,15 @@ void AbstractMetaBuilder::traverseFunctions(ScopeModelItem scope_item, AbstractM bool isInvalidDestructor = meta_function->isDestructor() && meta_function->isPrivate(); bool isInvalidConstructor = meta_function->isConstructor() && (meta_function->isPrivate() || meta_function->isInvalid()); + if (isInvalidConstructor && meta_function->arguments().size() == 1 && + meta_class->qualifiedCppName() == meta_function->arguments().at(0)->type()->typeEntry()->qualifiedCppName()) + { + // deleted or private copy constructor, it seems copying is not allowed + meta_class->typeEntry()->setNoCopy(true); + } if ((isInvalidDestructor || isInvalidConstructor) - && !meta_class->hasNonPrivateConstructor()) { + && !meta_class->hasNonPrivateConstructor()) + { *meta_class += AbstractMetaAttributes::Final; } else if (meta_function->isConstructor() && !meta_function->isPrivate()) { *meta_class -= AbstractMetaAttributes::Final; @@ -1323,6 +1173,48 @@ void AbstractMetaBuilder::traverseFunctions(ScopeModelItem scope_item, AbstractM } } } + removeEquivalentFunctions(meta_class); +} + +void AbstractMetaBuilder::removeEquivalentFunctions(AbstractMetaClass* parent) +{ + AbstractMetaFunctionList functions = parent->functions(); + for (AbstractMetaFunction* fun : functions) + { + AbstractMetaArgumentList args = fun->arguments(); + bool candidateToRemove = false; + for (AbstractMetaArgument* arg : args) { + const TypeEntry* argType = arg->type()->typeEntry(); + if (argType && argType->equivalentType()) { + candidateToRemove = true; + break; + } + } + if (!candidateToRemove) { + continue; + } + // check if there are other functions with the same name and equivalent parameters + AbstractMetaFunctionList overloadedFunctions = parent->queryFunctionsByName(fun->name()); + for (AbstractMetaFunction* overload : overloadedFunctions) { + if (overload != fun) { + AbstractMetaArgumentList overloadArgs = overload->arguments(); + if (overloadArgs.size() == args.size()) { + bool equivalentArgs = true; + for (int i = 0; i < args.size() && equivalentArgs; i++) { + const TypeEntry* argType = args[i]->type()->typeEntry(); + const TypeEntry* overloadArgType = overloadArgs[i]->type()->typeEntry(); + // This could have some more equivalency checks, but currently this seems to be sufficient + equivalentArgs = (argType && overloadArgType && + (argType == overloadArgType || argType->equivalentType() == overloadArgType)); + } + if (equivalentArgs) { + parent->removeFunction(fun); + break; + } + } + } + } + } } bool AbstractMetaBuilder::setupInheritance(AbstractMetaClass *meta_class) @@ -1449,17 +1341,11 @@ bool AbstractMetaBuilder::setupInheritance(AbstractMetaClass *meta_class) return true; } -void AbstractMetaBuilder::traverseEnums(ScopeModelItem scope_item, AbstractMetaClass *meta_class, const QStringList &enumsDeclarations) +void AbstractMetaBuilder::traverseEnums(ScopeModelItem scope_item, AbstractMetaClass *meta_class, const QSet &qEnumDeclarations) { EnumList enums = scope_item->enums(); for (EnumModelItem enum_item : enums) { - AbstractMetaEnum *meta_enum = traverseEnum(enum_item, meta_class, -#if QT_VERSION < QT_VERSION_CHECK(5,14,0) - QSet::fromList(enumsDeclarations) -#else - QSet(enumsDeclarations.begin(), enumsDeclarations.end()) -#endif - ); + AbstractMetaEnum* meta_enum = traverseEnum(enum_item, meta_class, qEnumDeclarations); if (meta_enum) { meta_enum->setOriginalAttributes(meta_enum->attributes()); meta_class->addEnum(meta_enum); @@ -1604,11 +1490,6 @@ AbstractMetaFunction *AbstractMetaBuilder::traverseFunction(FunctionModelItem fu meta_function->setFunctionType(AbstractMetaFunction::SlotFunction); } - if (function_item->isDeleted()) { - meta_function->setInvalid(true); - return meta_function; - } - ArgumentList arguments = function_item->arguments(); AbstractMetaArgumentList meta_arguments; @@ -1664,7 +1545,7 @@ AbstractMetaFunction *AbstractMetaBuilder::traverseFunction(FunctionModelItem fu } } - // If we where not able to translate the default argument make it + // If we were not able to translate the default argument make it // reset all default arguments before this one too. for (int i=0; isetDefaultValueExpression(QString()); @@ -1676,6 +1557,11 @@ AbstractMetaFunction *AbstractMetaBuilder::traverseFunction(FunctionModelItem fu } } + if (function_item->isDeleted()) { + meta_function->setInvalid(true); + return meta_function; + } + return meta_function; } @@ -1717,7 +1603,7 @@ AbstractMetaType *AbstractMetaBuilder::translateType(const TypeInfo &_typei, boo return 0; } - TypeParser::Info typeInfo = TypeParser::parse(typei.toString()); + TypeParser::Info typeInfo = TypeParser::parse(typei.toString(/*parsable=*/true)); if (typeInfo.is_busted) { *ok = false; return 0; @@ -1735,7 +1621,6 @@ AbstractMetaType *AbstractMetaBuilder::translateType(const TypeInfo &_typei, boo //newInfo.setArguments(typei.arguments()); newInfo.setIndirections(typei.indirections()); newInfo.setConstant(typei.isConstant()); - newInfo.setConstexpr(typei.isConstexpr()); newInfo.setFunctionPointer(typei.isFunctionPointer()); newInfo.setQualifiedName(typei.qualifiedName()); newInfo.setReference(typei.isReference()); @@ -2233,11 +2118,33 @@ void AbstractMetaBuilder::parseQ_Property(AbstractMetaClass *meta_class, const Q QStringList qualifiedScopeName = currentScope()->qualifiedName(); bool ok = false; AbstractMetaType *type = 0; - QString scope; + int pIndex = 0; + QString typeName = l.value(pIndex++); + bool isConst = false; + if (typeName == "const") { + // use the next part as the type name + typeName = l.value(pIndex++); + isConst = true; + } + QString propertyName = l.value(pIndex++); + QString modifiers; + while (typeName.endsWith("*") || typeName.endsWith("&")) { + modifiers.insert(0, typeName.at(typeName.length() - 1)); + typeName.chop(1); + } + while (propertyName.startsWith("*") || propertyName.startsWith("&")) { + modifiers.append(propertyName.at(0)); + propertyName.remove(0, 1); + if (propertyName.isEmpty() && pIndex < l.size()) { + propertyName = l.value(pIndex++); + } + } for (int j=qualifiedScopeName.size(); j>=0; --j) { - scope = j > 0 ? QStringList(qualifiedScopeName.mid(0, j)).join("::") + "::" : QString(); + QStringList scope(qualifiedScopeName.mid(0, j)); TypeInfo info; - info.setQualifiedName((scope + l.at(0)).split("::")); + info.setIndirections(modifiers.count('*')); + info.setReference(modifiers.contains('&')); // r-value reference seems improbable for a property... + info.setQualifiedName(scope + QStringList(typeName)); type = translateType(info, &ok); if (type != 0 && ok) { @@ -2246,18 +2153,16 @@ void AbstractMetaBuilder::parseQ_Property(AbstractMetaClass *meta_class, const Q } if (type == 0 || !ok) { - ReportHandler::warning(QString("Unable to decide type of property: '%1' in class '%2'") - .arg(l.at(0)).arg(meta_class->name())); + ReportHandler::warning(QString("Unable to decide type '%1' of property '%2' in class '%3'") + .arg(typeName).arg(propertyName).arg(meta_class->name())); continue; } - QString typeName = scope + l.at(0); - QPropertySpec *spec = new QPropertySpec(type->typeEntry()); - spec->setName(l.at(1)); + spec->setName(propertyName); spec->setIndex(i); - for (int pos=2; pos+1setRead(l.at(pos+1)); else if (l.at(pos) == QLatin1String("WRITE")) diff --git a/generator/abstractmetabuilder.h b/generator/abstractmetabuilder.h index 13ddf285..abaf900c 100644 --- a/generator/abstractmetabuilder.h +++ b/generator/abstractmetabuilder.h @@ -82,9 +82,7 @@ class AbstractMetaBuilder bool build(); - void figureOutEnumValuesForClass(AbstractMetaClass *meta_class, QSet *classes); - int figureOutEnumValue(const QString &name, int value, AbstractMetaEnum *meta_enum, AbstractMetaFunction *meta_function = 0); - void figureOutEnumValues(); + void autoAddQEnumsForClassItem(ClassModelItem item); void addAbstractMetaClass(AbstractMetaClass *cls); AbstractMetaClass *traverseTypeAlias(TypeAliasModelItem item); @@ -92,12 +90,16 @@ class AbstractMetaBuilder bool setupInheritance(AbstractMetaClass *meta_class); AbstractMetaClass *traverseNamespace(NamespaceModelItem item); AbstractMetaEnum *traverseEnum(EnumModelItem item, AbstractMetaClass *enclosing, const QSet &enumsDeclarations); - void traverseEnums(ScopeModelItem item, AbstractMetaClass *parent, const QStringList &enumsDeclarations); + void traverseEnums(ScopeModelItem item, AbstractMetaClass *parent, const QSet &enumsDeclarations); void traverseFunctions(ScopeModelItem item, AbstractMetaClass *parent); void traverseFields(ScopeModelItem item, AbstractMetaClass *parent); void traverseStreamOperator(FunctionModelItem function_item); void traverseCompareOperator(FunctionModelItem item); void traverseBinaryArithmeticOperator(FunctionModelItem item); + + //! remove functions/methods that are overloads with equivalent parameter types + //! when called from Python + void removeEquivalentFunctions(AbstractMetaClass* parent); AbstractMetaFunction *traverseFunction(FunctionModelItem function); AbstractMetaField *traverseField(VariableModelItem field, const AbstractMetaClass *cls); @@ -147,6 +149,8 @@ class AbstractMetaBuilder private: void sortLists(); + AbstractMetaClass* getGlobalNamespace(const TypeEntry* typeEntry); + QString m_file_name; AbstractMetaClassList m_meta_classes; diff --git a/generator/abstractmetalang.cpp b/generator/abstractmetalang.cpp index 45703f84..b8c0cd33 100644 --- a/generator/abstractmetalang.cpp +++ b/generator/abstractmetalang.cpp @@ -368,8 +368,6 @@ QString AbstractMetaFunction::signature() const if (isConstant()) s += " const"; - if (isConstexpr()) - s += " constexpr"; return s; } @@ -661,8 +659,6 @@ QString AbstractMetaFunction::minimalSignature() const minimalSignature += ")"; if (isConstant()) minimalSignature += "const"; - if (isConstexpr()) - minimalSignature += "constexpr"; minimalSignature = TypeSystem::normalizedSignature(minimalSignature.toLocal8Bit().constData()); m_cached_minimal_signature = minimalSignature; @@ -1107,6 +1103,11 @@ void AbstractMetaClass::addFunction(AbstractMetaFunction *function) m_has_nonpublic |= !function->isPublic(); } +void AbstractMetaClass::removeFunction(AbstractMetaFunction* function) +{ + m_functions.removeOne(function); +} + bool AbstractMetaClass::hasSignal(const AbstractMetaFunction *other) const { if (!other->isSignal()) diff --git a/generator/abstractmetalang.h b/generator/abstractmetalang.h index f161c5c7..560b8169 100644 --- a/generator/abstractmetalang.h +++ b/generator/abstractmetalang.h @@ -676,6 +676,7 @@ class AbstractMetaClass : public AbstractMetaAttributes m_has_clone_operator(false), m_is_type_alias(false), m_has_actual_declaration(false), + m_is_global_namespace(false), m_qDebug_stream_function(0) { } @@ -688,6 +689,7 @@ class AbstractMetaClass : public AbstractMetaAttributes AbstractMetaFunctionList functions() const { return m_functions; } void setFunctions(const AbstractMetaFunctionList &functions); void addFunction(AbstractMetaFunction *function); + void removeFunction(AbstractMetaFunction* function); bool hasFunction(const AbstractMetaFunction *f) const; bool hasFunction(const QString &str) const; bool hasSignal(const AbstractMetaFunction *f) const; @@ -800,6 +802,9 @@ class AbstractMetaClass : public AbstractMetaAttributes void setHasActualDeclaration(bool on) { m_has_actual_declaration = on; } bool hasActualDeclaration() const { return m_has_actual_declaration; } + void setIsGlobalNamespace(bool on) { m_is_global_namespace = on; } + bool isGlobalNamespace() const { return m_is_global_namespace; } + QString getDefaultNonZeroFunction() const; void addPropertySpec(QPropertySpec *spec) { m_property_specs << spec; } @@ -861,7 +866,8 @@ class AbstractMetaClass : public AbstractMetaAttributes uint m_has_clone_operator :1; uint m_is_type_alias : 1; uint m_has_actual_declaration : 1; - uint m_reserved : 17; + uint m_is_global_namespace : 1; + uint m_reserved : 16; QString m_destructor_exception; const AbstractMetaClass *m_enclosing_class{}; diff --git a/generator/generator.pri b/generator/generator.pri index 332d03ab..0127b292 100644 --- a/generator/generator.pri +++ b/generator/generator.pri @@ -5,6 +5,7 @@ TEMPLATE = app #CONFIG += cmdline -- does not work as expected with old Qt versions, f.e. is missing in 5.9 CONFIG += console CONFIG -= app_bundle +CONFIG += msvc_mp TARGET += DEPENDPATH += $$GENERATORPATH tests parser @@ -37,7 +38,6 @@ win32-clang-msvc:QMAKE_CXXFLAGS += -Wno-language-extension-token -Wno-microsoft- # Input HEADERS += \ $$GENERATORPATH/generator.h \ - $$GENERATORPATH/main.h \ $$GENERATORPATH/reporthandler.h \ $$GENERATORPATH/typeparser.h \ $$GENERATORPATH/typesystem.h \ diff --git a/generator/main.cpp b/generator/main.cpp index b75f06c7..7cb3b79d 100644 --- a/generator/main.cpp +++ b/generator/main.cpp @@ -40,20 +40,181 @@ ****************************************************************************/ #include +#include -#include "main.h" #include "asttoxml.h" #include "reporthandler.h" #include "typesystem.h" #include "generatorset.h" #include "fileout.h" #include "control.h" +#include "pp.h" #include +#include +#include +#include +#include void displayHelp(GeneratorSet *generatorSet); -#include +namespace +{ + + QStringList getIncludeDirectories(const QString &commandLineIncludes) + { + QStringList includes; + includes << QString("."); + + QChar pathSplitter = QDir::listSeparator(); + + // Environment PYTHONQT_INCLUDE + QString includePath = getenv("PYTHONQT_INCLUDE"); + if (!includePath.isEmpty()) + includes += includePath.split(pathSplitter, Qt::SkipEmptyParts); + + // Includes from the command line + if (!commandLineIncludes.isEmpty()) + includes += commandLineIncludes.split(pathSplitter, Qt::SkipEmptyParts); + for (auto it = includes.begin(); it != includes.end();) + { + if (!QDir(*it).exists()) + { + qWarning("Include path %s does not exist, ignoring it.", it->toUtf8().constData()); + it = includes.erase(it); + } + else + { + ++it; + } + } + + // Include Qt + QString qtdir = getenv("QTDIR"); + if (qtdir.isEmpty() || !QDir(qtdir).exists(qtdir)) + { + QString reason = "The QTDIR environment variable " + qtdir.isEmpty() ? + "is not set. " : "points to a non-existing directory. "; +#if defined(Q_OS_MAC) + qWarning((reason + "Assuming standard binary install using frameworks.").toUtf8().constData()); + QString frameworkDir = "/Library/Frameworks"; + includes << (frameworkDir + "/QtXml.framework/Headers"); + includes << (frameworkDir + "/QtNetwork.framework/Headers"); + includes << (frameworkDir + "/QtCore.framework/Headers"); + includes << (frameworkDir + "/QtGui.framework/Headers"); + includes << (frameworkDir + "/QtOpenGL.framework/Headers"); + includes << frameworkDir; +#else + qWarning((reason + "This may cause problems with finding the necessary include files.").toUtf8().constData()); +#endif + } + else + { + std::cout << "-------------------------------------------------------------" << std::endl; + std::cout << "Using QT at: " << qtdir.toLocal8Bit().constData() << std::endl; + std::cout << "-------------------------------------------------------------" << std::endl; + qtdir += "/include"; + includes << (qtdir + "/QtXml"); + includes << (qtdir + "/QtNetwork"); + includes << (qtdir + "/QtCore"); + includes << (qtdir + "/QtGui"); + includes << (qtdir + "/QtOpenGL"); + includes << qtdir; + } + return includes; + } + + bool + preprocess(const QString &sourceFile, const QString &targetFile, const QString &commandLineIncludes = QString()) + { + rpp::pp_environment env; + rpp::pp preprocess(env); + + rpp::pp_null_output_iterator null_out; + + const char *ppconfig = ":/trolltech/generator/parser/rpp/pp-qt-configuration"; + + QFile file(ppconfig); + if (!file.open(QFile::ReadOnly)) + { + fprintf(stderr, "Preprocessor configuration file not found '%s'\n", ppconfig); + return false; + } + + QByteArray ba = file.readAll(); + file.close(); + preprocess.operator()(ba.constData(), ba.constData() + ba.size(), null_out); + + foreach(QString + include, getIncludeDirectories(commandLineIncludes)) { + preprocess.push_include_path(QDir::toNativeSeparators(include).toStdString()); + } + + QString currentDir = QDir::current().absolutePath(); + QFileInfo sourceInfo(sourceFile); + QDir::setCurrent(sourceInfo.absolutePath()); + + std::string result; + result.reserve(20 * 1024); // 20K + + result += "# 1 \"builtins\"\n"; + result += "# 1 \""; + result += sourceFile.toStdString(); + result += "\"\n"; + + preprocess.file(sourceInfo.fileName().toStdString(), + rpp::pp_output_iterator(result)); + + QDir::setCurrent(currentDir); + + QFile f(targetFile); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) + { + fprintf(stderr, "Failed to write preprocessed file: %s\n", qPrintable(targetFile)); + } + f.write(result.c_str(), result.length()); + + return true; + } + + unsigned int getQtVersion(const QString &commandLineIncludes) + { + QRegularExpression re("#define\\s+QTCORE_VERSION\\s+0x([0-9a-f]+)", QRegularExpression::CaseInsensitiveOption); + for (const QString &includeDir: getIncludeDirectories(commandLineIncludes)) + { + QFileInfo fi(QDir(includeDir), "qtcoreversion.h"); + if (fi.exists()) + { + QString filePath = fi.absoluteFilePath(); + QFile f(filePath); + if (f.open(QIODevice::ReadOnly)) + { + QTextStream ts(&f); + QString content = ts.readAll(); + f.close(); + auto match = re.match(content); + if (match.isValid()) + { + unsigned int result; + bool ok; + result = match.captured(1).toUInt(&ok, 16); + if (!ok) + { + printf("Could not parse Qt version in file [%s] (looked for #define QTCORE_VERSION)\n", + qPrintable(filePath)); + } + return result; + } + } + } + } + printf("Error: Could not find Qt version (looked for qtcoreversion.h in %s)\n", + qPrintable(commandLineIncludes)); + return 0; + } +}; + + int main(int argc, char *argv[]) { ReportHandler::setContext("Arguments"); @@ -66,9 +227,9 @@ int main(int argc, char *argv[]) QString fileName; QString typesystemFileName; QString pp_file = ".preprocessed.tmp"; - QStringList rebuild_classes; QMap args; + unsigned int qtVersion{}; int argNum = 0; for (int i=1; isetRebuildClasses(classes); } + if (args.contains("qt-version")) { + bool ok; + qtVersion = TypeSystem::qtVersionFromString(args.value("qt-version"), ok); + if (!ok || qtVersion < 0x050000) { + printf("Invalid Qt version specified, will look into header files for version...\n"); + qtVersion = 0; + } + } + fileName = args.value("arg-1"); typesystemFileName = args.value("arg-2"); @@ -143,15 +313,31 @@ int main(int argc, char *argv[]) printf("Please wait while source files are being generated...\n"); + if (!qtVersion) { + printf("Trying to determine Qt version...\n"); + qtVersion = getQtVersion(args.value("include-paths")); + if (!qtVersion) + { + fprintf(stderr, "Aborting\n"); // the error message was printed by getQtVersion + return 1; + } + printf("Determined Qt version is %d.%d.%d\n", qtVersion >> 16, (qtVersion >> 8) & 0xFF, qtVersion & 0xFF); + } + printf("Parsing typesystem file [%s]\n", qPrintable(typesystemFileName)); + fflush(stdout); ReportHandler::setContext("Typesystem"); - if (!TypeDatabase::instance()->parseFile(typesystemFileName)) + if (TypeDatabase::instance()->parseFile(typesystemFileName, qtVersion)) { + TypeDatabase::instance()->finalSetup(); + } + else { qFatal("Cannot parse file: '%s'", qPrintable(typesystemFileName)); + } printf("PreProcessing - Generate [%s] using [%s] and include-paths [%s]\n", qPrintable(pp_file), qPrintable(fileName), qPrintable(args.value("include-paths"))); ReportHandler::setContext("Preprocess"); - if (!Preprocess::preprocess(fileName, pp_file, args.value("include-paths"))) { + if (!preprocess(fileName, pp_file, args.value("include-paths"))) { fprintf(stderr, "Preprocessor failed on file: '%s'\n", qPrintable(fileName)); return 1; } @@ -180,11 +366,7 @@ int main(int argc, char *argv[]) void displayHelp(GeneratorSet* generatorSet) { -#if defined(Q_OS_WIN32) - char path_splitter = ';'; -#else - char path_splitter = ':'; -#endif + const auto path_splitter = QDir::listSeparator().toLatin1(); printf("Usage:\n generator [options] header-file typesystem-file\n\n"); printf("Available options:\n\n"); printf("General:\n"); @@ -195,6 +377,7 @@ void displayHelp(GeneratorSet* generatorSet) { " --no-suppress-warnings \n" " --output-directory=[dir] \n" " --include-paths=[%c%c...] \n" + " --qt-version=x.y.z \n" " --print-stdout \n", path_splitter, path_splitter); diff --git a/generator/main.h b/generator/main.h deleted file mode 100644 index 15fc6dc1..00000000 --- a/generator/main.h +++ /dev/null @@ -1,147 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2008-2009 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of the Qt Script Generator project on Qt Labs. -** -** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the Technology Preview License Agreement accompanying -** this package. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -** -** -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef MAIN_H -#define MAIN_H - -#include "pp.h" - -#include -#include - -struct Preprocess -{ - static bool preprocess(const QString &sourceFile, const QString &targetFile, const QString &commandLineIncludes = QString()) - { - rpp::pp_environment env; - rpp::pp preprocess(env); - - rpp::pp_null_output_iterator null_out; - - const char *ppconfig = ":/trolltech/generator/parser/rpp/pp-qt-configuration"; - - QFile file(ppconfig); - if (!file.open(QFile::ReadOnly)) { - fprintf(stderr, "Preprocessor configuration file not found '%s'\n", ppconfig); - return false; - } - - QByteArray ba = file.readAll(); - file.close(); - preprocess.operator() (ba.constData(), ba.constData() + ba.size(), null_out); - - QStringList includes; - includes << QString("."); - -#if defined(Q_OS_WIN32) - const char *path_splitter = ";"; -#else - const char *path_splitter = ":"; -#endif - - // Environment INCLUDE - QString includePath = getenv("INCLUDE"); - if (!includePath.isEmpty()) - includes += includePath.split(path_splitter); - - // Includes from the command line - if (!commandLineIncludes.isEmpty()) - includes += commandLineIncludes.split(path_splitter); - - // Include Qt - QString qtdir = getenv ("QTDIR"); - if (qtdir.isEmpty()) { -#if defined(Q_OS_MAC) - qWarning("QTDIR environment variable not set. Assuming standard binary install using frameworks."); - QString frameworkDir = "/Library/Frameworks"; - includes << (frameworkDir + "/QtXml.framework/Headers"); - includes << (frameworkDir + "/QtNetwork.framework/Headers"); - includes << (frameworkDir + "/QtCore.framework/Headers"); - includes << (frameworkDir + "/QtGui.framework/Headers"); - includes << (frameworkDir + "/QtOpenGL.framework/Headers"); - includes << frameworkDir; -#else - qWarning("QTDIR environment variable not set. This may cause problems with finding the necessary include files."); -#endif - } else { - std::cout << "-------------------------------------------------------------" << std::endl; - std::cout << "Using QT at: " << qtdir.toLocal8Bit().constData() << std::endl; - std::cout << "-------------------------------------------------------------" << std::endl; - qtdir += "/include"; - includes << (qtdir + "/QtXml"); - includes << (qtdir + "/QtNetwork"); - includes << (qtdir + "/QtCore"); - includes << (qtdir + "/QtGui"); - includes << (qtdir + "/QtOpenGL"); - includes << qtdir; - } - foreach (QString include, includes) { - preprocess.push_include_path(QDir::toNativeSeparators(include).toStdString()); - } - - QString currentDir = QDir::current().absolutePath(); - QFileInfo sourceInfo(sourceFile); - QDir::setCurrent(sourceInfo.absolutePath()); - - std::string result; - result.reserve (20 * 1024); // 20K - - result += "# 1 \"builtins\"\n"; - result += "# 1 \""; - result += sourceFile.toStdString(); - result += "\"\n"; - - preprocess.file (sourceInfo.fileName().toStdString(), - rpp::pp_output_iterator (result)); - - QDir::setCurrent(currentDir); - - QFile f(targetFile); - if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { - fprintf(stderr, "Failed to write preprocessed file: %s\n", qPrintable(targetFile)); - } - f.write(result.c_str(), result.length()); - - return true; - } -}; - -#endif // MAIN_H diff --git a/generator/parser/ast.h b/generator/parser/ast.h index 181c6388..6afcbf92 100644 --- a/generator/parser/ast.h +++ b/generator/parser/ast.h @@ -372,10 +372,9 @@ struct DeclarationStatementAST: public StatementAST struct DeclaratorAST: public AST { DECLARE_AST_NODE(Declarator) + enum ValueReferenceEnum { UnspecifiedRef, Lvalue, Rvalue }; // "&" or "&&" after member function - DeclaratorAST() { - _override = false; - } + DeclaratorAST() = default; const ListNode *ptr_ops{}; DeclaratorAST *sub_declarator{}; NameAST *id{}; @@ -385,6 +384,8 @@ struct DeclaratorAST: public AST const ListNode *fun_cv{}; ExceptionSpecificationAST *exception_spec{}; bool _override{}; + bool packedParameter{}; + ValueReferenceEnum valueRef{ UnspecifiedRef }; }; struct DeleteExpressionAST: public ExpressionAST @@ -420,6 +421,7 @@ struct EnumSpecifierAST: public TypeSpecifierAST NameAST *name{}; const ListNode *enumerators{}; + bool is_enum_class{}; }; struct EnumeratorAST: public AST @@ -710,6 +712,7 @@ struct SimpleTypeSpecifierAST: public TypeSpecifierAST TypeIdAST *type_id{}; ExpressionAST *expression{}; NameAST *name{}; + bool is_auto{}; }; struct SizeofExpressionAST: public ExpressionAST @@ -749,6 +752,7 @@ struct TemplateArgumentAST: public AST TypeIdAST *type_id{}; ExpressionAST *expression{}; + bool variadic{}; }; struct TemplateDeclarationAST: public DeclarationAST diff --git a/generator/parser/binder.cpp b/generator/parser/binder.cpp index 6f0e2adb..cfc8d3bb 100644 --- a/generator/parser/binder.cpp +++ b/generator/parser/binder.cpp @@ -365,6 +365,15 @@ void Binder::visitFunctionDefinition(FunctionDefinitionAST *node) Q_ASSERT(node->init_declarator != 0); ScopeModelItem scope = currentScope(); + bool friendWithDefinition = false; + + if (hasFriendSpecifier(node->storage_specifiers) && ast_cast(node)) + { + // check if this function declaration is a "friend" function with implementation body. + // In this case we modify the scope, and remove the "friend" flag later on. + friendWithDefinition = true; + scope = model_static_cast(_M_current_file); + } InitDeclaratorAST *init_declarator = node->init_declarator; DeclaratorAST *declarator = init_declarator->declarator; @@ -384,6 +393,13 @@ void Binder::visitFunctionDefinition(FunctionDefinitionAST *node) } CodeModelFinder finder(model(), this); + if (declarator->valueRef == DeclaratorAST::Rvalue) + { + // rvalue reference methods are ignored, since we can't use them for the wrappers + // (there is usually also a method with lvalue reference binding) + return; + } + ScopeModelItem functionScope = finder.resolveScope(declarator->id, scope); if (! functionScope) { @@ -404,6 +420,12 @@ void Binder::visitFunctionDefinition(FunctionDefinitionAST *node) // << qPrintable(name_cc.name()) << std::endl; return; } + if (p.packedParameter) { + //warnHere(); + //std::cerr << "** Skipping function with packed parameter: " + // << qPrintable(name_cc.name()) << std::endl; + return; + } } Q_ASSERT(! decl_cc.id().isEmpty()); @@ -437,11 +459,30 @@ void Binder::visitFunctionDefinition(FunctionDefinitionAST *node) model_static_cast(_M_current_function)->setVirtual(true); } + if (friendWithDefinition) + { + // unset the friend flag, as we treat this like a stand-alone function definition + _M_current_function->setFriend(false); + // also set the access policy to public, just in case + _M_current_function->setAccessPolicy(CodeModel::Public); + } _M_current_function->setVariadics (decl_cc.isVariadics ()); foreach (DeclaratorCompiler::Parameter p, decl_cc.parameters()) { ArgumentModelItem arg = model()->create(); + + if (_M_current_class && _M_current_class->isTemplateClass()) + { + QStringList qualifiedName = p.type.qualifiedName(); + if (qualifiedName.size() == 1 && !qualifiedName.last().contains('<') && + qualifiedName.last() == _M_current_class->name().split('<').first()) + { + // Fix: add template arguments if the argument type is the current class + // name without template arguments + p.type.setQualifiedName(QStringList(_M_current_class->name())); + } + } arg->setType(qualifyType(p.type, functionScope->qualifiedName())); arg->setName(p.name); arg->setDefaultValue(p.defaultValue); @@ -767,6 +808,7 @@ void Binder::visitEnumSpecifier(EnumSpecifierAST *node) _M_current_enum = model()->create(); _M_current_enum->setAccessPolicy(_M_current_access); + _M_current_enum->setEnumClass(node->is_enum_class); updateItemPosition (_M_current_enum->toItem(), node); _M_current_enum->setName(name); _M_current_enum->setScope(enumScope->qualifiedName()); @@ -827,7 +869,7 @@ void Binder::visitQEnums(QEnumsAST *node) //if (node->isQEnum) { // std::cout << enum_list.at(i).toLatin1().constData() << std::endl; //} - scope->addEnumsDeclaration(enum_list.at(i)); + scope->addQEnumDeclaration(enum_list.at(i)); } } @@ -850,6 +892,24 @@ void Binder::warnHere() const } } +bool Binder::hasFriendSpecifier(const ListNode* it) +{ + if (it == 0) + return false; + + it = it->toFront(); + const ListNode* end = it; + + do + { + if (decode_token(it->element) == Token_friend) { + return true; + } + it = it->next; + } while (it != end); + return false; +} + void Binder::applyStorageSpecifiers(const ListNode *it, MemberModelItem item) { if (it == 0) @@ -904,6 +964,10 @@ void Binder::applyFunctionSpecifiers(const ListNode *it, FunctionMo default: break; + case Token_constexpr: + item->setConstexpr(true); + break; + case Token_inline: item->setInline(true); break; diff --git a/generator/parser/binder.h b/generator/parser/binder.h index e39210f8..92209239 100644 --- a/generator/parser/binder.h +++ b/generator/parser/binder.h @@ -104,6 +104,7 @@ class Binder: protected DefaultVisitor void declare_symbol(SimpleDeclarationAST *node, InitDeclaratorAST *init_declarator); + bool hasFriendSpecifier(const ListNode* it); void applyStorageSpecifiers(const ListNode *storage_specifiers, MemberModelItem item); void applyFunctionSpecifiers(const ListNode *it, FunctionModelItem item); diff --git a/generator/parser/codemodel.cpp b/generator/parser/codemodel.cpp index d2b8523d..52b1f33e 100644 --- a/generator/parser/codemodel.cpp +++ b/generator/parser/codemodel.cpp @@ -142,7 +142,6 @@ TypeInfo TypeInfo::combine (const TypeInfo &__lhs, const TypeInfo &__rhs) TypeInfo __result = __lhs; __result.setConstant (__result.isConstant () || __rhs.isConstant ()); - __result.setConstexpr (__result.isConstexpr () || __rhs.isConstexpr ()); __result.setVolatile (__result.isVolatile () || __rhs.isVolatile ()); __result.setMutable (__result.isMutable () || __rhs.isMutable ()); __result.setReference (__result.isReference () || __rhs.isReference ()); @@ -176,7 +175,7 @@ TypeInfo TypeInfo::resolveType (TypeInfo const &__type, CodeModelItem __scope) return otherType; } -QString TypeInfo::toString() const +QString TypeInfo::toString(bool parsable) const { QString tmp; @@ -184,14 +183,13 @@ QString TypeInfo::toString() const if (isConstant()) tmp += QLatin1String(" const"); - if (isConstexpr()) - tmp += QLatin1String(" constexpr"); + if (!parsable) { + if (isVolatile()) + tmp += QLatin1String(" volatile"); - if (isVolatile()) - tmp += QLatin1String(" volatile"); - - if (isMutable()) - tmp += QLatin1String(" mutable"); + if (isMutable()) + tmp += QLatin1String(" mutable"); + } if (indirections()) tmp += QString(indirections(), QLatin1Char('*')); @@ -209,7 +207,7 @@ QString TypeInfo::toString() const if (i != 0) tmp += QLatin1String(", "); - tmp += m_arguments.at(i).toString(); + tmp += m_arguments.at(i).toString(parsable); } tmp += QLatin1String(")"); } @@ -431,9 +429,9 @@ FunctionList _ScopeModelItem::functions() const return _M_functions.values(); } -void _ScopeModelItem::addEnumsDeclaration(const QString &enumsDeclaration) +void _ScopeModelItem::addQEnumDeclaration(const QString &qEnumDeclaration) { - _M_enumsDeclarations << enumsDeclaration; + _M_qEnumDeclarations.insert(qEnumDeclaration); } FunctionDefinitionList _ScopeModelItem::functionDefinitions() const diff --git a/generator/parser/codemodel.h b/generator/parser/codemodel.h index 71d8ed5b..867e368c 100644 --- a/generator/parser/codemodel.h +++ b/generator/parser/codemodel.h @@ -51,6 +51,7 @@ #include #include #include +#include #define DECLARE_MODEL_NODE(k) \ enum { __node_kind = Kind_##k }; \ @@ -132,9 +133,6 @@ struct TypeInfo bool isConstant() const { return m_flags.m_constant; } void setConstant(bool is) { m_flags.m_constant = is; } - bool isConstexpr() const { return m_flags.m_constexpr; } - void setConstexpr(bool is) { m_flags.m_constexpr = is; } - bool isVolatile() const { return m_flags.m_volatile; } void setVolatile(bool is) { m_flags.m_volatile = is; } @@ -165,7 +163,7 @@ struct TypeInfo // ### arrays and templates?? - QString toString() const; + QString toString(bool parsable = false) const; static TypeInfo combine (const TypeInfo &__lhs, const TypeInfo &__rhs); static TypeInfo resolveType (TypeInfo const &__type, CodeModelItem __scope); @@ -310,8 +308,8 @@ class _ScopeModelItem: public _CodeModelItem TypeAliasModelItem findTypeAlias(const QString &name) const; VariableModelItem findVariable(const QString &name) const; - void addEnumsDeclaration(const QString &enumsDeclaration); - QStringList enumsDeclarations() const { return _M_enumsDeclarations; } + void addQEnumDeclaration(const QString &qEnumDeclaration); + QSet qEnumDeclarations() const { return _M_qEnumDeclarations; } inline QHash classMap() const { return _M_classes; } inline QHash enumMap() const { return _M_enums; } @@ -338,7 +336,7 @@ class _ScopeModelItem: public _CodeModelItem _ScopeModelItem(const _ScopeModelItem &other); void operator = (const _ScopeModelItem &other); - QStringList _M_enumsDeclarations; + QSet _M_qEnumDeclarations; }; class _ClassModelItem: public _ScopeModelItem @@ -357,6 +355,7 @@ class _ClassModelItem: public _ScopeModelItem TemplateParameterList templateParameters() const; void setTemplateParameters(const TemplateParameterList &templateParameters); + bool isTemplateClass() const { return _M_templateParameters.size(); } bool extendsClass(const QString &name) const; @@ -678,6 +677,9 @@ class _EnumModelItem: public _CodeModelItem CodeModel::AccessPolicy accessPolicy() const; void setAccessPolicy(CodeModel::AccessPolicy accessPolicy); + bool isEnumClass() const { return _M_isEnumClass; } + void setEnumClass(bool isEnumClass) { _M_isEnumClass = isEnumClass; } + EnumeratorList enumerators() const; void addEnumerator(EnumeratorModelItem item); void removeEnumerator(EnumeratorModelItem item); @@ -691,6 +693,7 @@ class _EnumModelItem: public _CodeModelItem private: CodeModel::AccessPolicy _M_accessPolicy; EnumeratorList _M_enumerators; + bool _M_isEnumClass{}; private: _EnumModelItem(const _EnumModelItem &other); diff --git a/generator/parser/compiler_utils.cpp b/generator/parser/compiler_utils.cpp index c14f22b4..d924f26c 100644 --- a/generator/parser/compiler_utils.cpp +++ b/generator/parser/compiler_utils.cpp @@ -58,7 +58,6 @@ TypeInfo CompilerUtils::typeDescription(TypeSpecifierAST *type_specifier, Declar TypeInfo typeInfo; typeInfo.setQualifiedName (type_cc.qualifiedName ()); typeInfo.setConstant (type_cc.isConstant ()); - typeInfo.setConstexpr (type_cc.isConstexpr ()); typeInfo.setVolatile (type_cc.isVolatile ()); typeInfo.setMutable (type_cc.isMutable ()); typeInfo.setReference (decl_cc.isReference ()); diff --git a/generator/parser/declarator_compiler.cpp b/generator/parser/declarator_compiler.cpp index ac71f38f..4aaafeef 100644 --- a/generator/parser/declarator_compiler.cpp +++ b/generator/parser/declarator_compiler.cpp @@ -64,6 +64,7 @@ void DeclaratorCompiler::run(DeclaratorAST *node) _M_reference = false; _M_rvalue_reference = false; _M_variadics = false; + _M_packed_parameter = false; _M_indirection = 0; if (node) @@ -81,6 +82,7 @@ void DeclaratorCompiler::run(DeclaratorAST *node) _M_function = (node->parameter_declaration_clause != 0); if (node->parameter_declaration_clause && node->parameter_declaration_clause->ellipsis) _M_variadics = true; + _M_packed_parameter = node->packedParameter; visitNodes(this, node->ptr_ops); visit(node->parameter_declaration_clause); @@ -151,6 +153,7 @@ void DeclaratorCompiler::visitParameterDeclaration(ParameterDeclarationAST *node p.name = decl_cc.id(); p.type = CompilerUtils::typeDescription(node->type_specifier, node->declarator, _M_binder); + p.packedParameter = decl_cc.isPackedParameter(); // ignore case a single void parameter if (_M_parameters.isEmpty() && p.name.isEmpty() && p.type.toString() == "void") diff --git a/generator/parser/declarator_compiler.h b/generator/parser/declarator_compiler.h index f3d3578d..6337e1ae 100644 --- a/generator/parser/declarator_compiler.h +++ b/generator/parser/declarator_compiler.h @@ -60,9 +60,10 @@ class DeclaratorCompiler: protected DefaultVisitor TypeInfo type; QString name; QString defaultValueExpression; - bool defaultValue; + bool defaultValue{}; + bool packedParameter{}; - Parameter(): defaultValue(false) {} + Parameter() {} }; public: @@ -76,6 +77,7 @@ class DeclaratorCompiler: protected DefaultVisitor inline bool isVariadics() const { return _M_variadics; } inline bool isReference() const { return _M_reference; } inline bool isRvalueReference() const { return _M_rvalue_reference; } + inline bool isPackedParameter() const { return _M_packed_parameter; } inline int indirection() const { return _M_indirection; } inline QList parameters() const { return _M_parameters; } @@ -91,6 +93,7 @@ class DeclaratorCompiler: protected DefaultVisitor bool _M_reference; bool _M_rvalue_reference; bool _M_variadics; + bool _M_packed_parameter; int _M_indirection; QString _M_id; QStringList _M_array; diff --git a/generator/parser/lexer.cpp b/generator/parser/lexer.cpp index db614ad1..c2b59288 100644 --- a/generator/parser/lexer.cpp +++ b/generator/parser/lexer.cpp @@ -1426,6 +1426,18 @@ void Lexer::scanKeyword7() { switch (*cursor) { + case 'a': + if (*(cursor + 1) == 'l' && + *(cursor + 2) == 'i' && + *(cursor + 3) == 'g' && + *(cursor + 4) == 'n' && + *(cursor + 5) == 'a' && + *(cursor + 6) == 's') + { + token_stream[(int)index++].kind = Token_alignas; + return; + } + break; case 'd': if (*(cursor + 1) == 'e' && *(cursor + 2) == 'f' && diff --git a/generator/parser/name_compiler.cpp b/generator/parser/name_compiler.cpp index 1b70b879..7e136b4d 100644 --- a/generator/parser/name_compiler.cpp +++ b/generator/parser/name_compiler.cpp @@ -127,12 +127,6 @@ void NameCompiler::visitTemplateArgument(TemplateArgumentAST *node) if (type_cc.isConstant()) _M_name.last() += "const "; - /* An id can't be 'constexpr' but it may have a function type in which - * case constexpr could appear. - */ - if (type_cc.isConstexpr()) - _M_name.last() += "constexpr "; - QStringList q = type_cc.qualifiedName (); if (q.count () == 1) diff --git a/generator/parser/parser.cpp b/generator/parser/parser.cpp index bdf39f76..b115aeb5 100644 --- a/generator/parser/parser.cpp +++ b/generator/parser/parser.cpp @@ -230,6 +230,61 @@ void Parser::keepTrackDebug() #endif } +bool Parser::skipAlignas() +{ + // we are currently not interested in alignas, so we just skip it + if (token_stream.lookAhead() == Token_alignas) + { + nextToken(); + if (token_stream.lookAhead() == '(') + { + if (skip('(', ')')) + { + nextToken(); + } + } + return true; + } + return false; +} + +bool Parser::skipAttributes() +{ + bool any = false; + while (true) { + if (token_stream.lookAhead() == Token___attribute__) + { + parse_Attribute__(); + any = true; + } + else if (token_stream.lookAhead() == '[' && token_stream.lookAhead(1) == '[') + { + nextToken(); + while (true) + { + nextToken(); + int tk = token_stream.lookAhead(); + if (tk == Token_EOF) + { + break; + } + else if (tk == ']' && token_stream.lookAhead(1) == ']') // this has no separate token because "]]" can occur in other contexts + { + nextToken(); + nextToken(); + break; + } + } + any = true; + } + else + { + break; + } + } + return any; +} + bool Parser::skipUntil(int token) { while (token_stream.lookAhead()) @@ -385,7 +440,10 @@ bool Parser::parseName(NameAST *&node, bool acceptTemplateId) std::size_t start = token_stream.cursor(); WinDeclSpecAST *winDeclSpec = 0; - parseWinDeclSpec(winDeclSpec); + while (skipAlignas() || (!winDeclSpec && parseWinDeclSpec(winDeclSpec))) + { + ; + } NameAST *ast = CreateNode(_M_pool); @@ -400,7 +458,7 @@ bool Parser::parseName(NameAST *&node, bool acceptTemplateId) while (true) { UnqualifiedNameAST *n = 0; - if (!parseUnqualifiedName(n)) + if (!parseUnqualifiedName(n, acceptTemplateId)) return false; if (token_stream.lookAhead() == Token_scope) @@ -419,12 +477,6 @@ bool Parser::parseName(NameAST *&node, bool acceptTemplateId) else { Q_ASSERT(n != 0); - if (!acceptTemplateId) - { - rewind(n->start_token); - parseUnqualifiedName(n, false); - } - ast->unqualified_name = n; break; } @@ -511,15 +563,25 @@ bool Parser::parseDeclaration(DeclarationAST *&node) case Token_export: return parseTemplateDeclaration(node); + case Token_inline: + if (token_stream.lookAhead(1) == Token_namespace) + { + nextToken(); + // handle like a normal namespace for now + return parseNamespace(node); + } + // else fallthrough default: { - const ListNode *cv = 0; - parseCvQualify(cv); - - const ListNode *storageSpec = 0; - parseStorageClassSpecifier(storageSpec); + skipAttributes(); - parseCvQualify(cv); + const ListNode *cv = 0; + const ListNode* storageSpec = 0; + // consume all qualifiers/specifiers + while (parseCvQualify(cv) || parseStorageClassSpecifier(storageSpec)) + { + ; + } TypeSpecifierAST *spec = 0; if (parseEnumSpecifier(spec) @@ -702,7 +764,7 @@ bool Parser::parseUsing(DeclarationAST *&node) nextToken(); } - if (!parseName(ast->name)) + if (!parseName(ast->name, /*acceptTemplateId=*/true)) return false; ADVANCE(';', ";"); @@ -1014,6 +1076,15 @@ bool Parser::parseOperator(OperatorAST *&node) ast->close = token_stream.cursor(); nextToken(); } + else if (token_stream.lookAhead() == Token_string_literal + && token_stream.lookAhead(1) == Token_identifier) + { + // string literal operator + ast->op = token_stream.cursor(); + nextToken(); + // skip string literal suffix for now + nextToken(); + } else { return false; @@ -1032,7 +1103,7 @@ bool Parser::parseCvQualify(const ListNode *&node) int tk; while (0 != (tk = token_stream.lookAhead()) - && (tk == Token_const || tk == Token_constexpr || + && (tk == Token_const || tk == Token_volatile || tk == Token_mutable)) { node = snoc(node, token_stream.cursor(), _M_pool); @@ -1081,6 +1152,11 @@ bool Parser::parseSimpleTypeSpecifier(TypeSpecifierAST *&node, { ast->integrals = integrals; } + else if (token_stream.lookAhead() == Token_auto) + { + nextToken(); + ast->is_auto = true; + } else if (token_stream.lookAhead() == Token___typeof || token_stream.lookAhead() == Token_decltype) { @@ -1097,7 +1173,7 @@ bool Parser::parseSimpleTypeSpecifier(TypeSpecifierAST *&node, { ast->type_id = 0; rewind(saved); - parseUnaryExpression(ast->expression); + parseConditionalExpression(ast->expression); } ADVANCE(')', ")"); } @@ -1113,6 +1189,10 @@ bool Parser::parseSimpleTypeSpecifier(TypeSpecifierAST *&node, } else { + if (token_stream.lookAhead() == Token_typename) + { + nextToken(); // simply skip for now + } if (!parseName(ast->name, true)) { ast->name = 0; @@ -1182,6 +1262,7 @@ bool Parser::parseTemplateArgument(TemplateArgumentAST *&node) ExpressionAST *expr = 0; if (!parseTypeId(typeId) || (token_stream.lookAhead() != ',' + && token_stream.lookAhead() != Token_ellipsis && token_stream.lookAhead() != '>' && token_stream.lookAhead() != Token_shift_right)) { @@ -1195,6 +1276,11 @@ bool Parser::parseTemplateArgument(TemplateArgumentAST *&node) ast->type_id = typeId; ast->expression = expr; + if (token_stream.lookAhead() == Token_ellipsis) { + nextToken(); + ast->variadic = true; + } + UPDATE_POS(ast, start, token_stream.cursor()); node = ast; @@ -1205,6 +1291,8 @@ bool Parser::parseTypeSpecifier(TypeSpecifierAST *&node) { std::size_t start = token_stream.cursor(); + skipAttributes(); + const ListNode *cv = 0; parseCvQualify(cv); @@ -1223,7 +1311,7 @@ bool Parser::parseTypeSpecifier(TypeSpecifierAST *&node) return true; } -bool Parser::parseDeclarator(DeclaratorAST *&node) +bool Parser::parseDeclarator(DeclaratorAST *&node, bool asParameter) { std::size_t start = token_stream.cursor(); @@ -1255,14 +1343,23 @@ bool Parser::parseDeclarator(DeclaratorAST *&node) { // unnamed bitfield } - else if (parseName(declId, true)) - { - ast->id = declId; - } else { - rewind(start); - return false; + if (asParameter && token_stream.lookAhead() == Token_ellipsis) + { + // parameter pack + nextToken(); + ast->packedParameter = true; + } + if (parseName(declId, true)) + { + ast->id = declId; + } + else + { + rewind(start); + return false; + } } if (token_stream.lookAhead() == ':') @@ -1312,53 +1409,27 @@ bool Parser::parseDeclarator(DeclaratorAST *&node) return false; } - std::size_t index = token_stream.cursor(); - if (token_stream.lookAhead() == '(') + if (parseDeclaratorParametersAndSuffix(ast)) { + + if (token_stream.lookAhead() == Token_identifier && + token_stream.symbol(token_stream.cursor())->as_string() == "override") { nextToken(); - - ParameterDeclarationClauseAST *params = 0; - if (!parseParameterDeclarationClause(params)) - { - rewind(index); - goto update_pos; - } - - ast->parameter_declaration_clause = params; - - if (token_stream.lookAhead() != ')') - { - rewind(index); - goto update_pos; - } - - nextToken(); // skip ')' - - parseCvQualify(ast->fun_cv); - parseExceptionSpecification(ast->exception_spec); - if (token_stream.lookAhead() == Token_identifier) { - const NameSymbol *name_symbol = token_stream.symbol(token_stream.cursor()); - QString name = name_symbol->as_string(); - if (name == "override") { - nextToken(); - ast->_override = true; - } - } - if (token_stream.lookAhead() == Token___attribute__) - { - parse_Attribute__(); - } + ast->_override = true; } + skipAttributes(); - if (skipParen) + if (skipParen) { if (token_stream.lookAhead() != ')') - { - reportError(("')' expected")); - } + { + reportError(("')' expected")); + } else nextToken(); } + + } } update_pos: @@ -1438,31 +1509,7 @@ bool Parser::parseAbstractDeclarator(DeclaratorAST *&node) return false; } - int index = (int) token_stream.cursor(); - if (token_stream.lookAhead() == '(') - { - nextToken(); - - ParameterDeclarationClauseAST *params = 0; - if (!parseParameterDeclarationClause(params)) - { - rewind(index); - goto update_pos; - } - - ast->parameter_declaration_clause = params; - - if (token_stream.lookAhead() != ')') - { - rewind(index); - goto update_pos; - } - - nextToken(); // skip ')' - - parseCvQualify(ast->fun_cv); - parseExceptionSpecification(ast->exception_spec); - } + parseDeclaratorParametersAndSuffix(ast); } update_pos: @@ -1475,14 +1522,57 @@ bool Parser::parseAbstractDeclarator(DeclaratorAST *&node) return true; } +bool Parser::parseDeclaratorParametersAndSuffix(DeclaratorAST* ast) +{ + std::size_t index = token_stream.cursor(); + if (token_stream.lookAhead() == '(') + { + nextToken(); + + ParameterDeclarationClauseAST* params = 0; + if (!parseParameterDeclarationClause(params)) + { + rewind(index); + return false; + } + + ast->parameter_declaration_clause = params; + + if (token_stream.lookAhead() != ')') + { + rewind(index); + return false; + } + + nextToken(); // skip ')' + + parseCvQualify(ast->fun_cv); + if (token_stream.lookAhead() == '&') + { + ast->valueRef = DeclaratorAST::Lvalue; + nextToken(); + } + else if (token_stream.lookAhead() == Token_and) + { + ast->valueRef = DeclaratorAST::Rvalue; + nextToken(); + } + parseExceptionSpecification(ast->exception_spec); + return true; + } + return false; +} + bool Parser::parseEnumSpecifier(TypeSpecifierAST *&node) { std::size_t start = token_stream.cursor(); CHECK(Token_enum); + bool enum_class{}; if (token_stream.lookAhead() == Token_class) { + enum_class = true; nextToken(); } @@ -1509,6 +1599,7 @@ bool Parser::parseEnumSpecifier(TypeSpecifierAST *&node) EnumSpecifierAST *ast = CreateNode(_M_pool); ast->name = name; + ast->is_enum_class = enum_class; EnumeratorAST *enumerator = 0; if (parseEnumerator(enumerator)) @@ -1699,7 +1790,8 @@ bool Parser::parseStorageClassSpecifier(const ListNode *&node) int tk; while (0 != (tk = token_stream.lookAhead()) - && (tk == Token_friend || tk == Token_auto + && (tk == Token_friend + // || tk == Token_auto // I believe "auto" isn't used as storage class specifier in Qt, and it collides with the type-specifier of the same name || tk == Token_register || tk == Token_static || tk == Token_extern)) { @@ -1716,7 +1808,7 @@ bool Parser::parseFunctionSpecifier(const ListNode *&node) int tk; while (0 != (tk = token_stream.lookAhead()) - && (tk == Token_inline || tk == Token_virtual + && (tk == Token_constexpr || tk == Token_inline || tk == Token_virtual || tk == Token_explicit || tk == Token_Q_INVOKABLE)) { node = snoc(node, token_stream.cursor(), _M_pool); @@ -1860,7 +1952,7 @@ bool Parser::parseParameterDeclaration(ParameterDeclarationAST *&node) int index = (int) token_stream.cursor(); DeclaratorAST *decl = 0; - if (!parseDeclarator(decl)) + if (!parseDeclarator(decl, /*asParameter=*/true)) { rewind(index); @@ -1983,10 +2075,9 @@ bool Parser::parseClassSpecifier(TypeSpecifierAST *&node) nextToken(); WinDeclSpecAST *winDeclSpec = 0; - parseWinDeclSpec(winDeclSpec); - - if (token_stream.lookAhead() == Token___attribute__) { - parse_Attribute__(); + while (skipAttributes() || skipAlignas() || (!winDeclSpec && parseWinDeclSpec(winDeclSpec))) + { + ; } while (token_stream.lookAhead() == Token_identifier @@ -2136,12 +2227,12 @@ bool Parser::parseMemberSpecification(DeclarationAST *&node) rewind(start); const ListNode *cv = 0; - parseCvQualify(cv); - - const ListNode *storageSpec = 0; - parseStorageClassSpecifier(storageSpec); - - parseCvQualify(cv); + const ListNode* storageSpec = 0; + // consume all qualifiers/specifiers + while (parseCvQualify(cv) || parseStorageClassSpecifier(storageSpec)) + { + ; + } TypeSpecifierAST *spec = 0; if (parseEnumSpecifier(spec) || parseClassSpecifier(spec)) @@ -3195,12 +3286,12 @@ bool Parser::parseBlockDeclaration(DeclarationAST *&node) std::size_t start = token_stream.cursor(); const ListNode *cv = 0; - parseCvQualify(cv); - const ListNode *storageSpec = 0; - parseStorageClassSpecifier(storageSpec); - - parseCvQualify(cv); + // consume all qualifiers/specifiers + while (parseCvQualify(cv) || parseStorageClassSpecifier(storageSpec)) + { + ; + } TypeSpecifierAST *spec = 0; if (!parseTypeSpecifierOrClassSpec(spec)) @@ -3283,28 +3374,21 @@ bool Parser::parseDeclarationInternal(DeclarationAST *&node) // that is for the case '__declspec(dllexport) int ...' or // '__declspec(dllexport) inline int ...', etc. WinDeclSpecAST *winDeclSpec = 0; - parseWinDeclSpec(winDeclSpec); - const ListNode* cv = 0; - parseCvQualify(cv); - - const ListNode *funSpec = 0; - bool hasFunSpec = parseFunctionSpecifier(funSpec); - - if (!cv) - parseCvQualify(cv); - - const ListNode *storageSpec = 0; - bool hasStorageSpec = parseStorageClassSpecifier(storageSpec); - - if (hasStorageSpec && !hasFunSpec) - hasFunSpec = parseFunctionSpecifier(funSpec); - - // that is for the case 'friend __declspec(dllexport) ....' - parseWinDeclSpec(winDeclSpec); - - if (!cv) - parseCvQualify(cv); + const ListNode* funSpec = 0; + const ListNode* storageSpec = 0; + // since it seems that the various specifiers can come in almost any order, + // so just consume then until no specifiers are left. + // Luckily the parse methods can be called multiple times, they just add to existing nodes. + while (skipAttributes() || + skipAlignas() || + (!winDeclSpec && parseWinDeclSpec(winDeclSpec)) || + parseCvQualify(cv) || + parseFunctionSpecifier(funSpec) || + parseStorageClassSpecifier(storageSpec)) + { + ; + } int index = (int) token_stream.cursor(); NameAST *name = 0; @@ -3394,8 +3478,7 @@ bool Parser::parseDeclarationInternal(DeclarationAST *&node) start_decl: rewind(index); - if ((token_stream.lookAhead() == Token_const || - token_stream.lookAhead() == Token_constexpr) + if (token_stream.lookAhead() == Token_const && token_stream.lookAhead(1) == Token_identifier && token_stream.lookAhead(2) == '=') { @@ -3428,8 +3511,7 @@ bool Parser::parseDeclarationInternal(DeclarationAST *&node) { Q_ASSERT(spec != 0); - if (!hasFunSpec) - parseFunctionSpecifier(funSpec); // e.g. "void inline" + parseFunctionSpecifier(funSpec); // e.g. "void inline" spec->cv = cv; @@ -3440,7 +3522,7 @@ bool Parser::parseDeclarationInternal(DeclarationAST *&node) if (token_stream.lookAhead() != ';') { - if (parseInitDeclarator(decl) && token_stream.lookAhead() == '{') + if (parseInitDeclarator(decl) && (token_stream.lookAhead() == '{' || token_stream.lookAhead() == Token_arrow)) { // function definition maybeFunctionDefinition = true; @@ -3456,6 +3538,18 @@ bool Parser::parseDeclarationInternal(DeclarationAST *&node) } } + if (token_stream.lookAhead() == Token_arrow) { + // trailing return type, used in conjuction with "auto" return type + nextToken(); + TypeSpecifierAST* trailingReturnTypeSpec = 0; + if (!parseTypeSpecifier(trailingReturnTypeSpec)) { + // todo: replace "auto" return type? But I doubt we can handle these return types anyway. + syntaxError(); + return false; + } + maybeFunctionDefinition = true; + } + switch(token_stream.lookAhead()) { case ';': @@ -3624,6 +3718,7 @@ bool Parser::parsePrimaryExpression(ExpressionAST *&node) parseStringLiteral(ast->literal); break; + case Token_ellipsis: // "..." can occur in constexpr of variadic templates case Token_number_literal: case Token_char_literal: case Token_true: @@ -3650,8 +3745,19 @@ bool Parser::parsePrimaryExpression(ExpressionAST *&node) CHECK(')'); break; + case '{': + nextToken(); + + // support for new-style initializers + if (token_stream.lookAhead() != '}' && !parseExpression(ast->sub_expression)) { + return false; + } + + CHECK('}'); + break; + default: - if (!parseName(ast->name)) + if (!parseName(ast->name, true)) // this can also be a template return false; break; @@ -3887,11 +3993,19 @@ bool Parser::parsePostfixExpression(ExpressionAST *&node) L_no_rewind: if (!expr && parseSimpleTypeSpecifier(typeSpec) - && token_stream.lookAhead() == '(') + && (token_stream.lookAhead() == '(' || token_stream.lookAhead() == '{')) { - nextToken(); // skip '(' + int tk = token_stream.lookAhead(); + nextToken(); // skip '(' or '{' parseCommaExpression(expr); - CHECK(')'); + if (tk == '(') + { + CHECK(')'); + } + else + { + CHECK('}'); + } } else if (expr) { @@ -3902,7 +4016,24 @@ bool Parser::parsePostfixExpression(ExpressionAST *&node) typeSpec = 0; rewind(start); - if (!parsePrimaryExpression(expr)) + if (token_stream.lookAhead() == Token_noexcept) + { + nextToken(); + CHECK('('); + ExpressionAST* arg_expr = 0; + if (!parseExpression(arg_expr)) + { + return false; + } + CHECK(')'); + + // make noexcept() in expressions an unary expression + UnaryExpressionAST* ast = CreateNode(_M_pool); + ast->op = start; + ast->expression = arg_expr; + expr = ast; + } + else if (!parsePrimaryExpression(expr)) return false; } @@ -3925,6 +4056,12 @@ bool Parser::parsePostfixExpression(ExpressionAST *&node) else node = expr; + if (token_stream.lookAhead() == Token_ellipsis) { + // ignore ellipsis, it might be something like "Pair...", which might appear + // in template arguments of variadic templates + nextToken(); + } + return true; } @@ -3964,6 +4101,12 @@ bool Parser::parseUnaryExpression(ExpressionAST *&node) std::size_t sizeof_token = token_stream.cursor(); nextToken(); + if (token_stream.lookAhead() == Token_ellipsis) { + // sizeof... is used on parameter packs - currently we ignore this + // todo: handle this + nextToken(); + } + SizeofExpressionAST *ast = CreateNode(_M_pool); ast->sizeof_token = sizeof_token; @@ -4673,18 +4816,14 @@ bool Parser::parseQ_ENUM(DeclarationAST *&node) bool Parser::parseQ_PROPERTY(DeclarationAST *&node) { - if (token_stream.lookAhead() != Token_Q_PROPERTY) + if (token_stream.lookAhead() != Token_Q_PROPERTY || token_stream.lookAhead(1) != '(') return false; - if (token_stream.lookAhead(1) != '(') - return false; - - nextToken(); nextToken(); - size_t firstToken = token_stream.cursor(); - while (token_stream.lookAhead() != ')') { - nextToken(); + size_t firstToken = token_stream.cursor()+1; + if (!skip('(', ')')) { + return false; } QPropertyAST *ast = CreateNode(_M_pool); UPDATE_POS(ast, firstToken, token_stream.cursor()); diff --git a/generator/parser/parser.h b/generator/parser/parser.h index 92d87ed3..fdec18dc 100644 --- a/generator/parser/parser.h +++ b/generator/parser/parser.h @@ -93,7 +93,8 @@ class Parser bool parseDeclaration(DeclarationAST *&node); bool parseDeclarationInternal(DeclarationAST *&node); bool parseDeclarationStatement(StatementAST *&node); - bool parseDeclarator(DeclaratorAST *&node); + bool parseDeclarator(DeclaratorAST *&node, bool asParameter = false); + bool parseDeclaratorParametersAndSuffix(DeclaratorAST* node); bool parseDeleteExpression(ExpressionAST *&node); bool parseDoStatement(StatementAST *&node); bool parseElaboratedTypeSpecifier(TypeSpecifierAST *&node); @@ -185,6 +186,8 @@ class Parser bool parseQ_ENUMS(DeclarationAST *&node); bool parseQ_ENUM(DeclarationAST *&node); + bool skipAlignas(); + bool skipAttributes(); bool skipUntil(int token); bool skipUntilDeclaration(); bool skipUntilStatement(); diff --git a/generator/parser/rpp/pp-engine-bits.h b/generator/parser/rpp/pp-engine-bits.h index 550e5bfa..09ae2521 100644 --- a/generator/parser/rpp/pp-engine-bits.h +++ b/generator/parser/rpp/pp-engine-bits.h @@ -1070,9 +1070,7 @@ InputIterator pp::eval_expression (InputIterator _first, InputIterator _last, Va } template -InputIterator pp::handle_if (InputIterator _first, InputIterator _last) -{ - if (test_if_level()) +std::string pp::expand_condition(InputIterator _first, InputIterator _last) { pp_macro_expander expand_condition (env); std::string condition; @@ -1083,10 +1081,19 @@ InputIterator pp::handle_if (InputIterator _first, InputIterator _last) const char* first = condition.c_str (); const char* last = first + condition.size (); expand_condition (skip_blanks (first, last), last, std::back_inserter (condition2ndpass)); + return condition2ndpass; +} + +template +InputIterator pp::handle_if (InputIterator _first, InputIterator _last) +{ + if (test_if_level()) + { + std::string condition = expand_condition(_first, _last); Value result; result.set_long (0); - eval_expression(condition2ndpass.c_str (), condition2ndpass.c_str () + condition2ndpass.size (), &result); + eval_expression(condition.c_str (), condition.c_str () + condition.size (), &result); _M_true_test[iflevel] = !result.is_zero (); _M_skipping[iflevel] = result.is_zero (); @@ -1125,8 +1132,11 @@ InputIterator pp::handle_elif (InputIterator _first, InputIterator _last) } else if (!_M_true_test[iflevel] && !_M_skipping[iflevel - 1]) { + std::string condition = expand_condition(_first, _last); + Value result; - _first = eval_expression(_first, _last, &result); + result.set_long(0); + eval_expression(condition.c_str(), condition.c_str() + condition.size(), &result); _M_true_test[iflevel] = !result.is_zero (); _M_skipping[iflevel] = result.is_zero (); } diff --git a/generator/parser/rpp/pp-engine.h b/generator/parser/rpp/pp-engine.h index 3a7273a4..2704fa27 100644 --- a/generator/parser/rpp/pp-engine.h +++ b/generator/parser/rpp/pp-engine.h @@ -186,6 +186,9 @@ class pp template inline InputIterator eval_expression (InputIterator _first, InputIterator _last, Value *result); + template + std::string expand_condition(InputIterator _first, InputIterator _last); + template void file (std::string const &filename, OutputIterator _result); diff --git a/generator/parser/rpp/pp-macro-expander.h b/generator/parser/rpp/pp-macro-expander.h index a52182c1..14471592 100644 --- a/generator/parser/rpp/pp-macro-expander.h +++ b/generator/parser/rpp/pp-macro-expander.h @@ -71,6 +71,9 @@ class pp_macro_expander std::string const *resolve_formal (pp_fast_string const *_name) { + static const pp_fast_string va_args_name("__VA_ARGS__", 11); + static std::string empty(""); + assert (_name != 0); if (! frame) @@ -78,7 +81,10 @@ class pp_macro_expander assert (frame->expanding_macro != 0); - std::vector const formals = frame->expanding_macro->formals; + std::vector formals = frame->expanding_macro->formals; + if (frame->expanding_macro->is.variadics) + formals.push_back(&va_args_name); + for (std::size_t index = 0; index < formals.size(); ++index) { pp_fast_string const *formal = formals[index]; @@ -89,6 +95,10 @@ class pp_macro_expander else if (frame->actuals && index < frame->actuals->size()) return &(*frame->actuals)[index]; + else if (frame->expanding_macro->is.variadics && index == formals.size()-1) + // variadic argument may also be missing, replace with empty then + return ∅ + else assert (0); // internal error? } diff --git a/generator/parser/rpp/pp-qt-configuration b/generator/parser/rpp/pp-qt-configuration index 590a1426..7ac89908 100644 --- a/generator/parser/rpp/pp-qt-configuration +++ b/generator/parser/rpp/pp-qt-configuration @@ -4,6 +4,7 @@ // Qt #define QOBJECTDEFS_H +#define QTMETAMACROS_H // not yet supported #define Q_SLOTS slots @@ -24,3 +25,9 @@ #define QT_NO_DEBUG #define QT_JAMBI_RUN + +// Qt6 +#define Q_NAMESPACE_EXPORT(...) +#define Q_ENUM_NS(x) +#define Q_FLAG_NS(x) +#define Q_MOC_INCLUDE(...) diff --git a/generator/parser/tokens.cpp b/generator/parser/tokens.cpp index a7b8d0c6..02e02fc7 100644 --- a/generator/parser/tokens.cpp +++ b/generator/parser/tokens.cpp @@ -51,6 +51,7 @@ static char const * const _S_token_names[] = { "Q_PROPERTY", "__attribute__", "__typeof", + "alignas", "and", "and_eq", "arrow", diff --git a/generator/parser/tokens.h b/generator/parser/tokens.h index 416e0a9a..edc6221f 100644 --- a/generator/parser/tokens.h +++ b/generator/parser/tokens.h @@ -52,6 +52,7 @@ enum TOKEN_KIND Token_Q_PROPERTY, Token___attribute__, Token___typeof, + Token_alignas, Token_and, Token_and_eq, Token_arrow, diff --git a/generator/parser/type_compiler.cpp b/generator/parser/type_compiler.cpp index 5c506386..a03ef979 100644 --- a/generator/parser/type_compiler.cpp +++ b/generator/parser/type_compiler.cpp @@ -132,11 +132,6 @@ bool TypeCompiler::isConstant() const return _M_cv.contains(Token_const); } -bool TypeCompiler::isConstexpr() const -{ - return _M_cv.contains(Token_constexpr); -} - bool TypeCompiler::isVolatile() const { return _M_cv.contains(Token_volatile); diff --git a/generator/parser/type_compiler.h b/generator/parser/type_compiler.h index 6b706f50..b6ea6915 100644 --- a/generator/parser/type_compiler.h +++ b/generator/parser/type_compiler.h @@ -61,7 +61,6 @@ class TypeCompiler: protected DefaultVisitor inline QList cv() const { return _M_cv; } bool isConstant() const; - bool isConstexpr() const; bool isVolatile() const; bool isMutable() const; diff --git a/generator/qtscript_masterinclude.h b/generator/qtscript_masterinclude.h index 10f9319b..8c0da0b8 100644 --- a/generator/qtscript_masterinclude.h +++ b/generator/qtscript_masterinclude.h @@ -93,6 +93,12 @@ // don't need this: #define Q_REVISION(v) +#define Q_DECLARE_OPERATORS_FOR_FLAGS(x) + +#include + +// parse still stumbles over this declaration +#define QT_DECL_METATYPE_EXTERN_TAGGED(TYPE, TAG, EXPORT) #include #include @@ -101,6 +107,10 @@ #include #include +#if QT_VERSION >= 0x060000 +#include +#endif + #include #if QT_VERSION >= 0x050000 @@ -117,9 +127,11 @@ #include #endif +#if QT_VERSION < 0x060000 #ifndef QT_NO_XMLPATTERNS # include #endif +#endif #ifndef QT_NO_WEBKIT # include @@ -1222,4 +1234,8 @@ #define GL_LOGIC_OP GL_INDEX_LOGIC_OP #define GL_TEXTURE_COMPONENTS GL_TEXTURE_INTERNAL_FORMAT #include +#if QT_VERSION >= 0x060000 +#include +#endif + #endif // QT_NO_OPENGL diff --git a/generator/setupgenerator.cpp b/generator/setupgenerator.cpp index f7eb31d0..e22c6325 100644 --- a/generator/setupgenerator.cpp +++ b/generator/setupgenerator.cpp @@ -52,10 +52,6 @@ void SetupGenerator::addClass(const QString& package, const AbstractMetaClass *c packHash[package].append(cls); } -void maybeDeclareMetaType(QTextStream &stream, const QString &typeName, - QSet ®isteredTypeNames); -bool hasDefaultConstructor(const AbstractMetaClass *meta_class); - static QStringList getOperatorCodes(const AbstractMetaClass* cls) { QSet operatorCodes; AbstractMetaFunctionList returned; @@ -247,34 +243,11 @@ void SetupGenerator::generate() continue; std::stable_sort(list.begin(), list.end(), AbstractMetaClass::less_than); - QString packKey = pack.key(); - QString packName = pack.key(); - QStringList components = packName.split("_"); - if ((components.size() > 2) && (components.at(0) == "com") - && (components.at(1) == "trolltech")) { - // kill com.trolltech in key - components.removeAt(0); - components.removeAt(0); - } - - QString shortPackName; - foreach (QString comp, components) { - comp[0] = comp[0].toUpper(); - shortPackName += comp; - } - // add missing camel case (workaround..) - if (shortPackName == "QtWebkit") { - shortPackName = "QtWebKit"; - } else if (shortPackName == "QtXmlpatterns") { - shortPackName = "QtXmlPatterns"; - } else if (shortPackName == "QtWebenginewidgets") { - shortPackName = "QtWebEngineWidgets"; - } else if (shortPackName == "QtOpengl") { - shortPackName = "QtOpenGL"; - } else if (shortPackName == "QtUitools") { - shortPackName = "QtUiTools"; - } - + QString packKey = ShellGenerator::toFileNameBase(pack.key()); + QString packName = packKey; + QString qtPackageName = "Qt" + pack.key().split('.').back().split('_').front(); + bool isBuiltin = packKey.endsWith("_builtin"); + QString initName = qtPackageName + (isBuiltin ? "Builtin" : ""); { QString fileName(packName + "/" + packKey + "_init.cpp"); @@ -291,7 +264,7 @@ void SetupGenerator::generate() s << endl; QStringList polymorphicHandlers; - if (!packName.endsWith("_builtin")) { + if (!isBuiltin) { polymorphicHandlers = writePolymorphicHandler(s, list.at(0)->package(), classes_with_polymorphic_id, list); s << endl; } @@ -328,11 +301,7 @@ void SetupGenerator::generate() s << endl; // declare individual class creation functions - s << "void PythonQt_init_" << shortPackName << "(PyObject* module) {" << endl; - - if (shortPackName.endsWith("Builtin")) { - shortPackName = shortPackName.mid(0, shortPackName.length()-strlen("builtin")); - } + s << "void PythonQt_init_" << initName << "(PyObject* module) {" << endl; foreach (const AbstractMetaClass *cls, list) { if (cls->qualifiedCppName().contains("Ssl")) { @@ -367,10 +336,12 @@ void SetupGenerator::generate() operatorCodes = "0"; } if (cls->isQObject()) { - s << "PythonQt::priv()->registerClass(&" << cls->qualifiedCppName() << "::staticMetaObject, \"" << shortPackName <<"\", PythonQtCreateObjectname() << ">" << shellCreator << ", module, " << operatorCodes <<");" << endl; + s << "PythonQt::priv()->registerClass(&" << cls->qualifiedCppName() << "::staticMetaObject, \"" << qtPackageName <<"\", PythonQtCreateObjectname() << ">" << shellCreator << ", module, " << operatorCodes <<");" << endl; + } else if (cls->isGlobalNamespace()) { + s << "PythonQt::priv()->registerGlobalNamespace(\"" << cls->qualifiedCppName() << "\", \"" << qtPackageName << "\", PythonQtCreateObjectname() << ">, PythonQtWrapper_" << cls->name() << "::staticMetaObject, module); " << endl; } else { QString baseName = cls->baseClass()?cls->baseClass()->qualifiedCppName():""; - s << "PythonQt::priv()->registerCPPClass(\""<< cls->qualifiedCppName() << "\", \"" << baseName << "\", \"" << shortPackName <<"\", PythonQtCreateObjectname() << ">" << shellCreator << ", module, " << operatorCodes <<");" << endl; + s << "PythonQt::priv()->registerCPPClass(\""<< cls->qualifiedCppName() << "\", \"" << baseName << "\", \"" << qtPackageName <<"\", PythonQtCreateObjectname() << ">" << shellCreator << ", module, " << operatorCodes <<");" << endl; } for (AbstractMetaClass* interface : cls->interfaces()) { // the interface might be our own class... (e.g. QPaintDevice) diff --git a/generator/shellgenerator.cpp b/generator/shellgenerator.cpp index d9de72a4..0b4fa0c4 100644 --- a/generator/shellgenerator.cpp +++ b/generator/shellgenerator.cpp @@ -50,8 +50,6 @@ bool ShellGenerator::shouldGenerate(const AbstractMetaClass *meta_class) const { uint cg = meta_class->typeEntry()->codeGeneration(); - // ignore the "Global" namespace, which contains the QtMsgType enum - if (meta_class->name().startsWith("Global")) return false; return ((cg & TypeEntry::GenerateCode) != 0); } @@ -95,7 +93,7 @@ void ShellGenerator::writeTypeInfo(QTextStream &s, const AbstractMetaType *type, } if (type->instantiations().size() > 0 - && (!type->isContainer() + && (!te->isContainer() || (static_cast(te))->type() != ContainerTypeEntry::StringListContainer)) { s << '<'; QList args = type->instantiations(); @@ -125,6 +123,18 @@ void ShellGenerator::writeTypeInfo(QTextStream &s, const AbstractMetaType *type, s << ' '; } +namespace { + AbstractMetaEnum* findEnumTypeOfClass(const AbstractMetaClass* implementor, const QString& enumName) + { + for (AbstractMetaEnum* enumType : implementor->enums()) { + if (enumType->name() == enumName) { + return enumType; + } + } + return nullptr; + } +} + void ShellGenerator::writeFunctionArguments(QTextStream &s, const AbstractMetaFunction *meta_function, @@ -170,8 +180,20 @@ void ShellGenerator::writeFunctionArguments(QTextStream &s, qualifier = ((EnumTypeEntry *)arg->type()->typeEntry())->qualifier(); } else if (arg->type()->typeEntry()->isFlags() && expr.indexOf("::") < 0) { qualifier = ((FlagsTypeEntry *)arg->type()->typeEntry())->originator()->qualifier(); + } else if (_currentScope) { + int pos = expr.indexOf("::"); + if (pos > 0) { + QString typeName = expr.left(pos); + AbstractMetaEnum* enumType = findEnumTypeOfClass(_currentScope, typeName); + if (enumType && enumType->typeEntry()->isEnumClass()) { + // prepend original class name, otherwise the new enum type from the wrapper will be used, + // which is not compatible + qualifier = _currentScope->name(); + } + } } - if (!qualifier.isEmpty()) { + + if (!qualifier.isEmpty() && !expr.startsWith("{")) { s << qualifier << "::"; } } @@ -399,6 +421,13 @@ void ShellGenerator::writeInclude(QTextStream &stream, const Include &inc) stream << endl; } +const AbstractMetaClass* ShellGenerator::setCurrentScope(const AbstractMetaClass* scope) +{ + const AbstractMetaClass* previousScope = _currentScope; + _currentScope = scope; + return previousScope; +} + /*! Returns true if the given function \a fun is operator>>() or operator<<() that streams from/to a Q{Data,Text}Stream, false diff --git a/generator/shellgenerator.h b/generator/shellgenerator.h index dd383bb6..26b4c1ea 100644 --- a/generator/shellgenerator.h +++ b/generator/shellgenerator.h @@ -53,7 +53,7 @@ class ShellGenerator : public Generator public: virtual QString subDirectoryForClass(const AbstractMetaClass *cls) const { - return "generated_cpp/" + cls->package().replace(".", "_") + "/"; + return "generated_cpp/" + toFileNameBase(cls->package()) + "/"; } void writeTypeInfo(QTextStream &s, const AbstractMetaType *type, Option option = NoOption, TypeSystem::Ownership ownership = TypeSystem::InvalidOwnership); @@ -95,11 +95,19 @@ class ShellGenerator : public Generator static bool isBuiltIn(const QString& name); static bool isSpecialStreamingOperator(const AbstractMetaFunction *fun); - + + static QString toFileNameBase(QString packageName) { return packageName.replace('.', '_').toLower(); } + static void writeInclude(QTextStream &stream, const Include &inc); + // this scope is used in writeFunctionArguments + const AbstractMetaClass* setCurrentScope(const AbstractMetaClass* scope); + const AbstractMetaClass* currentScope() const { return _currentScope; } + protected: - PriGenerator *priGenerator; + PriGenerator* priGenerator{}; + + const AbstractMetaClass* _currentScope{}; }; diff --git a/generator/shellheadergenerator.cpp b/generator/shellheadergenerator.cpp index 89cd64c5..20431a83 100644 --- a/generator/shellheadergenerator.cpp +++ b/generator/shellheadergenerator.cpp @@ -100,10 +100,12 @@ static bool field_lessThan(const AbstractMetaField* a, const AbstractMetaField* void ShellHeaderGenerator::write(QTextStream& s, const AbstractMetaClass* meta_class) { + setCurrentScope(meta_class); QString builtIn = ShellGenerator::isBuiltIn(meta_class->name()) ? "_builtin" : ""; - QString pro_file_name = meta_class->package().replace(".", "_") + builtIn + "/" + meta_class->package().replace(".", "_") + builtIn + ".pri"; + QString fileBaseName = toFileNameBase(meta_class->package() + builtIn); + QString pro_file_name = fileBaseName + "/" + fileBaseName + ".pri"; priGenerator->addHeader(pro_file_name, fileNameForClass(meta_class)); - setupGenerator->addClass(meta_class->package().replace(".", "_") + builtIn, meta_class); + setupGenerator->addClass(meta_class->package() + builtIn, meta_class); QString include_block = "PYTHONQTWRAPPER_" + meta_class->name().toUpper() + "_H"; @@ -221,6 +223,11 @@ void ShellHeaderGenerator::write(QTextStream& s, const AbstractMetaClass* meta_c // always do a direct call, since we want to call the real virtual function here s << "this->"; } + if (fun->originalName() == "operator=") { + // make it clear we don't want to call the (automatically generated) + // assignment operator of the promoter + s << meta_class->qualifiedCppName() << "::"; + } s << fun->originalName() << "("; writePromoterArgs(args, s); s << "); }" << endl; @@ -302,14 +309,26 @@ void ShellHeaderGenerator::write(QTextStream& s, const AbstractMetaClass* meta_c } for (AbstractMetaEnum * enum1 : enums) { - s << "enum " << enum1->name() << "{" << endl; + bool isEnumClass = enum1->typeEntry()->isEnumClass(); + s << "enum " << (isEnumClass ? "class " : "") << enum1->name() << "{" << endl; bool first = true; - QString scope = enum1->wasProtected() ? promoterClassName(meta_class) : meta_class->qualifiedCppName(); + QString scope = meta_class->isGlobalNamespace() ? QString() : + (enum1->wasProtected() ? promoterClassName(meta_class) : meta_class->qualifiedCppName()); + if (isEnumClass) { + scope += "::" + enum1->name(); + } for (AbstractMetaEnumValue * value : enum1->values()) { if (first) { first = false; } else { s << ", "; } - s << " " << value->name() << " = " << scope << "::" << value->name(); + QString assignedValue = scope + "::" + value->name(); + if (isEnumClass) { + s << " " << value->name() << " = " << "static_cast(" << scope << "::" << value->name() << ")"; + } + else { + s << " " << value->name() << " = " << scope << "::" << value->name(); + + } } s << "};" << endl; } @@ -347,7 +366,8 @@ void ShellHeaderGenerator::write(QTextStream& s, const AbstractMetaClass* meta_c } if (meta_class->typeEntry()->isValue() - && !copyConstructorSeen && defaultConstructorSeen) { + && !copyConstructorSeen && defaultConstructorSeen && !meta_class->typeEntry()->hasNoCopy()) + { QString className = meta_class->generateShellClass() ? shellClassName(meta_class) : meta_class->qualifiedCppName(); s << meta_class->qualifiedCppName() << "* new_" << meta_class->name() << "(const " << meta_class->qualifiedCppName() << "& other) {" << endl; s << className << "* a = new " << className << "();" << endl; @@ -431,7 +451,7 @@ void ShellHeaderGenerator::write(QTextStream& s, const AbstractMetaClass* meta_c s << "#endif // " << include_block << endl; - + setCurrentScope(nullptr); } void ShellHeaderGenerator::writePromoterArgs(AbstractMetaArgumentList& args, QTextStream& s) diff --git a/generator/shellimplgenerator.cpp b/generator/shellimplgenerator.cpp index 798e96a2..2145af03 100644 --- a/generator/shellimplgenerator.cpp +++ b/generator/shellimplgenerator.cpp @@ -70,8 +70,11 @@ static void writeHelperCode(QTextStream &, const AbstractMetaClass *) void ShellImplGenerator::write(QTextStream &s, const AbstractMetaClass *meta_class) { + setCurrentScope(meta_class); + QString builtIn = ShellGenerator::isBuiltIn(meta_class->name())?"_builtin":""; - QString pro_file_name = meta_class->package().replace(".", "_") + builtIn + "/" + meta_class->package().replace(".", "_") + builtIn + ".pri"; + QString fileBaseName = toFileNameBase(meta_class->package() + builtIn); + QString pro_file_name = fileBaseName + "/" + fileBaseName + ".pri"; priGenerator->addSource(pro_file_name, fileNameForClass(meta_class)); s << "#include \"PythonQtWrapper_" << meta_class->name() << ".h\"" << endl << endl; @@ -302,13 +305,13 @@ void ShellImplGenerator::write(QTextStream &s, const AbstractMetaClass *meta_cla } } s << "("; - if (scriptFunctionName.startsWith("operator>>")) { + if (scriptFunctionName.startsWith("operator>>") && !fun->wasProtected()) { s << wrappedObject << " >>" << args.at(0)->argumentName(); - } else if (scriptFunctionName.startsWith("operator<<")) { + } else if (scriptFunctionName.startsWith("operator<<") && !fun->wasProtected()) { s << wrappedObject << " <<" << args.at(0)->argumentName(); - } else if (scriptFunctionName.startsWith("operator[]")) { + } else if (scriptFunctionName.startsWith("operator[]") && !fun->wasProtected()) { s << wrappedObject << "[" << args.at(0)->argumentName() << "]"; - } else if (scriptFunctionName.startsWith("operator") && args.size()==1) { + } else if (scriptFunctionName.startsWith("operator") && args.size()==1 && !fun->wasProtected()) { QString op = scriptFunctionName.mid(8); s << wrappedObject << op << " " << args.at(0)->argumentName(); } else { @@ -326,7 +329,13 @@ void ShellImplGenerator::write(QTextStream &s, const AbstractMetaClass *meta_cla s << " theWrappedObject->"; } } - s << fun->originalName() << "("; + if (fun->wasProtected()) { + // this is different e.g. for operators + s << fun->name() << "("; + } + else { + s << fun->originalName() << "("; + } for (int i = 0; i < args.size(); ++i) { if (i > 0) s << ", "; @@ -360,6 +369,8 @@ void ShellImplGenerator::write(QTextStream &s, const AbstractMetaClass *meta_cla if (meta_class->qualifiedCppName().contains("Ssl")) { s << "#endif" << endl; } + + setCurrentScope(nullptr); } void ShellImplGenerator::writeInjectedCode(QTextStream &s, const AbstractMetaClass *meta_class) diff --git a/generator/typesystem.cpp b/generator/typesystem.cpp index c3f6ded9..c9a2607f 100644 --- a/generator/typesystem.cpp +++ b/generator/typesystem.cpp @@ -153,8 +153,10 @@ class StackElement class Handler : public QXmlDefaultHandler { public: - Handler(TypeDatabase *database, bool generate) - : m_database(database), m_generate(generate ? TypeEntry::GenerateAll : TypeEntry::GenerateForSubclass) + Handler(TypeDatabase *database, unsigned int qtVersion, bool generate) + : m_database(database) + , m_qtVersion(qtVersion) + , m_generate(generate ? TypeEntry::GenerateAll : TypeEntry::GenerateForSubclass) { m_current_enum = 0; current = 0; @@ -195,6 +197,7 @@ class Handler : public QXmlDefaultHandler tagNames["reference-count"] = StackElement::ReferenceCount; } + bool startDocument() { m_nestingLevel = 0; m_disabledLevel = -1; return true; } bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts); bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName); @@ -212,6 +215,7 @@ class Handler : public QXmlDefaultHandler bool importFileElement(const QXmlAttributes &atts); bool convertBoolean(const QString &, const QString &, bool); + bool qtVersionMatches(const QXmlAttributes& atts, bool& ok); TypeDatabase *m_database; StackElement* current; @@ -227,6 +231,10 @@ class Handler : public QXmlDefaultHandler FieldModificationList m_field_mods; QHash tagNames; + + unsigned int m_qtVersion{}; + int m_nestingLevel{}; // current nesting level, needed to reset m_disabledLevel if leaving element + int m_disabledLevel{}; // if this is != 0, elements should be ignored }; bool Handler::error(const QXmlParseException &e) @@ -261,7 +269,7 @@ void Handler::fetchAttributeValues(const QString &name, const QXmlAttributes &at QString key = atts.localName(i).toLower(); QString val = atts.value(i); - if (!acceptedAttributes->contains(key)) { + if (!acceptedAttributes->contains(key) && key != "since-version" && key != "before-version") { ReportHandler::warning(QString("Unknown attribute for '%1': '%2'").arg(name).arg(key)); } else { (*acceptedAttributes)[key] = val; @@ -272,7 +280,14 @@ void Handler::fetchAttributeValues(const QString &name, const QXmlAttributes &at bool Handler::endElement(const QString &, const QString &localName, const QString &) { QString tagName = localName.toLower(); - if(tagName == "import-file") + int oldNestingLevel = m_nestingLevel--; + if (m_disabledLevel >= 0) { + if (m_disabledLevel == oldNestingLevel) { + m_disabledLevel = -1; + } + return true; + } + if(tagName == "import-file" || tagName == "group") return true; if (!current) @@ -321,7 +336,7 @@ bool Handler::endElement(const QString &, const QString &localName, const QStrin m_code_snips.last().addTemplateInstance(current->value.templateInstance); }else if(current->parent->type == StackElement::Template){ current->parent->value.templateEntry->addTemplateInstance(current->value.templateInstance); - }else if(current->parent->type == StackElement::CustomMetaConstructor || current->parent->type == StackElement::CustomMetaConstructor){ + }else if(current->parent->type == StackElement::CustomMetaConstructor){ current->parent->value.customFunction->addTemplateInstance(current->value.templateInstance); }else if(current->parent->type == StackElement::ConversionRule){ m_function_mods.last().argument_mods.last().conversion_rules.last().addTemplateInstance(current->value.templateInstance); @@ -454,13 +469,59 @@ bool Handler::convertBoolean(const QString &_value, const QString &attributeName } } +bool Handler::qtVersionMatches(const QXmlAttributes& atts, bool& ok) +{ + ok = true; + int beforeIndex = atts.index("before-version"); + if (beforeIndex >= 0) { + uint beforeVersion = TypeSystem::qtVersionFromString(atts.value(beforeIndex), ok); + if (ok) { + if (m_qtVersion >= beforeVersion) { + return false; + } + } + else { + m_error = "Invalid 'before-version' version string: " + atts.value(beforeIndex); + } + } + int sinceIndex = atts.index("since-version"); + if (sinceIndex >= 0) { + uint sinceVersion = TypeSystem::qtVersionFromString(atts.value(sinceIndex), ok); + if (ok) { + if (m_qtVersion < sinceVersion) { + return false; + } + } + else { + m_error = "Invalid 'since-version' version string: " + atts.value(sinceIndex); + } + } + return true; +} + bool Handler::startElement(const QString &, const QString &n, const QString &, const QXmlAttributes &atts) { QString tagName = n.toLower(); - if(tagName == "import-file"){ + m_nestingLevel++; + if (m_disabledLevel >= 0 && m_disabledLevel < m_nestingLevel) { + return true; + } + bool ok; + bool versionMatches = qtVersionMatches(atts, ok); + if (!ok) { + return false; // abort + } else if (!versionMatches) { + m_disabledLevel = m_nestingLevel; + return true; + } + + if (tagName == "import-file") { return importFileElement(atts); } + else if (tagName == "group") { // non-functional element to handle version ranges + return true; + } std::unique_ptr element(new StackElement(current)); @@ -844,7 +905,7 @@ bool Handler::startElement(const QString &, const QString &n, return false; } - if (!m_database->parseFile(name, convertBoolean(attributes["generate"], "generate", true))) { + if (!m_database->parseFile(name, m_qtVersion, convertBoolean(attributes["generate"], "generate", true))) { m_error = QString("Failed to parse: '%1'").arg(name); return false; } @@ -1448,14 +1509,27 @@ TypeDatabase *TypeDatabase::instance() TypeDatabase::TypeDatabase() : m_suppressWarnings(true) { - addType(new StringTypeEntry("QString")); + StringTypeEntry* mainStringType = new StringTypeEntry("QString"); + addType(mainStringType); StringTypeEntry *e = new StringTypeEntry("QLatin1String"); e->setPreferredConversion(false); + e->setEquivalentType(mainStringType); addType(e); e = new StringTypeEntry("QStringRef"); e->setPreferredConversion(false); + e->setEquivalentType(mainStringType); + addType(e); + + e = new StringTypeEntry("QStringView"); + e->setPreferredConversion(false); + e->setEquivalentType(mainStringType); + addType(e); + + e = new StringTypeEntry("QAnyStringView"); + e->setPreferredConversion(false); + e->setEquivalentType(mainStringType); addType(e); e = new StringTypeEntry("QXmlStreamStringRef"); @@ -1499,7 +1573,20 @@ TypeDatabase::TypeDatabase() : m_suppressWarnings(true) addRemoveFunctionToTemplates(this); } -bool TypeDatabase::parseFile(const QString &filename, bool generate) +void TypeDatabase::finalSetup() +{ + TypeEntry* byteArrayType = findType("QByteArray"); + if (byteArrayType) { + // Support QByteArrayView as alternative parameter type. + // Using StringTypeEntry for it, because no wrappers are generated for those types + StringTypeEntry* e = new StringTypeEntry("QByteArrayView"); + e->setPreferredConversion(false); + e->setEquivalentType(byteArrayType); + addType(e); + } +} + +bool TypeDatabase::parseFile(const QString &filename, unsigned int qtVersion, bool generate) { QFile file(filename); @@ -1509,7 +1596,7 @@ bool TypeDatabase::parseFile(const QString &filename, bool generate) int count = m_entries.size(); QXmlSimpleReader reader; - Handler handler(this, generate); + Handler handler(this, qtVersion, generate); reader.setContentHandler(&handler); reader.setErrorHandler(&handler); @@ -1681,19 +1768,6 @@ QString FlagsTypeEntry::jniName() const return "jint"; } -void EnumTypeEntry::addEnumValueRedirection(const QString &rejected, const QString &usedValue) -{ - m_enum_redirections << EnumValueRedirection(rejected, usedValue); -} - -QString EnumTypeEntry::enumValueRedirection(const QString &value) const -{ - for (int i=0; ijavaQualifier() + "." + targetLangName(); @@ -1761,8 +1835,9 @@ FlagsTypeEntry *TypeDatabase::findFlagsType(const QString &name) const return fte ? fte : (FlagsTypeEntry *) m_flags_entries.value(name); } -QString TypeDatabase::globalNamespaceClassName(const TypeEntry * /*entry*/) { - return QLatin1String("Global"); +QString TypeDatabase::globalNamespaceClassName(const TypeEntry * entry) +{ + return "Qt" + entry->javaPackage().split('.').back(); } @@ -2048,3 +2123,27 @@ QByteArray TypeSystem::normalizedSignature(const char* signature) result.replace("QStringList", "QStringList"); return result; } + +unsigned int TypeSystem::qtVersionFromString(const QString& value, bool& ok) +{ + ok = true; + QList values; + for (QString dotValue : value.split('.')) + { + dotValue = dotValue.trimmed(); + bool ok2; + values.append(dotValue.toUInt(&ok2)); + if (!ok2) { + ok = false; + } + } + uint result = values[0] << 16; + if (values.size() >= 2) { + result += values[1] << 8; + } + if (values.size() >= 3) { + result += values[2]; + } + return result; +} + diff --git a/generator/typesystem.h b/generator/typesystem.h index bee5b0fc..a6d8b4cb 100644 --- a/generator/typesystem.h +++ b/generator/typesystem.h @@ -78,7 +78,7 @@ * changed to Qt::endl. */ #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) - static const constexpr decltype (Qt::endl) *endl = Qt::endl; + static constexpr decltype (Qt::endl) *endl = Qt::endl; #endif /* END: Qt compatibility. */ @@ -167,6 +167,9 @@ namespace TypeSystem { //! A better normalized signature, which takes care of PODs with the same name QByteArray normalizedSignature(const char* signature); + + //! Determine version ID from version string + unsigned int qtVersionFromString(const QString& value, bool& ok); } struct ReferenceCount @@ -546,6 +549,8 @@ class TypeEntry virtual bool isNativeIdBased() const { return false; } + virtual TypeEntry* equivalentType() const { return nullptr; } + private: QString m_name; Type m_type; @@ -678,7 +683,8 @@ class EnumTypeEntry : public TypeEntry : TypeEntry(nspace.isEmpty() ? enumName : nspace + QLatin1String("::") + enumName, EnumType), m_flags(0), - m_extensible(false) + m_extensible(false), + m_force_integer(false) { m_qualifier = nspace; m_java_name = enumName; @@ -723,8 +729,8 @@ class EnumTypeEntry : public TypeEntry void addEnumValueRejection(const QString &name) { m_rejected_enums << name; } QStringList enumValueRejections() const { return m_rejected_enums; } - void addEnumValueRedirection(const QString &rejected, const QString &usedValue); - QString enumValueRedirection(const QString &value) const; + bool isEnumClass() const { return m_enum_class; } + void setEnumClass(bool enum_class) { m_enum_class = enum_class; } bool forceInteger() const { return m_force_integer; } void setForceInteger(bool force) { m_force_integer = force; } @@ -739,11 +745,11 @@ class EnumTypeEntry : public TypeEntry QString m_upper_bound; QStringList m_rejected_enums; - QList m_enum_redirections; FlagsTypeEntry *m_flags; bool m_extensible; + bool m_enum_class; bool m_force_integer; }; @@ -797,6 +803,7 @@ class ComplexTypeEntry : public TypeEntry m_generic_class(false), m_createShell(false), m_createPromoter(false), + m_noCopy(false), m_type_flags(0) { Include inc; @@ -927,6 +934,9 @@ class ComplexTypeEntry : public TypeEntry bool isGenericClass() const { return m_generic_class; } void setGenericClass(bool isGeneric) { m_generic_class = isGeneric; } + bool hasNoCopy() const { return m_noCopy; } + void setNoCopy(bool noCopy) { m_noCopy = noCopy; } + private: IncludeList m_extra_includes; Include m_include; @@ -944,6 +954,7 @@ class ComplexTypeEntry : public TypeEntry uint m_generic_class : 1; uint m_createShell : 1; uint m_createPromoter : 1; + uint m_noCopy : 1; QString m_polymorphic_id_value; QString m_lookup_name; @@ -1004,8 +1015,14 @@ class ValueTypeEntry : public ComplexTypeEntry virtual bool isNativeIdBased() const { return true; } + virtual TypeEntry* equivalentType() const { return _equivalentType; } + void setEquivalentType(TypeEntry* typeEntry) { _equivalentType = typeEntry; } + protected: ValueTypeEntry(const QString &name, Type t) : ComplexTypeEntry(name, t) { } + +private: + TypeEntry* _equivalentType{}; }; @@ -1216,7 +1233,8 @@ class TypeDatabase static QString globalNamespaceClassName(const TypeEntry *te); QString filename() const { return "typesystem.txt"; } - bool parseFile(const QString &filename, bool generate = true); + bool parseFile(const QString &filename, unsigned int qtVersion, bool generate = true); + void finalSetup(); private: bool m_suppressWarnings; diff --git a/generator/typesystem_core.xml b/generator/typesystem_core.xml index d21f7080..6fe37a3d 100644 --- a/generator/typesystem_core.xml +++ b/generator/typesystem_core.xml @@ -1,5 +1,5 @@ - + @@ -19,21 +19,38 @@ + + + + + + + + + + + + + + + + + @@ -203,6 +220,8 @@ + + @@ -220,6 +239,7 @@ + @@ -241,6 +261,7 @@ + @@ -416,6 +437,8 @@ + + @@ -444,43 +467,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/generator/typesystem_gui.xml b/generator/typesystem_gui.xml index 4b1c638d..dd772ca8 100644 --- a/generator/typesystem_gui.xml +++ b/generator/typesystem_gui.xml @@ -1,5 +1,5 @@ - + @@ -22,11 +22,13 @@ + + @@ -84,6 +86,7 @@ + @@ -104,6 +107,7 @@ + @@ -156,13 +160,15 @@ + + - + @@ -207,6 +213,7 @@ + @@ -247,7 +254,8 @@ - + + @@ -388,6 +396,8 @@ + + @@ -449,6 +459,7 @@ + @@ -523,8 +534,8 @@ - - + + @@ -571,7 +582,8 @@ - + + @@ -602,6 +614,8 @@ + + @@ -609,6 +623,7 @@ + @@ -635,6 +650,7 @@ + @@ -725,6 +741,7 @@ + @@ -766,6 +783,7 @@ + @@ -777,8 +795,8 @@ - - + + @@ -787,8 +805,6 @@ - - @@ -886,6 +902,7 @@ + @@ -972,7 +989,7 @@ - + @@ -1006,7 +1023,11 @@ QImage* new_QImage( const uchar * data, int width, int height, QImage::Format format ) { QImage* image = new QImage(width, height, format); +#if QT_VERSION >= QT_VERSION_CHECK(5,10,0) + memcpy(image->bits(), data, image->sizeInBytes()); +#else memcpy(image->bits(), data, image->byteCount()); +#endif return image; } @@ -1014,7 +1035,11 @@ PyObject* bits(QImage* image) { return PythonQtPrivate::wrapMemoryAsBuffer(image->bits(), image->bytesPerLine()* image->height()); } -#if QT_VERSION >= QT_VERSION_CHECK(4,7,0) +#if QT_VERSION >= QT_VERSION_CHECK(5,10,0) +PyObject* constBits(QImage* image) { + return PythonQtPrivate::wrapMemoryAsBuffer(image->constBits(), image->sizeInBytes()); +} +#elif QT_VERSION >= QT_VERSION_CHECK(4,7,0) PyObject* constBits(QImage* image) { return PythonQtPrivate::wrapMemoryAsBuffer(image->constBits(), image->byteCount()); } @@ -1106,6 +1131,7 @@ PyObject* constScanLine(QImage* image, int line) { + @@ -1266,6 +1292,7 @@ PyObject* constScanLine(QImage* image, int line) { + @@ -1311,6 +1338,7 @@ PyObject* constScanLine(QImage* image, int line) { + @@ -1653,6 +1681,19 @@ PyObject* constScanLine(QImage* image, int line) { + + + + + + + + + + + + + @@ -2082,18 +2123,20 @@ PyObject* constScanLine(QImage* image, int line) { - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -2487,12 +2530,18 @@ PyObject* constScanLine(QImage* image, int line) { - + - + + + + + + + @@ -2900,6 +2949,7 @@ PyObject* constScanLine(QImage* image, int line) { + @@ -3042,6 +3092,7 @@ PyObject* constScanLine(QImage* image, int line) { + @@ -3057,9 +3108,11 @@ PyObject* constScanLine(QImage* image, int line) { - + + + @@ -3080,6 +3133,9 @@ PyObject* constScanLine(QImage* image, int line) { + @@ -3139,6 +3195,15 @@ PyObject* constScanLine(QImage* image, int line) { + + + + + + + + + diff --git a/generator/typesystem_multimedia.xml b/generator/typesystem_multimedia.xml index c206c518..5f1cb7e3 100644 --- a/generator/typesystem_multimedia.xml +++ b/generator/typesystem_multimedia.xml @@ -1,5 +1,5 @@ - + @@ -7,12 +7,23 @@ + + + + + + + - + + + + @@ -52,6 +63,7 @@ + @@ -75,7 +87,7 @@ - + @@ -108,7 +120,7 @@ - + @@ -116,10 +128,18 @@ + + + + + + + + @@ -176,4 +196,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/generator/typesystem_network.xml b/generator/typesystem_network.xml index abb86257..239c574d 100644 --- a/generator/typesystem_network.xml +++ b/generator/typesystem_network.xml @@ -1,16 +1,16 @@ - + - - - - + + + + @@ -42,6 +42,7 @@ + @@ -76,6 +77,7 @@ + @@ -83,6 +85,7 @@ + @@ -105,8 +108,6 @@ - - @@ -167,11 +168,10 @@ - - + @@ -185,6 +185,8 @@ + + @@ -224,6 +226,23 @@ + + + + + + + + + + + + + + + + + diff --git a/generator/typesystem_opengl.xml b/generator/typesystem_opengl.xml index c0c6d851..b8de602c 100644 --- a/generator/typesystem_opengl.xml +++ b/generator/typesystem_opengl.xml @@ -1,7 +1,6 @@ - - - + + diff --git a/generator/typesystem_qml.xml b/generator/typesystem_qml.xml index ebc664fd..60db1e5e 100644 --- a/generator/typesystem_qml.xml +++ b/generator/typesystem_qml.xml @@ -1,20 +1,46 @@ - + + + + + + + + + - + + + + + + + + + + + + + + + + + + + diff --git a/generator/typesystem_quick.xml b/generator/typesystem_quick.xml index bd255e68..ac889ecc 100644 --- a/generator/typesystem_quick.xml +++ b/generator/typesystem_quick.xml @@ -1,5 +1,5 @@ - + @@ -29,9 +29,16 @@ - + + - + + + + + + + diff --git a/generator/typesystem_sql.xml b/generator/typesystem_sql.xml index f3377411..334860d8 100644 --- a/generator/typesystem_sql.xml +++ b/generator/typesystem_sql.xml @@ -1,5 +1,5 @@ - + diff --git a/generator/typesystem_svg.xml b/generator/typesystem_svg.xml index 815cfca5..c4e5a6a7 100644 --- a/generator/typesystem_svg.xml +++ b/generator/typesystem_svg.xml @@ -1,5 +1,5 @@ - + @@ -8,7 +8,7 @@ - + @@ -24,6 +24,7 @@ + diff --git a/generator/typesystem_uitools.xml b/generator/typesystem_uitools.xml index 1b2c2edb..9b5b3b9d 100644 --- a/generator/typesystem_uitools.xml +++ b/generator/typesystem_uitools.xml @@ -1,4 +1,4 @@ - + diff --git a/generator/typesystem_webenginewidgets.xml b/generator/typesystem_webenginewidgets.xml index 1e545dbf..e89b61c9 100644 --- a/generator/typesystem_webenginewidgets.xml +++ b/generator/typesystem_webenginewidgets.xml @@ -1,17 +1,23 @@ - + + + + + + + @@ -20,6 +26,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -38,6 +73,7 @@ + diff --git a/generator/typesystem_webkit.xml b/generator/typesystem_webkit.xml index b1a4eb7f..84ba47e4 100644 --- a/generator/typesystem_webkit.xml +++ b/generator/typesystem_webkit.xml @@ -1,5 +1,5 @@ - + diff --git a/generator/typesystem_xml.xml b/generator/typesystem_xml.xml index f2fbe1bc..27d8f24f 100644 --- a/generator/typesystem_xml.xml +++ b/generator/typesystem_xml.xml @@ -1,5 +1,6 @@ - + + @@ -35,6 +36,8 @@ + + @@ -179,6 +182,7 @@ + diff --git a/generator/typesystem_xmlpatterns.xml b/generator/typesystem_xmlpatterns.xml index 958ddfac..c61126d9 100644 --- a/generator/typesystem_xmlpatterns.xml +++ b/generator/typesystem_xmlpatterns.xml @@ -1,6 +1,5 @@ - - + @@ -112,6 +111,8 @@ + + diff --git a/src/PythonQt.cpp b/src/PythonQt.cpp index 288ae57f..7eb47c1d 100644 --- a/src/PythonQt.cpp +++ b/src/PythonQt.cpp @@ -126,16 +126,14 @@ void PythonQt::init(int flags, const QByteArray& pythonQtModuleName) } else { qRegisterMetaType("size_t"); } -#if QT_VERSION < 0x060000 - int stringRefId = qRegisterMetaType("QStringRef"); - PythonQtConv::registerMetaTypeToPythonConverter(stringRefId, PythonQtConv::convertFromStringRef); -#endif + PythonQtConv::registerStringViewTypes(); int objectPtrListId = qRegisterMetaType >("QList"); PythonQtConv::registerMetaTypeToPythonConverter(objectPtrListId, PythonQtConv::convertFromQListOfPythonQtObjectPtr); PythonQtConv::registerPythonToMetaTypeConverter(objectPtrListId, PythonQtConv::convertToQListOfPythonQtObjectPtr); PythonQtRegisterToolClassesTemplateConverter(int); + PythonQtRegisterToolClassesTemplateConverter(bool); PythonQtRegisterToolClassesTemplateConverter(float); PythonQtRegisterToolClassesTemplateConverter(double); PythonQtRegisterToolClassesTemplateConverter(qint32); @@ -2081,6 +2079,73 @@ void PythonQtPrivate::registerCPPClass(const char* typeName, const char* parentT } } +namespace { + + void addObjectToPackage(PyObject* obj, const char* name, const char* packageName, PyObject* package) + { + if (PyModule_AddObject(package, name, obj) < 0) { + Py_DECREF(obj); + std::cerr << "failed to add " << name << " to " << packageName << "\n"; + } + } + +}; + +void PythonQtPrivate::registerGlobalNamespace(const char* typeName, const char* packageName, PythonQtQObjectCreatorFunctionCB* wrapperCreator, const QMetaObject& metaObject, PyObject* module) +{ + registerCPPClass(typeName, "", packageName, wrapperCreator, nullptr, module, 0); + + PyObject* package = module ? module : PythonQt::priv()->packageByName(packageName); + PythonQtClassInfo* classInfo = PythonQt::priv()->getClassInfo(typeName); + PyObject* globalNamespace = classInfo->pythonQtClassWrapper(); + + // Collect the names of global methods + QSet methodNames; + for (int i = metaObject.methodOffset(); i < metaObject.methodCount(); i++) { + methodNames.insert(metaObject.method(i).name()); + } + QByteArray staticPrefix = "static_" + QByteArray(typeName) + "_"; // every static method starts with this string + for (auto name: methodNames) { + if (name.startsWith(staticPrefix)) { // non-static methods wouldn't work (and should not exists) + name = name.mid(staticPrefix.length()); + PyObject* obj = PyObject_GetAttrString(globalNamespace, name.constData()); + if (obj) { + addObjectToPackage(obj, name, packageName, package); + } + else { + std::cerr << "method not found " << name.constData() << " in " << typeName << std::endl; + } + } + } + + // Global enums + for (int i = metaObject.enumeratorOffset(); i < metaObject.enumeratorCount(); i++) { + QMetaEnum metaEnum = metaObject.enumerator(i); + PyObject* obj = PyObject_GetAttrString(globalNamespace, metaEnum.name()); + if (obj) { + addObjectToPackage(obj, metaEnum.name(), packageName, package); + } + else { + std::cerr << "enum type not found " << metaEnum.name() << " in " << typeName << std::endl; + } +#if QT_VERSION > 0x050800 + bool isScoped = metaEnum.isScoped(); +#else + bool isScoped = false; +#endif + if (!isScoped) { + for (int j = 0; j < metaEnum.keyCount(); j++) { + QByteArray key = PythonQtClassInfo::escapeReservedNames(metaEnum.key(j)); + int value = metaEnum.value(j); + PyObject* obj = PyInt_FromLong(value); + addObjectToPackage(obj, key, packageName, package); + } + } + } + + PythonQtClassInfo::addGlobalNamespaceWrapper(classInfo); +} + PyObject* PythonQtPrivate::packageByName(const char* name) { if (name==nullptr || name[0]==0) { diff --git a/src/PythonQt.h b/src/PythonQt.h index dcce7135..b16c5848 100644 --- a/src/PythonQt.h +++ b/src/PythonQt.h @@ -742,6 +742,9 @@ class PYTHONQT_EXPORT PythonQtPrivate : public QObject { */ void registerCPPClass(const char* typeName, const char* parentTypeName = nullptr, const char* package = nullptr, PythonQtQObjectCreatorFunctionCB* wrapperCreator = nullptr, PythonQtShellSetInstanceWrapperCB* shell = nullptr, PyObject* module = nullptr, int typeSlots = 0); + //! as an alternative to registerClass, you can tell PythonQt the names of QObject derived classes + void registerGlobalNamespace(const char* typeName, const char* package, PythonQtQObjectCreatorFunctionCB* wrapperCreator, const QMetaObject& metaObject, PyObject* module = nullptr); + //! as an alternative to registerClass, you can tell PythonQt the names of QObject derived classes //! and it will register the classes when it first sees a pointer to such a derived class void registerQObjectClassNames(const QStringList& names); diff --git a/src/PythonQtClassInfo.cpp b/src/PythonQtClassInfo.cpp index 3c011dd3..96d99386 100644 --- a/src/PythonQtClassInfo.cpp +++ b/src/PythonQtClassInfo.cpp @@ -49,6 +49,14 @@ QHash PythonQtMethodInfo::_parameterTypeDict; +// List of registered global namespace wrappers that might contain a top-level enum definition +QList PythonQtClassInfo::_globalNamespaceWrappers; + +// List of words that are reserved in Python, but not in C++, so they need escaping +QSet PythonQtClassInfo::_reservedNames{ + "None", "True", "False" +}; + PythonQtClassInfo::PythonQtClassInfo() { _meta = nullptr; _constructors = nullptr; @@ -279,7 +287,7 @@ bool PythonQtClassInfo::lookForEnumAndCache(const QMetaObject* meta, const char* if (e.isFlag()) continue; for (int j=0; j < e.keyCount(); j++) { - if (qstrcmp(e.key(j), memberName)==0) { + if (escapeReservedNames(e.key(j)) == memberName) { PyObject* enumType = findEnumWrapper(e.name()); if (enumType) { PythonQtObjectPtr enumValuePtr; @@ -846,11 +854,20 @@ PyObject* PythonQtClassInfo::findEnumWrapper(const QByteArray& name, PythonQtCla return nullptr; } } + PyObject* enumWrapper = nullptr; if (localScope) { - return localScope->findEnumWrapper(name); - } else { - return nullptr; + enumWrapper = localScope->findEnumWrapper(name); } + if (!enumWrapper) { + // it might be a top-level enum - search in all currently registered global namespace wrappers + for (PythonQtClassInfo* globalWrapper : _globalNamespaceWrappers) { + enumWrapper = globalWrapper->findEnumWrapper(name); + if (enumWrapper) { + break; + } + } + } + return enumWrapper; } void PythonQtClassInfo::createEnumWrappers(const QMetaObject* meta) @@ -858,7 +875,14 @@ void PythonQtClassInfo::createEnumWrappers(const QMetaObject* meta) for (int i = meta->enumeratorOffset();ienumeratorCount();i++) { QMetaEnum e = meta->enumerator(i); PythonQtObjectPtr p; - p.setNewRef(PythonQtPrivate::createNewPythonQtEnumWrapper(e.name(), _pythonQtClassWrapper.object())); + p.setNewRef(PythonQtPrivate::createNewPythonQtEnumWrapper(e.name(), _pythonQtClassWrapper)); + // add enum values to the enum type itself, in case enum value names are so generic + // that they are not unique + for (int j = 0; j < e.keyCount(); j++) { + PythonQtObjectPtr enumValuePtr; + enumValuePtr.setNewRef(PythonQtPrivate::createEnumValueInstance(p.object(), e.value(j))); + p.addVariable(escapeReservedNames(e.key(j)), enumValuePtr.toLocalVariant()); + } _enumWrappers.append(p); } } @@ -1004,6 +1028,21 @@ PythonQtVoidPtrCB* PythonQtClassInfo::referenceCountingUnrefCB() return _unrefCallback; } +QByteArray PythonQtClassInfo::escapeReservedNames(const QByteArray& name) +{ + if (_reservedNames.contains(name)) { + return name + "_"; + } + else { + return name; + } +} + +void PythonQtClassInfo::addGlobalNamespaceWrapper(PythonQtClassInfo* namespaceWrapper) +{ + _globalNamespaceWrappers.insert(0, namespaceWrapper); +} + void PythonQtClassInfo::updateRefCountingCBs() { if (!_refCallback) { @@ -1032,13 +1071,13 @@ PyObject* PythonQtClassInfo::getPythonTypeForProperty( const QString& name ) PythonQtClassInfo* PythonQtClassInfo::getClassInfoForProperty( const QString& name ) { QByteArray typeName; - PythonQtMemberInfo info1 = member(QStringToPythonConstCharPointer(name)); - if (info1._type == PythonQtMemberInfo::Property) { - typeName = info1._property.typeName(); + PythonQtMemberInfo info = member(QStringToPythonConstCharPointer(name)); + if (info._type == PythonQtMemberInfo::Property) { + typeName = info._property.typeName(); } else { - PythonQtMemberInfo info2 = member(QStringToPythonConstCharPointer(QString("py_get_" + name))); - if (info2._type == PythonQtMemberInfo::Slot) { - typeName = info2._slot->parameters().at(0).name; + info = member(QStringToPythonConstCharPointer(QString("py_get_" + name))); + if (info._type == PythonQtMemberInfo::Slot) { + typeName = info._slot->parameters().at(0).name; } } if (!typeName.isEmpty()) { @@ -1107,3 +1146,4 @@ PythonQtMemberInfo::PythonQtMemberInfo( const QMetaProperty& prop ) _enumValue = nullptr; _pythonType = nullptr; } + diff --git a/src/PythonQtClassInfo.h b/src/PythonQtClassInfo.h index c77aaa72..7266e354 100644 --- a/src/PythonQtClassInfo.h +++ b/src/PythonQtClassInfo.h @@ -235,6 +235,13 @@ class PYTHONQT_EXPORT PythonQtClassInfo { //! _typeSlots with Type_RichCompare. The result is cached internally. bool supportsRichCompare(); + //! Sometimes enum values use a reserved name in Python. In this case + //! replace it with something that is not reserved + static QByteArray escapeReservedNames(const QByteArray& name); + + //! Add a wrapper that contains global enums + static void addGlobalNamespaceWrapper(PythonQtClassInfo* namespaceWrapper); + private: void updateRefCountingCBs(); @@ -298,6 +305,9 @@ class PYTHONQT_EXPORT PythonQtClassInfo { bool _searchPolymorphicHandlerOnParent; bool _searchRefCountCB; + static QList _globalNamespaceWrappers; + + static QSet _reservedNames; }; //--------------------------------------------------------------- diff --git a/src/PythonQtConversion.cpp b/src/PythonQtConversion.cpp index 5d18f5a1..8b24e1e8 100644 --- a/src/PythonQtConversion.cpp +++ b/src/PythonQtConversion.cpp @@ -48,6 +48,22 @@ #include #include +#if QT_VERSION < 0x060000 +#include + +Q_DECLARE_METATYPE(QStringRef) + +int PythonQtConv::stringRefTypeId = 0; +#else +#include +#include +#include + +int PythonQtConv::stringViewTypeId = 0; +int PythonQtConv::anyStringViewTypeId = 0; +int PythonQtConv::byteArrayViewTypeId = 0; +#endif + QHash PythonQtConv::_metaTypeToPythonConverters; QHash PythonQtConv::_pythonToMetaTypeConverters; @@ -255,14 +271,14 @@ PyObject* PythonQtConv::convertQtValueToPythonInternal(int type, const void* dat // check if we have a QList of pointers, which we can circumvent with a QList if (info.isQList && (info.innerNamePointerCount == 1)) { static int id = QMetaType::type("QList"); - PythonQtArgumentFrame_ADD_VARIANT_VALUE(frame, QVariant::Type(id), ptr); + PythonQtArgumentFrame_ADD_VARIANT_VALUE_BY_ID(frame, id, ptr); // return the constData pointer that will be filled with the result value later on ptr = (void*)((QVariant*)ptr)->constData(); } if (!ptr && info.typeId != PythonQtMethodInfo::Unknown) { // everything else is stored in a QVariant, if we know the meta type... - PythonQtArgumentFrame_ADD_VARIANT_VALUE(frame, QVariant::Type(info.typeId), ptr); + PythonQtArgumentFrame_ADD_VARIANT_VALUE_BY_ID(frame, info.typeId, ptr); // return the constData pointer that will be filled with the result value later on ptr = (void*)((QVariant*)ptr)->constData(); } @@ -385,6 +401,9 @@ void* PythonQtConv::ConvertPythonToQt(const PythonQtMethodInfo::ParameterInfo& i if (PyObject_TypeCheck(obj, &PythonQtInstanceWrapper_Type) && info.typeId != PythonQtMethodInfo::Variant && +#if QT_VERSION >= 0x060000 + info.typeId != byteArrayViewTypeId && // this case is handled later on +#endif !PythonQt::priv()->isPythonQtAnyObjectPtrMetaId(info.typeId)) { // if we have a Qt wrapper object and if we do not need a QVariant, we do the following: // (the Variant case is handled below in a switch) @@ -583,13 +602,7 @@ void* PythonQtConv::ConvertPythonToQt(const PythonQtMethodInfo::ParameterInfo& i break; case QMetaType::QByteArray: { - QByteArray bytes = PyObjGetBytes(obj, strict, ok); - if (!ok && !strict) { - // since Qt uses QByteArray in many places for identifier strings, - // we need to allow implicit conversion from unicode as well. - // We allow that for both Python 2.x and 3.x to be compatible. - bytes = PyObjGetString(obj, true, ok).toUtf8(); - } + QByteArray bytes = PyObjGetBytesAllowString(obj, strict, ok); if (ok) { PythonQtArgumentFrame_ADD_VARIANT_VALUE_IF_NEEDED(alreadyAllocatedCPPObject,frame, QVariant(bytes), ptr); ptr = (void*)((QVariant*)ptr)->constData(); @@ -633,7 +646,8 @@ void* PythonQtConv::ConvertPythonToQt(const PythonQtMethodInfo::ParameterInfo& i // we have a exact enum type match: val = PyInt_AS_LONG(obj); ok = true; - } else if (!strict) { + } + else if (!strict) { // we try to get any integer, when not being strict. If we are strict, integers are not wanted because // we want an integer overload to be taken first! val = (unsigned int)PyObjGetLongLong(obj, false, ok); @@ -641,17 +655,82 @@ void* PythonQtConv::ConvertPythonToQt(const PythonQtMethodInfo::ParameterInfo& i if (ok) { PythonQtArgumentFrame_ADD_VALUE_IF_NEEDED(alreadyAllocatedCPPObject,frame, unsigned int, val, ptr); return ptr; - } else { + } + else { return nullptr; } } + // Handle QStringView et al, which need a reference to a persistent QString +#if QT_VERSION < 0x060000 + if (info.typeId == stringRefTypeId) { + QString str = PyObjGetString(obj, strict, ok); + if (ok) { + void* ptr2 = nullptr; + PythonQtArgumentFrame_ADD_VARIANT_VALUE_IF_NEEDED(nullptr, frame, QVariant(str), ptr2); + PythonQtArgumentFrame_ADD_VARIANT_VALUE_IF_NEEDED(alreadyAllocatedCPPObject, frame, + QVariant::fromValue(QStringRef((const QString*)((QVariant*)ptr2)->constData())), ptr); + ptr = (void*)((QVariant*)ptr)->constData(); + return ptr; + } + else { + return nullptr; + } + } +#else + if (info.typeId == stringViewTypeId) { + // Handle QStringView, which needs a reference to a persistent QString + QString str = PyObjGetString(obj, strict, ok); + if (ok) { + void* ptr2 = nullptr; + PythonQtArgumentFrame_ADD_VARIANT_VALUE_IF_NEEDED(nullptr, frame, QVariant(str), ptr2); + PythonQtArgumentFrame_ADD_VARIANT_VALUE_IF_NEEDED(alreadyAllocatedCPPObject, frame, + QVariant::fromValue(QStringView(*((const QString*)((QVariant*)ptr2)->constData()))), ptr); + ptr = (void*)((QVariant*)ptr)->constData(); + return ptr; + } + else { + return nullptr; + } + } + else if (info.typeId == anyStringViewTypeId) { + // Handle QAnyStringView, which needs a reference to a persistent QString + QString str = PyObjGetString(obj, strict, ok); + if (ok) { + void* ptr2 = nullptr; + PythonQtArgumentFrame_ADD_VARIANT_VALUE_IF_NEEDED(nullptr, frame, QVariant(str), ptr2); + PythonQtArgumentFrame_ADD_VARIANT_VALUE_IF_NEEDED(alreadyAllocatedCPPObject, frame, + QVariant::fromValue(QAnyStringView(*((const QString*)((QVariant*)ptr2)->constData()))), ptr); + ptr = (void*)((QVariant*)ptr)->constData(); + return ptr; + } + else { + return nullptr; + } + } + else if (info.typeId == byteArrayViewTypeId) { + // Handle QByteArrayView, which needs a reference to a persistent QByteArray + QByteArray ba = PyObjGetBytesAllowString(obj, strict, ok); + if (ok) { + void* ptr2 = nullptr; + PythonQtArgumentFrame_ADD_VARIANT_VALUE_IF_NEEDED(nullptr, frame, QVariant(ba), ptr2); + PythonQtArgumentFrame_ADD_VARIANT_VALUE_IF_NEEDED(alreadyAllocatedCPPObject, frame, + QVariant::fromValue(QByteArrayView(*((const QByteArray*)((QVariant*)ptr2)->constData()))), ptr); + ptr = (void*)((QVariant*)ptr)->constData(); + return ptr; + } + else { + return nullptr; + } + } +#endif + if (info.typeId == PythonQtMethodInfo::Unknown || info.typeId >= QMetaType::User) { // check for QList case, where we will use a QList QVariant if (info.isQList && (info.innerNamePointerCount == 1)) { static int id = QMetaType::type("QList"); if (!alreadyAllocatedCPPObject) { - PythonQtArgumentFrame_ADD_VARIANT_VALUE_IF_NEEDED(alreadyAllocatedCPPObject, frame, QVariant::Type(id), ptr); + PythonQtArgumentFrame_ADD_VARIANT_VALUE_BY_ID_IF_NEEDED(alreadyAllocatedCPPObject, frame, id, ptr); ptr = (void*)((QVariant*)ptr)->constData(); } else { ptr = alreadyAllocatedCPPObject; @@ -672,7 +751,7 @@ void* PythonQtConv::ConvertPythonToQt(const PythonQtMethodInfo::ParameterInfo& i if (converter) { if (!alreadyAllocatedCPPObject) { // create a new empty variant of concrete type: - PythonQtArgumentFrame_ADD_VARIANT_VALUE_IF_NEEDED(alreadyAllocatedCPPObject,frame, QVariant::Type(info.typeId), ptr); + PythonQtArgumentFrame_ADD_VARIANT_VALUE_BY_ID_IF_NEEDED(alreadyAllocatedCPPObject,frame, info.typeId, ptr); ptr = (void*)((QVariant*)ptr)->constData(); } else { ptr = alreadyAllocatedCPPObject; @@ -783,6 +862,15 @@ QByteArray PythonQtConv::PyObjGetBytes(PyObject* val, bool /*strict*/, bool& ok) // TODO: support buffer objects in general QByteArray r; ok = true; + if (PyObject_TypeCheck(val, &PythonQtInstanceWrapper_Type)) { + // check if we already have a QByteArray wrapper here + PythonQtInstanceWrapper* wrapper = (PythonQtInstanceWrapper*)val; + bool baOk; + QByteArray* baPtr = (QByteArray*)castWrapperTo(wrapper, "QByteArray", baOk); + if (baOk && baPtr) { + return *baPtr; + } + } if (PyBytes_Check(val)) { r = QByteArray(PyBytes_AS_STRING(val), PyBytes_GET_SIZE(val)); } else { @@ -791,6 +879,18 @@ QByteArray PythonQtConv::PyObjGetBytes(PyObject* val, bool /*strict*/, bool& ok) return r; } +QByteArray PythonQtConv::PyObjGetBytesAllowString(PyObject* val, bool strict, bool& ok) +{ + QByteArray bytes = PyObjGetBytes(val, strict, ok); + if (!ok && !strict) { + // since Qt uses QByteArray in many places for identifier strings, + // we need to allow implicit conversion from unicode as well. + // We allow that for both Python 2.x and 3.x to be compatible. + bytes = PyObjGetString(val, true, ok).toUtf8(); + } + return bytes; +} + bool PythonQtConv::PyObjGetBool(PyObject* val, bool strict, bool &ok) { bool d = false; ok = false; @@ -1496,7 +1596,23 @@ PyObject* PythonQtConv::createCopyFromMetaType( int type, const void* data ) #if QT_VERSION < 0x060000 PyObject* PythonQtConv::convertFromStringRef(const void* inObject, int /*metaTypeId*/) { - return PythonQtConv::QStringToPyObject(((QStringRef*)inObject)->toString()); + return QStringToPyObject(((QStringRef*)inObject)->toString()); +} +#else +PyObject* PythonQtConv::convertFromStringView(const void* inObject, int /*metaTypeId*/) +{ + return QStringToPyObject(((QStringView*)inObject)->toString()); +} + +PyObject* PythonQtConv::convertFromAnyStringView(const void* inObject, int /*metaTypeId*/) +{ + return QStringToPyObject(((QAnyStringView*)inObject)->toString()); +} + +PyObject* PythonQtConv::convertFromByteArrayView(const void* inObject, int) +{ + QByteArray ba = ((QByteArrayView*)inObject)->toByteArray(); + return createCopyFromMetaType(QMetaType::QByteArray, &ba); } #endif @@ -1550,6 +1666,21 @@ bool PythonQtConv::isStringType(PyTypeObject* type) #endif } +void PythonQtConv::registerStringViewTypes() +{ +#if QT_VERSION < 0x060000 + stringRefTypeId = qRegisterMetaType("QStringRef"); + PythonQtConv::registerMetaTypeToPythonConverter(stringRefTypeId, PythonQtConv::convertFromStringRef); +#else + stringViewTypeId = qRegisterMetaType("QStringView"); + PythonQtConv::registerMetaTypeToPythonConverter(stringViewTypeId, PythonQtConv::convertFromStringView); + anyStringViewTypeId = qRegisterMetaType("QAnyStringView"); + PythonQtConv::registerMetaTypeToPythonConverter(anyStringViewTypeId, PythonQtConv::convertFromAnyStringView); + byteArrayViewTypeId = qRegisterMetaType("QByteArrayView"); + PythonQtConv::registerMetaTypeToPythonConverter(byteArrayViewTypeId, PythonQtConv::convertFromByteArrayView); +#endif +} + PyObject* PythonQtConv::convertFromQListOfPythonQtObjectPtr(const void* inObject, int /*metaTypeId*/) { QList& list = *((QList*)inObject); diff --git a/src/PythonQtConversion.h b/src/PythonQtConversion.h index b5205d0d..9de01564 100644 --- a/src/PythonQtConversion.h +++ b/src/PythonQtConversion.h @@ -133,6 +133,8 @@ class PYTHONQT_EXPORT PythonQtConv { //! get bytes from py object static QByteArray PyObjGetBytes(PyObject* val, bool strict, bool &ok); //! get int from py object + static QByteArray PyObjGetBytesAllowString(PyObject* val, bool strict, bool& ok); + //! get int from py object static int PyObjGetInt(PyObject* val, bool strict, bool &ok); //! get int64 from py object static qint64 PyObjGetLongLong(PyObject* val, bool strict, bool &ok); @@ -187,6 +189,10 @@ class PYTHONQT_EXPORT PythonQtConv { static PyObject* convertFromQListOfPythonQtObjectPtr(const void* /* QList* */ inObject, int /*metaTypeId*/); #if QT_VERSION < 0x060000 static PyObject* convertFromStringRef(const void* inObject, int /*metaTypeId*/); +#else + static PyObject* convertFromStringView(const void* inObject, int /*metaTypeId*/); + static PyObject* convertFromAnyStringView(const void* inObject, int /*metaTypeId*/); + static PyObject* convertFromByteArrayView(const void* inObject, int /*metaTypeId*/); #endif //! Returns the name of the equivalent CPP type (for signals and slots) @@ -195,6 +201,9 @@ class PYTHONQT_EXPORT PythonQtConv { //! Returns if the given object is a string (or unicode string) static bool isStringType(PyTypeObject* type); + //! Register QStringView like types, that need to be handled specially + static void registerStringViewTypes(); + protected: static QHash _metaTypeToPythonConverters; static QHash _pythonToMetaTypeConverters; @@ -215,6 +224,13 @@ class PYTHONQT_EXPORT PythonQtConv { template static PyObject* mapToPython (const Map& m); +#if QT_VERSION < 0x060000 + static int stringRefTypeId; +#else + static int stringViewTypeId; + static int anyStringViewTypeId; + static int byteArrayViewTypeId; +#endif }; template @@ -273,7 +289,7 @@ PyObject* PythonQtConvertListOfKnownClassToPythonList(const void* /*QList* */ ListType* list = (ListType*)inList; static PythonQtClassInfo* innerType = PythonQt::priv()->getClassInfo(PythonQtMethodInfo::getInnerListTypeName(QByteArray(QMetaType::typeName(metaTypeId)))); if (innerType == nullptr) { - std::cerr << "PythonQtConvertListOfKnownClassToPythonList: unknown inner type " << innerType->className().constData() << std::endl; + std::cerr << "PythonQtConvertListOfKnownClassToPythonList: unknown inner type for " << QMetaType::typeName(metaTypeId) << std::endl; } PyObject* result = PyTuple_New(list->size()); int i = 0; @@ -293,7 +309,7 @@ bool PythonQtConvertPythonListToListOfKnownClass(PyObject* obj, void* /*QList ListType* list = (ListType*)outList; static PythonQtClassInfo* innerType = PythonQt::priv()->getClassInfo(PythonQtMethodInfo::getInnerListTypeName(QByteArray(QMetaType::typeName(metaTypeId)))); if (innerType == nullptr) { - std::cerr << "PythonQtConvertListOfKnownClassToPythonList: unknown inner type " << innerType->className().constData() << std::endl; + std::cerr << "PythonQtConvertListOfKnownClassToPythonList: unknown inner type for " << QMetaType::typeName(metaTypeId) << std::endl; } bool result = false; if (PySequence_Check(obj)) { diff --git a/src/PythonQtDoc.h b/src/PythonQtDoc.h index 26df3b86..f8f40afe 100644 --- a/src/PythonQtDoc.h +++ b/src/PythonQtDoc.h @@ -112,12 +112,13 @@ Qt framework. - QtCore - QtGui - QtNetwork - - QtOpenGL + - QtOpenGL (before Qt6) - QtSql - QtSvg - - QtWebKit + - QtWebEngineWidgets + - QtWebKit (when available) - QtXml - - QtXmlPatterns + - QtXmlPatterns (before Qt6) - QtMultimedia - QtQml - QtQuick @@ -129,10 +130,11 @@ Qt framework. \section Supported Supported Versions PythonQt supports: - - Python 2 (>= Python 2.6) - - Python 3 (>= Python 3.3) - - Qt 4.x (Qt 4.7 and Qt 4.8 recommended) - - Qt 5.x (Tested with Qt 5.0, 5.3, 5.4, 5.6 and 5.11) + - Python 2 (>= Python 2.7) + - Python 3 (>= Python 3.6) + - Qt 4.x (Qt 4.7 and Qt 4.8 recommended) (not in the master branch, see below) + - Qt 5.x (Tested with Qt 5.0, 5.3, 5.4, 5.6, 5.11, 5.12 and 5.15) + - Qt 6.x (Tested with Qt 6.5 and 6.6) - support may not be complete, support for optional modules may be added as needed The last working Qt4 version is available at svn branches/Qt4LastWorkingVersion or you can download the PythonQt 3.0 release. The current git master branch no longer supports Qt4, since we started to make use of some Qt5-only features. @@ -196,13 +198,13 @@ Qt framework. char/uchar,int/uint,short,ushort,QCharinteger longinteger ulong,longlong,ulonglonglong - QStringunicode string - QByteArrayQByteArray wrapper \ref qbytearray-bytes "(1)" + QString \ref qstring "(1)"unicode string + QByteArray \ref qbytearray "(2)"QByteArray wrapper \ref qbytearray-bytes "(3)" char*str QStringListtuple of unicode strings QVariantListtuple of objects QVariantMapdict of objects - QVariantdepends on type \ref qvariant "(2)" + QVariantdepends on type \ref qvariant "(4)" QSize, QRect and all other standard Qt QVariantsvariant wrapper that supports complete API of the respective Qt classes OwnRegisteredMetaTypeC++ wrapper, optionally with additional information/wrapping provided by registerCPPClass() QListconverts to a list of CPP wrappers @@ -210,9 +212,11 @@ Qt framework. EnumTypeEnum wrapper derived from python integer QObject (and derived classes)QObject wrapper C++ objectCPP wrapper, either wrapped via PythonQtCppWrapperFactory or just decorated with decorators - PyObjectPyObject \ref pyobject "(3)" + PyObjectPyObject \ref pyobject "(5)" + -# \anchor qstring QStringRef (Qt5), QStringView and QAnyStringView (Qt6) are handled like QString. + -# \anchor qbytearray QByteArrayView (Qt6) is handled like QByteArray. -# \anchor qbytearray-bytes The Python 'bytes' type will automatically be converted to QByteArray where required. For converting a QByteArray to 'bytes' use the .data() method. -# \anchor qvariant QVariants are mapped recursively as given above, e.g. a dictionary can contain lists of dictionaries of doubles. @@ -265,6 +269,27 @@ Qt framework. \endcode + And this example shows how you can define your own signals and slots: + + \code + class MySender(QtCore.QObject): + + emitProgress = QtCore.Signal(float) # this is actually a double argument in C++ + + class MyReceiver(QtCore.QObject): + + @QtCore.Slot(float) + def progress(self, value): + print(f"progress: {value}") + + sender = MySender() + receiver = MyReceiver() + # connecting with the effective signature: + sender.connect("emitProgress(double)", receiver, "progress(double)") + sender.emitProgress(2.0) + + \endcode + \section CPP CPP Wrapping You can create dedicated wrapper QObjects for any C++ class. This is done by deriving from PythonQtCppWrapperFactory @@ -304,6 +329,8 @@ QtCore.QDate.currentDate() # enum value QtCore.QFont.UltraCondensed +# or, alternatively +QtCore.QFont.Stretch.UltraCondensed \endcode @@ -450,11 +477,11 @@ yourCpp = None \page Building Building - PythonQt requires at least Qt 4.6.1 (for earlier Qt versions, you will need to run the pythonqt_generator, Qt 4.3 is the absolute minimum) and Python 2.6.x/2.7.x or Python 3.3 (or higher). + PythonQt requires at least Qt 5.0 and Python 2.7.x or Python 3.6 (or higher). To compile PythonQt, you will need a python developer installation which includes Python's header files and the python2x.[lib | dll | so | dynlib]. The recommended way to build PythonQt is to use the QMake-based *.pro file. - The build scripts a currently set to use Python 2.6. + The build scripts are currently set to use Python 3.10 by default. You may need to tweak the \b build/python.prf file to set the correct Python includes and libs on your system. \subsection Windows @@ -465,21 +492,20 @@ the python2x.[lib | dll | so | dynlib]. Python yourself, using your compiler. To build PythonQt, you need to set the environment variable \b PYTHON_PATH to point to the root - dir of the python installation and \b PYTHON_LIB to point to - the directory where the python lib file is located. + dir of the python installation and \b PYTHON_VERSION should state the used Python version. When using the prebuild Python installer, this will be: \code - > set PYTHON_PATH = c:\Python26 - > set PYTHON_LIB = c:\Python26\libs + > set PYTHON_PATH = c:\Python310 + > set PYTHON_VERSION = 3.10 \endcode When using the python sources, this will be something like: \code - > set PYTHON_PATH = c:\yourDir\Python-2.6.1\ - > set PYTHON_LIB = c:\yourDir\Python-2.6.1\PCbuild8\Win32 + > set PYTHON_PATH = c:\yourDir\Python-3.10.12\ + > set PYTHON_VERSION = 3.10 \endcode To build all, do the following (after setting the above variables): @@ -519,7 +545,7 @@ the python2x.[lib | dll | so | dynlib]. You should add PythonQt/lib to your LD_LIBRARY_PATH so that the runtime linker can find the *.so files. - \subsection MacOsX + \subsection MacOS On Mac, Python is installed as a Framework, so you should not need to install it. To build PythonQt, just do a: diff --git a/src/PythonQtMisc.h b/src/PythonQtMisc.h index dc04a667..c13eddee 100644 --- a/src/PythonQtMisc.h +++ b/src/PythonQtMisc.h @@ -67,6 +67,18 @@ ptr = (void*)item; \ } +#if QT_VERSION >= 0x060000 + +#define PythonQtArgumentFrame_ADD_VARIANT_VALUE_BY_ID(store, id, ptr) \ + PythonQtArgumentFrame_ADD_VARIANT_VALUE(store, QVariant(QMetaType(id)), ptr) + +#else + +#define PythonQtArgumentFrame_ADD_VARIANT_VALUE_BY_ID(store, id, ptr) \ + PythonQtArgumentFrame_ADD_VARIANT_VALUE(store, QVariant::Type(id), ptr) + +#endif + #define PythonQtArgumentFrame_ADD_VARIANT_VALUE_IF_NEEDED(alreadyAllocatedPtr,store, value, ptr) \ { \ QVariant* item = (QVariant*)(alreadyAllocatedPtr?alreadyAllocatedPtr:store->nextVariantPtr()); \ @@ -74,6 +86,18 @@ ptr = (void*)item; \ } +#if QT_VERSION >= 0x060000 + +#define PythonQtArgumentFrame_ADD_VARIANT_VALUE_BY_ID_IF_NEEDED(alreadyAllocatedPtr,store, id, ptr) \ + PythonQtArgumentFrame_ADD_VARIANT_VALUE_IF_NEEDED(alreadyAllocatedPtr,store, QVariant(QMetaType(id)), ptr) + +#else + +#define PythonQtArgumentFrame_ADD_VARIANT_VALUE_BY_ID_IF_NEEDED(alreadyAllocatedPtr,store, id, ptr) \ + PythonQtArgumentFrame_ADD_VARIANT_VALUE_IF_NEEDED(alreadyAllocatedPtr,store, QVariant::Type(id), ptr) + +#endif + //! Stores C++ arguments for a qt_metacall (which are created when converting data from Python to C++) class PythonQtArgumentFrame { diff --git a/src/src.pro b/src/src.pro index a6fe37ca..f2bbc5ad 100644 --- a/src/src.pro +++ b/src/src.pro @@ -10,7 +10,7 @@ TEMPLATE = lib #DESTDIR = ../lib -CONFIG += qt +CONFIG += qt msvc_mp CONFIG -= flat # allow to choose static linking through the environment variable PYTHONQT_STATIC diff --git a/tests/PythonQtTests.cpp b/tests/PythonQtTests.cpp index 8316ebce..0d90b0f4 100644 --- a/tests/PythonQtTests.cpp +++ b/tests/PythonQtTests.cpp @@ -386,8 +386,8 @@ void PythonQtTestSignalHandler::testSignalHandler() QVERIFY(_helper->emitVariantSignal(12)); _helper->setExpectedVariant(QStringList() << "test1" << "test2"); QVERIFY(_helper->emitVariantSignal(QStringList() << "test1" << "test2")); - _helper->setExpectedVariant(qVariantFromValue((QObject*)_helper)); - QVERIFY(_helper->emitVariantSignal(qVariantFromValue((QObject*)_helper))); + _helper->setExpectedVariant(QVariant::fromValue((QObject*)_helper)); + QVERIFY(_helper->emitVariantSignal(QVariant::fromValue((QObject*)_helper))); PyRun_SimpleString("def testComplexSignal(a,b,l,o):\n if a==12 and b==13 and l==('test1','test2') and o == obj: obj.setPassed();\n"); // intentionally not normalized signal: @@ -497,7 +497,7 @@ void PythonQtTestApi::testCall() QVERIFY(_helper->call("testCall1", QVariantList() << QVariant("test"), QVariant(QString("test2")))); PyRun_SimpleString("def testCall2(a, b):\n if a=='test' and b==obj: obj.setPassed();\n return obj;\n"); - QVariant r = PythonQt::self()->call(PythonQt::self()->getMainModule(), "testCall2", QVariantList() << QVariant("test") << qVariantFromValue((QObject*)_helper)); + QVariant r = PythonQt::self()->call(PythonQt::self()->getMainModule(), "testCall2", QVariantList() << QVariant("test") << QVariant::fromValue((QObject*)_helper)); QObject* p = qvariant_cast(r); QVERIFY(p==_helper); } @@ -512,11 +512,17 @@ void PythonQtTestApi::testVariables() QVariant v2 = PythonQt::self()->getVariable(PythonQt::self()->getMainModule(), "someObject2"); QVERIFY(v2==QVariant()); - PythonQt::self()->addVariable(PythonQt::self()->getMainModule(), "someValue", QStringList() << "test1" << "test2"); + QVariant someValue = QStringList() << "test1" << "test2"; + PythonQt::self()->addVariable(PythonQt::self()->getMainModule(), "someValue", someValue); QVariant v3 = PythonQt::self()->getVariable(PythonQt::self()->getMainModule(), "someValue"); - QVERIFY(v3 == QVariant(QStringList() << "test1" << "test2")); +#if QT_VERSION >= 0x060000 - QStringList l = PythonQt::self()->introspection(PythonQt::self()->getMainModule(), QString::null, PythonQt::Variable); + // value first, so we replicate this first (otherwise the comparison QVariantList <-> QStringList fails) + QVERIFY(someValue.convert(v3.metaType())); +#endif + QVERIFY(v3 == someValue); + + QStringList l = PythonQt::self()->introspection(PythonQt::self()->getMainModule(), QString(), PythonQt::Variable); QSet s; // check that at least these three variables are set s << "obj" << "someObject" << "someValue"; diff --git a/with_pyenv b/with_pyenv deleted file mode 100755 index b17afafb..00000000 --- a/with_pyenv +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash -x -# Run `with_pyenv pyenv update` or remove ./.pyenv or your $PYENV_ROOT to update environment -# -if [[ "${BASH_SOURCE[0]}" = bash ]] ; then - SCRIPT_DIR=$(dirname "$0") - else - SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") -fi - -SCRIPT_DIR=$(realpath "$SCRIPT_DIR") - -echo $SCRIPT_DIR - -CCname=${CC:-${CXX:-default}} -export PYENV_VERSION=${PYENV_VERSION:-3.7.4} -export PYENV_ROOT="${PYENV_ROOT:-${SCRIPT_DIR}/.pyenv-${CCname}}" -if [ ! -x "${PYENV_ROOT}/bin/pyenv" ] ; then - rm -rf "${PYENV_ROOT}" - DOWNLOADER="wget -O-" - $DOWNLOADER --version || DOWNLOADER="curl -Lo-" - $DOWNLOADER https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash -#else -# ${PYENV_ROOT}/bin/pyenv update -fi -export PATH="${PYENV_ROOT}/bin:$PATH" -eval "$(pyenv init -)" -#eval "$(pyenv virtualenv-init -)" -if [ $(uname) = Darwin ] ; then - CFLAGS="${CFLAGS} -mmacosx-version-min=10.9" -fi - -INSTALL_COMMAND='eval env CFLAGS="-O2 -g -fPIC ${CFLAGS}" pyenv install -k -s "$PYENV_VERSION"' - -$INSTALL_COMMAND || pyenv update && $INSTALL_COMMAND - -PKG_CONFIG_PATH=$(python3-config --prefix)/lib/pkgconfig -export PKG_CONFIG_PATH - -if (( "$#" == 0 )) ; then - echo "Use ${BASH_SOURCE[0]} " - echo "Example: " - echo "${BASH_SOURCE[0]} \"echo '\$PKG_CONFIG_PATH' && python --version\"" -else - exec "$@" -fi