From 2004278e04081b445686712a42698480bfce33e1 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Thu, 1 Feb 2018 11:33:52 +0100 Subject: Add CMake-based build rules for scriptableapplication example Mostly a clean-ish port of the qmake project file, which also uses the pyside_config.py for getting proper include and link paths. Task-number: PYSIDE-597 Change-Id: I87f71b93ecdcc27d49250ccc0710e64300532dab Reviewed-by: Friedemann Kleint --- examples/scriptableapplication/CMakeLists.txt | 187 +++++++++++++++++++++++ examples/scriptableapplication/README.CMake.txt | 28 ++++ examples/scriptableapplication/pyside2_config.py | 100 +++++++++--- 3 files changed, 294 insertions(+), 21 deletions(-) create mode 100644 examples/scriptableapplication/CMakeLists.txt create mode 100644 examples/scriptableapplication/README.CMake.txt (limited to 'examples') diff --git a/examples/scriptableapplication/CMakeLists.txt b/examples/scriptableapplication/CMakeLists.txt new file mode 100644 index 000000000..40e29346a --- /dev/null +++ b/examples/scriptableapplication/CMakeLists.txt @@ -0,0 +1,187 @@ +cmake_minimum_required(VERSION 3.1) +cmake_policy(VERSION 3.1) + +# Enable policy to run automoc on generated files. +if(POLICY CMP0071) + cmake_policy(SET CMP0071 NEW) +endif() + +project(scriptableapplication) + +# Set CPP standard to C++11 minimum. +set(CMAKE_CXX_STANDARD 11) + +# Find required Qt packages. +find_package(Qt5 5.9 REQUIRED COMPONENTS Core Gui Widgets) + +# Macro to get various pyside / python include / link flags. +macro(pyside2_config option output_var) + if(${ARGC} GREATER 2) + set(is_list ${ARGV2}) + else() + set(is_list "") + endif() + + execute_process( + COMMAND python "${CMAKE_SOURCE_DIR}/pyside2_config.py" ${option} + OUTPUT_VARIABLE ${output_var} + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if ("${${output_var}}" STREQUAL "") + message(FATAL_ERROR "Got empty string when running: pyside2_config.py ${option}") + endif() + if(is_list) + string (REPLACE " " ";" ${output_var} "${${output_var}}") + endif() +endmacro() + +# Get relevant general paths, include paths and linker flags. +pyside2_config(--pyside2 PYSIDE2_PATH) +set(SHIBOKEN_PATH "${PYSIDE2_PATH}/shiboken2") + +if(NOT EXISTS ${SHIBOKEN_PATH}) + message(FATAL_ERROR "Shiboken executable not found at path: ${SHIBOKEN_PATH}") +endif() + +pyside2_config(--python-include PYTHON_INCLUDE_DIR) +pyside2_config(--pyside2-include PYSIDE2_INCLUDE_DIR 1) +pyside2_config(--python-link-cmake PYTHON_LINKING_DATA 1) +pyside2_config(--pyside2-shared-libraries-cmake PYSIDE2_SHARED_LIBRARIES 1) + +# Get all relevant Qt include dirs, to pass them on to shiboken. +get_property(QT_CORE_INCLUDE_DIRS TARGET Qt5::Core PROPERTY INTERFACE_INCLUDE_DIRECTORIES) +get_property(QT_GUI_INCLUDE_DIRS TARGET Qt5::Gui PROPERTY INTERFACE_INCLUDE_DIRECTORIES) +get_property(QT_WIDGETS_INCLUDE_DIRS TARGET Qt5::Widgets PROPERTY INTERFACE_INCLUDE_DIRECTORIES) +set(QT_INCLUDE_DIRS ${QT_CORE_INCLUDE_DIRS} ${QT_GUI_INCLUDE_DIRS} ${QT_WIDGETS_INCLUDE_DIRS}) +set(INCLUDES "") +foreach(INCLUDE_DIR ${QT_INCLUDE_DIRS}) + list(APPEND INCLUDES "-I${INCLUDE_DIR}") +endforeach() + +# Set up the options to pass to shiboken. +set(WRAPPED_HEADER ${CMAKE_SOURCE_DIR}/wrappedclasses.h) +set(TYPESYSTEM_FILE ${CMAKE_SOURCE_DIR}/scriptableapplication.xml) + +set(SHIBOKEN_OPTIONS --generator-set=shiboken --enable-parent-ctor-heuristic + --enable-pyside-extensions --enable-return-value-heuristic --use-isnull-as-nb_nonzero + --avoid-protected-hack + ${INCLUDES} + -I${CMAKE_SOURCE_DIR} + -T${CMAKE_SOURCE_DIR} + -T${PYSIDE2_PATH}/typesystems + --output-directory=${CMAKE_CURRENT_BINARY_DIR} + ) + +# Specify which sources will be generated by shiboken, and their dependencies. +set(GENERATED_SOURCES + ${CMAKE_CURRENT_BINARY_DIR}/AppLib/applib_module_wrapper.cpp + ${CMAKE_CURRENT_BINARY_DIR}/AppLib/mainwindow_wrapper.cpp) + +set(GENERATED_SOURCES_DEPENDENCIES + ${WRAPPED_HEADER} + ${TYPESYSTEM_FILE} + ) + +# Add custom target to run shiboken. +add_custom_command(OUTPUT ${GENERATED_SOURCES} + COMMAND ${SHIBOKEN_PATH} + ${SHIBOKEN_OPTIONS} ${WRAPPED_HEADER} ${TYPESYSTEM_FILE} + DEPENDS ${GENERATED_SOURCES_DEPENDENCIES} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Running generator for ${TYPESYSTEM_FILE}.") + +# Set the CPP files. +set(SOURCES + mainwindow.cpp + pythonutils.cpp + ${GENERATED_SOURCES} + ) + +# We need to include the headers for the module bindings that we use. +set(PYSIDE2_ADDITIONAL_INCLUDES "") +foreach(INCLUDE_DIR ${PYSIDE2_INCLUDE_DIR}) + list(APPEND PYSIDE2_ADDITIONAL_INCLUDES "${INCLUDE_DIR}/QtCore") + list(APPEND PYSIDE2_ADDITIONAL_INCLUDES "${INCLUDE_DIR}/QtGui") + list(APPEND PYSIDE2_ADDITIONAL_INCLUDES "${INCLUDE_DIR}/QtWidgets") +endforeach() + +# ============================================================================================= +# !!! (The section below is deployment related, so in a real world application you will want to +# take care of this properly with some custom script or tool). +# ============================================================================================= +# Enable rpaths so that the example can be executed from the build dir. +set(CMAKE_SKIP_BUILD_RPATH FALSE) +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +SET(CMAKE_INSTALL_RPATH "") +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +# ============================================================================================= +# !!! End of dubious section. +# ============================================================================================= + +# Declare executable so we can enable automoc. +add_executable(${PROJECT_NAME} main.cpp) + +# Enable automoc. +set_property(TARGET ${PROJECT_NAME} PROPERTY AUTOMOC 1) + +# Add the rest of the sources. +target_sources(${PROJECT_NAME} PUBLIC ${SOURCES}) + +# Apply relevant include and link flags. +target_include_directories(${PROJECT_NAME} PRIVATE ${PYTHON_INCLUDE_DIR}) +target_include_directories(${PROJECT_NAME} PRIVATE ${PYSIDE2_INCLUDE_DIR}) +target_include_directories(${PROJECT_NAME} PRIVATE ${PYSIDE2_ADDITIONAL_INCLUDES}) +target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}) + +target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets) +target_link_libraries(${PROJECT_NAME} PRIVATE ${PYSIDE2_SHARED_LIBRARIES}) + +# Find and link to the python library. +list(GET PYTHON_LINKING_DATA 0 PYTHON_LIBDIR) +list(GET PYTHON_LINKING_DATA 1 PYTHON_LIB) +find_library(PYTHON_LINK_FLAGS ${PYTHON_LIB} HINTS ${PYTHON_LIBDIR}) +target_link_libraries(${PROJECT_NAME} PRIVATE ${PYTHON_LINK_FLAGS}) + +# Same as CONFIG += no_keywords to avoid syntax errors in object.h due to the usage of the word Slot +target_compile_definitions(${PROJECT_NAME} PRIVATE QT_NO_KEYWORDS) + +if(WIN32) + # ============================================================================================= + # !!! (The section below is deployment related, so in a real world application you will want to + # take care of this properly (this is simply to eliminate errors that users usually encounter. + # ============================================================================================= + # Circumvent some "#pragma comment(lib)"s in "include/pyconfig.h" which might force to link + # against a wrong python shared library. + + set(PYTHON_VERSIONS_LIST 3 32 33 34 35 36 37 38) + set(PYTHON_ADDITIONAL_LINK_FLAGS "") + foreach(VER ${PYTHON_VERSIONS_LIST}) + set(PYTHON_ADDITIONAL_LINK_FLAGS + "${PYTHON_ADDITIONAL_LINK_FLAGS} /NODEFAULTLIB:\"python${VER}_d.lib\"") + set(PYTHON_ADDITIONAL_LINK_FLAGS + "${PYTHON_ADDITIONAL_LINK_FLAGS} /NODEFAULTLIB:\"python${VER}.lib\"") + endforeach() + + set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "${PYTHON_ADDITIONAL_LINK_FLAGS}") + + # Add custom target to hard link PySide2 shared libraries (just like in qmake example), so you + # don't have to set PATH manually to point to the PySide2 package. + foreach(LIBRARY_PATH ${PYSIDE2_SHARED_LIBRARIES}) + string(REGEX REPLACE ".lib$" ".dll" LIBRARY_PATH ${LIBRARY_PATH}) + get_filename_component(BASE_NAME ${LIBRARY_PATH} NAME) + file(TO_NATIVE_PATH ${LIBRARY_PATH} SOURCE_PATH) + file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${BASE_NAME}" DEST_PATH) + add_custom_command(OUTPUT "${BASE_NAME}" + COMMAND mklink /H "${DEST_PATH}" "${SOURCE_PATH}" + DEPENDS ${LIBRARY_PATH} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Creating hardlink to PySide2 shared library ${BASE_NAME}") + + # Fake target that depends on the previous one, but has special ALL keyword, which means + # it will always be executed. + add_custom_target("fake_${BASE_NAME}" ALL DEPENDS ${BASE_NAME}) + endforeach() + # ============================================================================================= + # !!! End of dubious section. + # ============================================================================================= +endif() diff --git a/examples/scriptableapplication/README.CMake.txt b/examples/scriptableapplication/README.CMake.txt new file mode 100644 index 000000000..ea658efd5 --- /dev/null +++ b/examples/scriptableapplication/README.CMake.txt @@ -0,0 +1,28 @@ +For general information read README.txt instead. + +To build this example you will need: +* A recent version of CMake (3.1+) +* Make sure that a --standalone PySide2 package (bundled with Qt libraries) is installed into the + current active Python environment (system or virtualenv) +* qmake to be in your PATH (so that CMake find_package(Qt5) works; used for include headers) +* use the same Qt version for building the example application, as was used for building +* PySide2, this is to ensure binary compatibility between the newly generated bindings libraries, + the PySide2 libraries and the Qt libraries. + +For Windows you will also need: +* Visual studio environment to be active in your terminal +* Correct visual studio architecture chosen (32 vs 64 bit) +* Make sure that your Qt + Python + PySide + CMake app build configuration is the same (either or + all Release (which is more likely) or all Debug). + +You can build this example by executing the following commands (slightly adapted to your file +system) in a terminal: + +cd ~/pyside-setup/examples/scriptableapplication +(or cd C:\pyside-setup\examples\scriptableapplication) +mkdir build +cd build +cmake -H.. -B. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release +(or cmake -H.. -B. -G "NMake Makefiles JOM" -DCMAKE_BUILD_TYPE=Release) +make (or nmake / jom) +./scriptableapplication (or scriptableapplication.exe) diff --git a/examples/scriptableapplication/pyside2_config.py b/examples/scriptableapplication/pyside2_config.py index c81d81827..361043aef 100644 --- a/examples/scriptableapplication/pyside2_config.py +++ b/examples/scriptableapplication/pyside2_config.py @@ -73,6 +73,15 @@ def sharedLibraryGlobPattern(): glob = '*.' + sharedLibrarySuffix() return glob if sys.platform == 'win32' else 'lib' + glob +def filterPySide2SharedLibraries(list): + def predicate(item): + basename = os.path.basename(item) + if 'shiboken' in basename or 'pyside2' in basename: + return True + return False + result = [item for item in list if predicate(item)] + return result + # Return qmake link option for a library file name def linkOption(lib): baseName = os.path.splitext(os.path.basename(lib))[0] @@ -99,54 +108,86 @@ def pythonVersion(): def pythonInclude(): return sysconfig.get_python_inc() -def pythonLink(): +def pythonLinkQmake(): + flags = pythonLinkData() + if sys.platform == 'win32' or sys.platform == 'darwin': + return '-L{} -l{}'.format(flags['libdir'], flags['lib']) + + # Linux and anything else + return '-l{}'.format(flags['lib']) + +def pythonLinkCmake(): + flags = pythonLinkData() + libdir = flags['libdir'] + lib = re.sub(r'.dll$', '.lib', flags['lib']) + return '{} {}'.format(libdir, lib) + +def pythonLinkData(): # @TODO Fix to work with static builds of Python libdir = sysconfig.get_config_var('LIBDIR') version = pythonVersion() version_no_dots = version.replace('.', '') + flags = {} + flags['libdir'] = libdir if sys.platform == 'win32': suffix = '_d' if any([tup[0].endswith('_d.pyd') for tup in imp.get_suffixes()]) else '' - return "-L%s -lpython%s%s" % (libdir, version_no_dots, suffix) + flags['lib'] = 'python{}{}'.format(version_no_dots, suffix) - if sys.platform == 'darwin': - return '-L%s -lpython%s' % (libdir, version) + elif sys.platform == 'darwin': + flags['lib'] = 'python{}'.format(version) # Linux and anything else - if sys.version_info[0] < 3: - suffix = '_d' if any([tup[0].endswith('_d.so') for tup in imp.get_suffixes()]) else '' - return "-lpython%s%s" % (version, suffix) else: - return "-lpython%s%s" % (version, sys.abiflags) + if sys.version_info[0] < 3: + suffix = '_d' if any([tup[0].endswith('_d.so') for tup in imp.get_suffixes()]) else '' + flags['lib'] = 'python{}{}'.format(version, suffix) + else: + flags['lib'] = 'python{}{}'.format(version, sys.abiflags) + + return flags def pyside2Include(): pySide2 = findPySide2() if pySide2 is None: return None - return "%s/include/PySide2 %s/include/shiboken2" % (pySide2, pySide2) + return "{0}/include/PySide2 {0}/include/shiboken2".format(pySide2) def pyside2Link(): pySide2 = findPySide2() if pySide2 is None: return None - link = "-L%s" % pySide2 - for lib in glob.glob(os.path.join(pySide2, sharedLibraryGlobPattern())): + link = "-L{}".format(pySide2) + glob_result = glob.glob(os.path.join(pySide2, sharedLibraryGlobPattern())) + for lib in filterPySide2SharedLibraries(glob_result): link += ' ' link += linkOption(lib) return link -def pyside2SharedLibraries(): +def pyside2SharedLibrariesData(): pySide2 = findPySide2() if pySide2 is None: return None + glob_result = glob.glob(os.path.join(pySide2, sharedLibraryGlobPattern())) + filtered_libs = filterPySide2SharedLibraries(glob_result) + libs = [] if sys.platform == 'win32': - libs = [] - for lib in glob.glob(os.path.join(pySide2, sharedLibraryGlobPattern())): + for lib in filtered_libs: libs.append(os.path.realpath(lib)) + else: + for lib in filtered_libs: + libs.append(lib) + return libs + +def pyside2SharedLibraries(): + libs = pyside2SharedLibrariesData() + if libs is None: + return None + + if sys.platform == 'win32': if not libs: return '' - dlls = '' for lib in libs: dll = os.path.splitext(lib)[0] + '.dll' @@ -154,10 +195,15 @@ def pyside2SharedLibraries(): return dlls else: - libs = '' - for lib in glob.glob(os.path.join(pySide2, sharedLibraryGlobPattern())): - libs += ' ' + lib - return libs + libs_string = '' + for lib in libs: + libs_string += ' ' + lib + return libs_string + +def pyside2SharedLibrariesCmake(): + libs = pyside2SharedLibrariesData() + result = ' '.join(libs) + return result def clangBinPath(): source = 'LLVM_INSTALL_DIR' @@ -207,7 +253,13 @@ if option == '--python-include' or option == '-a': print(i) if option == '--python-link' or option == '-a': - l = pythonLink() + l = pythonLinkQmake() + if l is None: + sys.exit('Unable to locate Python') + print(l) + +if option == '--python-link-cmake' or option == '-a': + l = pythonLinkCmake() if l is None: sys.exit('Unable to locate Python') print(l) @@ -215,7 +267,13 @@ if option == '--python-link' or option == '-a': if option == '--pyside2-shared-libraries' or option == '-a': l = pyside2SharedLibraries() if l is None: - sys.exit('Unable to locate the PySide sahred libraries') + sys.exit('Unable to locate the PySide2 shared libraries') + print(l) + +if option == '--pyside2-shared-libraries-cmake' or option == '-a': + l = pyside2SharedLibrariesCmake() + if l is None: + sys.exit('Unable to locate the PySide2 shared libraries') print(l) if option == '--clang-bin-dir' or option == '-a': -- cgit v1.2.3