diff options
| -rw-r--r-- | find-modules/FindPythonModuleGeneration.cmake | 436 | ||||
| -rw-r--r-- | tests/CMakeLists.txt | 18 | ||||
| -rw-r--r-- | tests/GenerateSipBindings/CMakeLists.txt | 30 | ||||
| -rw-r--r-- | tests/GenerateSipBindings/cpplib.cpp | 143 | ||||
| -rw-r--r-- | tests/GenerateSipBindings/cpplib.h | 113 | ||||
| -rw-r--r-- | tests/GenerateSipBindings/rules_SipTest.py | 17 | ||||
| -rw-r--r-- | tests/GenerateSipBindings/testscript.py | 87 | 
7 files changed, 844 insertions, 0 deletions
| diff --git a/find-modules/FindPythonModuleGeneration.cmake b/find-modules/FindPythonModuleGeneration.cmake new file mode 100644 index 00000000..ef47fed2 --- /dev/null +++ b/find-modules/FindPythonModuleGeneration.cmake @@ -0,0 +1,436 @@ +#.rst: +# FindPythonModuleGeneration +# -------------------------- +# +# This module is experimental and internal.  The interface will likely +# change in the coming releases. +# +# Tools and macros for generating python bindings +# +# This will define the following public function: +# +#   ecm_generate_python_binding(TARGET <target> +#                               PYTHONNAMESPACE <namespace> +#                               MODULENAME <modulename> +#                               HEADERS <headers>) +# +# Invoking the function will create bindings for the <target> for python 2 and 3, +# if available.  The bindings will be put in the namespace <namespace> in python, +# and will be available from the module <modulename>. +# +# The optional rules file specifies the rules for creating the bindings +# +# A simple invokation would be: +# +#   ecm_generate_python_binding(KMyTarget +#     PYTHONNAMESPACE PyKF5 +#     MODULENAME MyTarget +#     SIP_DEPENDS QtCore/QtCoremod.sip +#     HEADERS ${myTargetHeaders} +#   ) +# +# which can then be used from python as +# +#   import PyKF5.MyTarget +# + +#============================================================================= +# Copyright 2016 Stephen Kelly <steveire@gmail.com> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +#    notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +#    notice, this list of conditions and the following disclaimer in the +#    documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +#    derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + + +macro(_find_python version minor_version) +  set(_CURRENT_VERSION ${version}.${minor_version}) +  find_library(GPB_PYTHON${version}_LIBRARY +    NAMES +      python${_CURRENT_VERSION}mu +      python${_CURRENT_VERSION}m +      python${_CURRENT_VERSION}u +      python${_CURRENT_VERSION} +    PATHS +      [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs +      [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs +    # Avoid finding the .dll in the PATH.  We want the .lib. +    NO_SYSTEM_ENVIRONMENT_PATH +  ) + +  if(GPB_PYTHON${version}_LIBRARY) +    # Use the library's install prefix as a hint +    set(_Python_INCLUDE_PATH_HINT) +    get_filename_component(_Python_PREFIX ${GPB_PYTHON${version}_LIBRARY} PATH) +    get_filename_component(_Python_PREFIX ${_Python_PREFIX} PATH) +    if(_Python_PREFIX) +      set(_Python_INCLUDE_PATH_HINT ${_Python_PREFIX}/include) +    endif() +    unset(_Python_PREFIX) + +    find_path(GPB_PYTHON${version}_INCLUDE_DIR +      NAMES Python.h +      HINTS +        ${_Python_INCLUDE_PATH_HINT} +      PATHS +        [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/include +        [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/include +      PATH_SUFFIXES +        python${_CURRENT_VERSION}mu +        python${_CURRENT_VERSION}m +        python${_CURRENT_VERSION}u +        python${_CURRENT_VERSION} +    ) +  endif() + +  find_program(GPB_PYTHON${version}_COMMAND python${version}) +endmacro() + +macro(_create_imported_python_target version) +  if(GPB_PYTHON${version}_LIBRARY AND GPB_PYTHON${version}_INCLUDE_DIR AND EXISTS "${GPB_PYTHON${version}_INCLUDE_DIR}/patchlevel.h") +    list(APPEND _pyversions ${version}) + +    file(STRINGS "${GPB_PYTHON${version}_INCLUDE_DIR}/patchlevel.h" python_version_define +         REGEX "^#define[ \t]+PY_MINOR_VERSION[ \t]+[0-9]+") +    string(REGEX REPLACE "^#define[ \t]+PY_MINOR_VERSION[ \t]+([0-9]+)" "\\1" +                         min_ver "${python_version_define}") +    unset(python_version_define) + +    set(pyversion${version}_maj_min ${version}.${min_ver}) + +    add_library(Python::Libs${version} UNKNOWN IMPORTED) +    set_property(TARGET Python::Libs${version} PROPERTY IMPORTED_LOCATION ${GPB_PYTHON${version}_LIBRARY}) +    set_property(TARGET Python::Libs${version} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${GPB_PYTHON${version}_INCLUDE_DIR}) +  endif() +endmacro() + +macro(_report_NOT_FOUND message) +  if(NOT ${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) +    set(_GPB_MESSAGE_TYPE STATUS) +    if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED) +      set(_GPB_MESSAGE_TYPE FATAL_ERROR) +    endif() +    message(${_GPB_MESSAGE_TYPE} ${message}) +  endif() +  set(PythonModuleGeneration_FOUND FALSE) +  return() +endmacro() + +if (NOT Qt5Core_FOUND) +  _report_NOT_FOUND("Qt 5 must be found before finding ${CMAKE_FIND_PACKAGE_NAME}.") +endif() + +if (NOT GPB_PYTHON3_LIBRARY) +  set(_PYTHON3_MIN_VERSION 4) +  set(_PYTHON3_MAX_VERSION 10) + +  _find_python(3 ${_PYTHON3_MAX_VERSION}) # Canary check + +  if (GPB_PYTHON3_LIBRARY) +    message(FATAL_ERROR "The max python version in ${CMAKE_FIND_PACKAGE_NAME} must be updated.") +  endif() + +  set(_PYTHON3_FIND_VERSION ${_PYTHON3_MAX_VERSION}) + +  while(NOT GPB_PYTHON3_LIBRARY +      AND NOT GPB_PYTHON3_INCLUDE_DIR +      AND NOT EXISTS "${GPB_PYTHON3_INCLUDE_DIR}/patchlevel.h" +      AND NOT _PYTHON3_FIND_VERSION EQUAL ${_PYTHON3_MIN_VERSION}) +    math(EXPR _PYTHON3_FIND_VERSION "${_PYTHON3_FIND_VERSION} - 1") +    _find_python(3 ${_PYTHON3_FIND_VERSION}) +  endwhile() +endif() +_create_imported_python_target(3) + +_find_python(2 7) +_create_imported_python_target(2) + +if (NOT _pyversions) +  _report_NOT_FOUND("At least one python version must be available to use ${CMAKE_FIND_PACKAGE_NAME}.") +endif() + +find_program(GBP_SIP_COMMAND sip) + +if (NOT GBP_SIP_COMMAND) +  _report_NOT_FOUND("The sip executable must be available to use ${CMAKE_FIND_PACKAGE_NAME}.") +endif() + +if (NOT GPB_PYTHON2_COMMAND) +  _report_NOT_FOUND("The python2 executable is required by clang-python for the ${CMAKE_FIND_PACKAGE_NAME} Module.") +endif() + +if (NOT libclang_LIBRARY) +  set(_LIBCLANG3_MAX_VERSION 16) +  set(_LIBCLANG3_MIN_VERSION 8) + +  find_library(libclang_LIBRARY clang-3.${_LIBCLANG3_MAX_VERSION}) + +  if (libclang_LIBRARY) +    message(FATAL_ERROR "The max python version in ${CMAKE_FIND_PACKAGE_NAME} must be updated.") +  endif() + +  set(_LIBCLANG3_FIND_VERSION ${_LIBCLANG3_MAX_VERSION}) +  while(NOT libclang_LIBRARY AND NOT _LIBCLANG3_FIND_VERSION EQUAL _LIBCLANG3_MIN_VERSION) +    math(EXPR _LIBCLANG3_FIND_VERSION "${_LIBCLANG3_FIND_VERSION} - 1") +    set(_GPB_CLANG_SUFFIX 3.${_LIBCLANG3_FIND_VERSION}) +    find_library(libclang_LIBRARY clang-3.${_LIBCLANG3_FIND_VERSION}) +  endwhile() +endif() + +if (NOT libclang_LIBRARY) +  _report_NOT_FOUND("Could not find libclang version 3.8 or greater.") +endif() + +execute_process( +  COMMAND ${GPB_PYTHON2_COMMAND} ${CMAKE_CURRENT_LIST_DIR}/sip_generator.py --self-check ${libclang_LIBRARY} +  RESULT_VARIABLE selfCheckErrors +) + +if (selfCheckErrors) +  _report_NOT_FOUND("sip_generator failed a self-check for the ${CMAKE_FIND_PACKAGE_NAME} Module.") +endif() + +get_filename_component(libclang_file "${libclang_file}" REALPATH) + +find_file(SIP_Qt5Core_Mod_FILE +  NAMES QtCoremod.sip +  PATH_SUFFIXES share/sip/PyQt5/QtCore +) + +if(NOT SIP_Qt5Core_Mod_FILE) +  _report_NOT_FOUND("PyQt module files not found for the ${CMAKE_FIND_PACKAGE_NAME} Module.") +endif() + +file(STRINGS "${SIP_Qt5Core_Mod_FILE}" _SIP_Qt5_VERSIONS +  REGEX "^%Timeline" +) + +string(REGEX MATCHALL "Qt_5_[^ }]+" _SIP_Qt5_VERSIONS "${_SIP_Qt5_VERSIONS}") + +set(GPB_Qt5_Tag Qt_5_${Qt5Core_VERSION_MINOR}_${Qt5Core_VERSION_PATCH}) + +list(FIND _SIP_Qt5_VERSIONS ${GPB_Qt5_Tag} _SIP_Qt5_Version_Index) + +if(_SIP_Qt5_Version_Index EQUAL -1) +  _report_NOT_FOUND("PyQt module does not support Qt version 5.${Qt5Core_VERSION_MINOR}.${Qt5Core_VERSION_PATCH} for the ${CMAKE_FIND_PACKAGE_NAME} Module. Found available Qt5 tags: \"${_SIP_Qt5_VERSIONS}\".") +endif() + +set(PythonModuleGeneration_FOUND TRUE) + +include(CMakeParseArguments) + +set(GPB_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR}) + +function(_compute_implicit_include_dirs) +  set(CLANG_CXX_DRIVER clang++${_GPB_CLANG_SUFFIX}) +  if (NOT EXISTS ${CLANG_CXX_DRIVER}) +    set(CLANG_CXX_DRIVER clang++) +  endif() +  execute_process(COMMAND ${CLANG_CXX_DRIVER} -v -E -x c++ - +                  ERROR_VARIABLE _compilerOutput +                  OUTPUT_VARIABLE _compilerStdout +                  INPUT_FILE /dev/null) + +  if( "${_compilerOutput}" MATCHES "> search starts here[^\n]+\n *(.+ *\n) *End of (search) list" ) + +    # split the output into lines and then remove leading and trailing spaces from each of them: +    string(REGEX MATCHALL "[^\n]+\n" _includeLines "${CMAKE_MATCH_1}") +    foreach(nextLine ${_includeLines}) +      # on OSX, gcc says things like this:  "/System/Library/Frameworks (framework directory)", strip the last part +      string(REGEX REPLACE "\\(framework directory\\)" "" nextLineNoFramework "${nextLine}") +      # strip spaces at the beginning and the end +      string(STRIP "${nextLineNoFramework}" _includePath) +      list(APPEND _resultIncludeDirs "${_includePath}") +    endforeach() +  endif() + +  set(_GPB_IMPLICIT_INCLUDE_DIRS ${_resultIncludeDirs} PARENT_SCOPE) +endfunction() + +function(ecm_generate_python_binding +    target_keyword target_value +    pythonnamespace_keyword pythonnamespace_value +    modulename_keyword modulename_value +    ) + +    cmake_parse_arguments(GPB "" "RULES_FILE" "SIP_DEPENDS;SIP_INCLUDES;HEADERS"  ${ARGN}) + +    file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/sip/${pythonnamespace_value}/${modulename_value}/${modulename_value}mod.sip" +          " +%Module ${pythonnamespace_value}.${modulename_value} + +%ModuleHeaderCode +#pragma GCC visibility push(default) +%End\n\n") + +    set(generator_depends "${GPB_MODULE_DIR}/sip_generator.py" +        "${GPB_MODULE_DIR}/rules_engine.py" +        "${GPB_MODULE_DIR}/FindPythonModuleGeneration.cmake" +        ) + +    if (NOT _GPB_IMPLICIT_INCLUDE_DIRS) +      _compute_implicit_include_dirs() +    endif() + +    foreach(dep ${GPB_SIP_DEPENDS}) +        if (IS_ABSOLUTE ${dep}) +          list(APPEND generator_depends "${dep}") +        endif() +        file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/sip/${pythonnamespace_value}/${modulename_value}/${modulename_value}mod.sip" +          "%Import ${dep}\n\n") +    endforeach() + +    set(sip_files) +    set(commands) + +    if (NOT GPB_RULES_FILE) +      set(GPB_RULES_FILE "${GPB_MODULE_DIR}/Qt5Ruleset.py") +    endif() + + +    list(APPEND generator_depends ${GPB_RULES_FILE}) + +    foreach(hdr ${GPB_HEADERS}) + +        get_filename_component(hdr_file ${hdr} ABSOLUTE) + +        get_filename_component(hdr ${hdr} NAME_WE) +        string(TOLOWER ${hdr}.h hdr_filename) + +        if (NOT EXISTS "${hdr_file}") +          message(FATAL_ERROR "File not found: ${hdr_file}") +        endif() + +        set(sip_file "${CMAKE_CURRENT_BINARY_DIR}/sip/${pythonnamespace_value}/${modulename_value}/${hdr}.sip") +        list(APPEND sip_files ${sip_file}) + +        set(inc_dirs "-I$<JOIN:$<TARGET_PROPERTY:${target_value},INTERFACE_INCLUDE_DIRECTORIES>,;-I>") +        set(sys_inc_dirs) +        foreach(d ${_GPB_IMPLICIT_INCLUDE_DIRS}) +           list(APPEND sys_inc_dirs "-isystem" "${d}") +        endforeach() +        set(comp_defs "-D$<JOIN:$<TARGET_PROPERTY:${target_value},INTERFACE_COMPILE_DEFINITIONS>,;-D>") + +        foreach(stdVar 11 14) +          set(stdFlag "$<$<STREQUAL:$<TARGET_PROPERTY:${target_value},CXX_STANDARD>,${stdVar}>:${CMAKE_CXX${stdVar}_EXTENSION_COMPILE_OPTION}>") +        endforeach() + +        set(comp_flags "$<JOIN:$<TARGET_PROPERTY:${target_value},INTERFACE_COMPILE_OPTIONS>;${stdFlag},;>") + +        add_custom_command(OUTPUT ${sip_file} +            COMMAND ${GPB_PYTHON2_COMMAND} ${GPB_MODULE_DIR}/sip_generator.py +              --flags " ${inc_dirs};${sys_inc_dirs};${comp_defs};${comp_flags}" +              --include_filename "${hdr_filename}" +              ${libclang_LIBRARY} +              ${GPB_RULES_FILE} +              "${hdr_file}" +              "${sip_file}" +            DEPENDS ${hdr_file} ${generator_depends} +            VERBATIM +        ) + +        file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/sip/${pythonnamespace_value}/${modulename_value}/${modulename_value}mod.sip" +          "%Include ${hdr}.sip\n") +    endforeach() + +    file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${pythonnamespace_value}/${modulename_value}/module.sbf" +        " +target = ${modulename_value} +sources = sip${modulename_value}cmodule.cpp +headers = sipAPI${modulename_value} +" +    ) + +    get_filename_component(SIP_PYQT5_DIR ${SIP_Qt5Core_Mod_FILE} PATH) +    get_filename_component(SIP_PYQT5_DIR ${SIP_PYQT5_DIR} PATH) + +    set(sip_includes -I "${SIP_PYQT5_DIR}") +    if (GPB_SIP_INCLUDES) +      list(APPEND sip_includes -I "$<JOIN:${GPB_SIP_INCLUDES},-I>") +    endif() +    foreach(path ${CMAKE_PREFIX_PATH} ${CMAKE_INSTALL_PREFIX}) +      if (EXISTS ${path}/share/sip) +        list(APPEND sip_includes -I "${path}/share/sip") +      endif() +    endforeach() + +    if (WIN32) +      set(GPB_WS_Tag -t WS_WIN) +    elif(APPLE) +      set(GPB_WS_Tag -t WS_MACX) +    else() +      set(GPB_WS_Tag -t WS_X11) +    endif() + +    add_custom_target(generate_${modulename_value}_sip_files ALL DEPENDS ${sip_files}) + +    add_custom_command(OUTPUT +      "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${pythonnamespace_value}/${modulename_value}/unified${modulename_value}.cpp" +      COMMAND ${GPB_PYTHON2_COMMAND} "${GPB_MODULE_DIR}/run-sip.py" --sip ${GBP_SIP_COMMAND} +       --unify "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${pythonnamespace_value}/${modulename_value}/unified${modulename_value}.cpp" +       --module-name "${modulename_value}" +       -c "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${pythonnamespace_value}/${modulename_value}" +       -b "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${pythonnamespace_value}/${modulename_value}/module.sbf" +       -t ${GPB_Qt5_Tag} ${GPB_WS_Tag} + +       -x VendorID -x Py_v3 + +       -I "${CMAKE_CURRENT_BINARY_DIR}/sip/${pythonnamespace_value}/${modulename_value}" +       ${sip_includes} +       "${CMAKE_CURRENT_BINARY_DIR}/sip/${pythonnamespace_value}/${modulename_value}/${modulename_value}mod.sip" +       DEPENDS generate_${modulename_value}_sip_files "${GPB_MODULE_DIR}/run-sip.py" ${generator_depends} +    ) + +    add_custom_target(sip_generated_${modulename_value}_files ALL +          DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${pythonnamespace_value}/${modulename_value}/unified${modulename_value}.cpp") + +    file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/sip/${pythonnamespace_value}/${modulename_value}" +         "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${pythonnamespace_value}/${modulename_value}") + +    foreach(pyversion ${_pyversions}) +        file(MAKE_DIRECTORY +            "${CMAKE_CURRENT_BINARY_DIR}/py${pyversion}/${pythonnamespace_value}") +        execute_process(COMMAND "${CMAKE_COMMAND}" -E touch "${CMAKE_CURRENT_BINARY_DIR}/py${pyversion}/${pythonnamespace_value}/__init__.py") + +        add_library(Py${pyversion}KF5${modulename_value} MODULE +          "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${pythonnamespace_value}/${modulename_value}/unified${modulename_value}.cpp" +        ) +        add_dependencies(Py${pyversion}KF5${modulename_value} sip_generated_${modulename_value}_files) +        target_link_libraries(Py${pyversion}KF5${modulename_value} PRIVATE ${target_value} Python::Libs${pyversion}) + +        target_compile_options(Py${pyversion}KF5${modulename_value} PRIVATE -fstack-protector-strong -Wno-deprecated-declarations -Wno-overloaded-virtual) +        target_include_directories(Py${pyversion}KF5${modulename_value} PRIVATE ${GPB_SIP_INCLUDES}) +        target_link_libraries(Py${pyversion}KF5${modulename_value} PRIVATE -Wl,-Bsymbolic-functions -Wl,-z,relro) + +        set_property(TARGET Py${pyversion}KF5${modulename_value} PROPERTY AUTOMOC OFF) +        set_property(TARGET Py${pyversion}KF5${modulename_value} PROPERTY PREFIX "") +        set_property(TARGET Py${pyversion}KF5${modulename_value} PROPERTY OUTPUT_NAME py${pyversion}/${pythonnamespace_value}/${modulename_value}) + +        add_test(NAME Py${pyversion}Test COMMAND ${GPB_PYTHON${pyversion}_COMMAND} "${CMAKE_SOURCE_DIR}/autotests/pythontest.py" ${CMAKE_CURRENT_BINARY_DIR}/py${pyversion}) + +        install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/py${pyversion}/${pythonnamespace_value} +            DESTINATION lib/python${pyversion${pyversion}_maj_min}/dist-packages) +        install(FILES ${sip_files} "${CMAKE_CURRENT_BINARY_DIR}/sip/${pythonnamespace_value}/${modulename_value}/${modulename_value}mod.sip" +          DESTINATION share/sip/${pythonnamespace_value}/${modulename_value} +        ) +    endforeach() +endfunction() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4880f5a5..d83db8a4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -79,6 +79,24 @@ macro(add_test_macro NAME)      add_test_variant("${NAME}" "${NAME}" ${ARGN})  endmacro() +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/find-modules) + +find_package(PythonModuleGeneration REQUIRED) + +foreach(pyversion 2 3) +  if (GPB_PYTHON${pyversion}_COMMAND) +    if (pythonCommands) +      list(APPEND pythonCommands " && ") +    endif() +    set(pythonCommands +      ${GPB_PYTHON${pyversion}_COMMAND} +      "${CMAKE_CURRENT_SOURCE_DIR}/GenerateSipBindings/testscript.py" +      "${CMAKE_CURRENT_BINARY_DIR}/GenerateSipBindings/py${pyversion}" +    ) +  endif() +endforeach() +add_test_macro(GenerateSipBindings ${pythonCommands}) +  add_test_macro(ExecuteCoreModules dummy)  add_test_macro(ExecuteKDEModules dummy) diff --git a/tests/GenerateSipBindings/CMakeLists.txt b/tests/GenerateSipBindings/CMakeLists.txt new file mode 100644 index 00000000..c48b1bce --- /dev/null +++ b/tests/GenerateSipBindings/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.3) + +project(GenerateSipBindings) + +find_package(Qt5Core REQUIRED) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON) + +set(CMAKE_CXX_STANDARD 14) + +add_library(CppLib SHARED cpplib.cpp) +target_link_libraries(CppLib PUBLIC Qt5::Core) +target_compile_features(CppLib PUBLIC cxx_nullptr) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../find-modules) + +find_package(PythonModuleGeneration REQUIRED) + +ecm_generate_python_binding( +  TARGET CppLib +  PYTHONNAMESPACE PyTest +  MODULENAME CppLib +  RULES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/rules_SipTest.py" +  SIP_DEPENDS +    QtCore/QtCoremod.sip +  HEADERS +    cpplib.h +) diff --git a/tests/GenerateSipBindings/cpplib.cpp b/tests/GenerateSipBindings/cpplib.cpp new file mode 100644 index 00000000..c5b7a5fb --- /dev/null +++ b/tests/GenerateSipBindings/cpplib.cpp @@ -0,0 +1,143 @@ + +#include "cpplib.h" + +MyObject::MyObject(QObject* parent) +  : QObject(parent) +{ + +} + +double MyObject::unnamedParameters(int i, double d) +{ +  return i * d; +} + +int MyObject::addThree(int input) const +{ +  return input + 3; +} + +QList<int> MyObject::addThree(QList<int> input) const +{ +  auto output = input; +  std::transform(output.begin(), output.end(), +      output.begin(), +      [](int in) { return in + 3; }); +  return output; +} + +const QString MyObject::addThree(const QString& input, const QString& prefix) const +{ +  return prefix + input + QStringLiteral("Three"); +} + +int MyObject::findNeedle(QStringList list, QString needle, Qt::MatchFlags flags) const +{ +  if (flags & Qt::MatchStartsWith) { +    auto it = std::find_if(list.begin(), list.end(), [needle](QString cand) { +      return cand.startsWith(needle); +    }); +    if (it != list.end()) { +      return std::distance(list.begin(), it); +    } +    return -1; +  } +  return list.indexOf(needle); +} + +int MyObject::qtEnumTest(QFlags<Qt::MatchFlag> flags) +{ +  return flags; +} + +int MyObject::localEnumTest(QFlags<LocalEnum> flags) +{ +  return flags; +} + +int MyObject::functionParam(std::function<int()> fn) +{ +  return fn(); +} + +int MyObject::groups(unsigned int maxCount) const +{ +  return maxCount; +} + +class FwdDecl +{ + +}; + +int MyObject::fwdDecl(const FwdDecl&) +{ +  return 42; +} + +int MyObject::const_parameters(const int input, QObject* const obj) const +{ +  if (obj) return input / 3; +  return input / 2; +} + +NonCopyable::NonCopyable() +  : mNum(new int(42)) +{ + +} + +NonCopyable::~NonCopyable() +{ +  delete mNum; +} + +NonCopyableByMacro::NonCopyableByMacro() +{ + +} + +namespace SomeNS { + +NonCopyableInNS::NonCopyableInNS() +  : mNum(new int(42)) +{ + +} + +NonCopyableInNS::~NonCopyableInNS() +{ +  delete mNum; +} + +} + +void MyObject::publicSlot1() +{ +  Q_EMIT publicSlotCalled(); +} + +void MyObject::publicSlot2() +{ +  Q_EMIT publicSlotCalled(); +} + +void MyObject::protectedSlot1() +{ +  Q_EMIT protectedSlotCalled(); +} + +void MyObject::protectedSlot2() +{ +  Q_EMIT protectedSlotCalled(); +} + +void MyObject::privateSlot1() +{ +  Q_EMIT privateSlotCalled(); +} + +void MyObject::privateSlot2() +{ +  Q_EMIT privateSlotCalled(); +} diff --git a/tests/GenerateSipBindings/cpplib.h b/tests/GenerateSipBindings/cpplib.h new file mode 100644 index 00000000..f8db75b5 --- /dev/null +++ b/tests/GenerateSipBindings/cpplib.h @@ -0,0 +1,113 @@ + +#pragma once + +#include <QtCore/QObject> +#include <QtCore/QMap> + +#include <functional> + +class FwdDecl; + +class MyObject : public QObject +{ +  Q_OBJECT +public: +  MyObject(QObject* parent = nullptr); + +  enum LocalEnum { +    Val1 = 1, +    Val2 +  }; +  Q_DECLARE_FLAGS(LocalEnums, LocalEnum) + +  enum { +     AnonVal1, +     AnonVal2 +  }; + +  double unnamedParameters(int, double); + +  int addThree(int input) const; +  QList<int> addThree(QList<int> input) const; + +  const QString addThree(const QString& input, const QString& prefix = QStringLiteral("Default")) const; + +  int findNeedle(QStringList list, QString needle, Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const; + +  int qtEnumTest(QFlags<Qt::MatchFlag> flags); +  int localEnumTest(QFlags<MyObject::LocalEnum> flags); + +  int functionParam(std::function<int()> fn); +  int groups(unsigned int maxCount = std::numeric_limits<uint>::max()) const; + +  int const_parameters(const int input, QObject* const obj = 0) const; + +  int fwdDecl(const FwdDecl& f); +  int fwdDeclRef(FwdDecl& f); + +signals: +  void publicSlotCalled(); + +Q_SIGNALS: +  void privateSlotCalled(); +  void protectedSlotCalled(); + +public slots: +  void publicSlot1(); + +public Q_SLOTS: +  void publicSlot2(); + +protected slots: +  void protectedSlot1(); + +protected Q_SLOTS: +  void protectedSlot2(); + +private slots: +  void privateSlot1(); + +private Q_SLOTS: +  void privateSlot2(); +}; + +class NonCopyable +{ +public: +  NonCopyable(); +  ~NonCopyable(); + +private: +  int* const mNum; +}; + + +class NonCopyableByMacro +{ +public: +  NonCopyableByMacro(); + +private: +  Q_DISABLE_COPY(NonCopyableByMacro) +}; + +class HasPrivateDefaultCtor +{ +public: +private: +  HasPrivateDefaultCtor(int param = 0); +}; + +namespace SomeNS { + +class NonCopyableInNS +{ +public: +  NonCopyableInNS(); +  ~NonCopyableInNS(); + +private: +  int* const mNum; +}; + +} diff --git a/tests/GenerateSipBindings/rules_SipTest.py b/tests/GenerateSipBindings/rules_SipTest.py new file mode 100644 index 00000000..e0cd2b9e --- /dev/null +++ b/tests/GenerateSipBindings/rules_SipTest.py @@ -0,0 +1,17 @@ + +import os, sys + +import rules_engine +sys.path.append(os.path.dirname(os.path.dirname(rules_engine.__file__))) +import Qt5Ruleset + +def local_function_rules(): +    return [ +        ["MyObject", "fwdDecl", ".*", ".*", ".*", rules_engine.function_discard], +        ["MyObject", "fwdDeclRef", ".*", ".*", ".*", rules_engine.function_discard], +    ] + +class RuleSet(Qt5Ruleset.RuleSet): +    def __init__(self): +        Qt5Ruleset.RuleSet.__init__(self) +        self._fn_db = rules_engine.FunctionRuleDb(lambda: local_function_rules() + Qt5Ruleset.function_rules()) diff --git a/tests/GenerateSipBindings/testscript.py b/tests/GenerateSipBindings/testscript.py new file mode 100644 index 00000000..6f2132a2 --- /dev/null +++ b/tests/GenerateSipBindings/testscript.py @@ -0,0 +1,87 @@ + +import sys + +from PyQt5 import QtCore + +sys.path.append(sys.argv[1]) + +import PyTest.CppLib + +mo = PyTest.CppLib.MyObject() + +assert(mo.addThree(39) == 42) + +assert(mo.addThree([38, 39, 40]) == [41, 42, 43]) + +assert(mo.addThree("SomeString") == "DefaultSomeStringThree") + +assert(mo.findNeedle(["One", "Two", "Three"], "Two") == 1) +assert(mo.findNeedle(["One", "Two", "Three"], "Four") == -1) +assert(mo.findNeedle(["One", "Two", "Three"], "Th") == 2) +assert(mo.findNeedle(["One", "Two", "Three"], "Th", QtCore.Qt.MatchExactly) == -1) + +assert(mo.const_parameters(30) == 15) +assert(mo.const_parameters(30, mo) == 10) + +assert(mo.qtEnumTest(QtCore.Qt.MatchContains | QtCore.Qt.MatchStartsWith) == 3) +assert(mo.localEnumTest(PyTest.CppLib.MyObject.Val2) == 2) + +class Reactor(QtCore.QObject): +    def __init__(self, obj): +        QtCore.QObject.__init__(self) +        self.gotPrivateSlotCalledSignal = False +        self.gotProtectedSlotCalledSignal = False +        self.gotPublicSlotCalledSignal = False + +        obj.privateSlotCalled.connect(self.react_to_privateSlotCalled) +        obj.protectedSlotCalled.connect(self.react_to_protectedSlotCalled) +        obj.publicSlotCalled.connect(self.react_to_publicSlotCalled) + +    def react_to_privateSlotCalled(self): +        self.gotPrivateSlotCalledSignal = True + +    def react_to_protectedSlotCalled(self): +        self.gotProtectedSlotCalledSignal = True + +    def react_to_publicSlotCalled(self): +        self.gotPublicSlotCalledSignal = True + +class Emitter(QtCore.QObject): +    privateTrigger = QtCore.pyqtSignal() +    protectedTrigger = QtCore.pyqtSignal() +    publicTrigger = QtCore.pyqtSignal() + +    def __init__(self, obj): +        QtCore.QObject.__init__(self) +        self.privateTrigger.connect(obj.privateSlot1) +        self.protectedTrigger.connect(obj.protectedSlot1) +        self.publicTrigger.connect(obj.publicSlot1) + +    def emitSignalForPublic(self): +        self.publicTrigger.emit() + +    def emitSignalForPrivate(self): +        self.privateTrigger.emit() + +    def emitSignalForProtected(self): +        self.protectedTrigger.emit() + +e = Emitter(mo) + +r = Reactor(mo) + +assert(not r.gotPrivateSlotCalledSignal) +assert(not r.gotProtectedSlotCalledSignal) +assert(not r.gotPublicSlotCalledSignal) + +e.emitSignalForPrivate() + +assert(r.gotPrivateSlotCalledSignal) + +e.emitSignalForProtected() + +assert(r.gotProtectedSlotCalledSignal) + +e.emitSignalForPublic() + +assert(r.gotPublicSlotCalledSignal) | 
