diff options
Diffstat (limited to 'libs/cmake_common')
| -rw-r--r-- | libs/cmake_common/.pre-commit-config.yaml | 15 | ||||
| -rw-r--r-- | libs/cmake_common/README.md | 109 | ||||
| -rw-r--r-- | libs/cmake_common/mo2-cmake-config.cmake | 3 | ||||
| -rw-r--r-- | libs/cmake_common/mo2.cmake | 48 | ||||
| -rw-r--r-- | libs/cmake_common/mo2_cpp.cmake | 358 | ||||
| -rw-r--r-- | libs/cmake_common/mo2_python.cmake | 356 | ||||
| -rw-r--r-- | libs/cmake_common/mo2_utils.cmake | 464 | ||||
| -rw-r--r-- | libs/cmake_common/mo2_versions.cmake | 49 |
8 files changed, 1402 insertions, 0 deletions
diff --git a/libs/cmake_common/.pre-commit-config.yaml b/libs/cmake_common/.pre-commit-config.yaml new file mode 100644 index 0000000..e00bc0d --- /dev/null +++ b/libs/cmake_common/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-merge-conflict + - id: check-case-conflict + +ci: + autofix_commit_msg: "[pre-commit.ci] Auto fixes from pre-commit.com hooks." + autofix_prs: true + autoupdate_commit_msg: "[pre-commit.ci] Pre-commit autoupdate." + autoupdate_schedule: quarterly + submodules: false diff --git a/libs/cmake_common/README.md b/libs/cmake_common/README.md new file mode 100644 index 0000000..c66a5a3 --- /dev/null +++ b/libs/cmake_common/README.md @@ -0,0 +1,109 @@ +# MO2 CMake Common + +This repository contains useful CMake macro and functions that are used to ease creating +CMake configuration for most of MO2 repositories (e.g., +[uibase](https://github.com/ModOrganizer2/modorganizer-uibase), +[plugin_python](https://github.com/ModOrganizer2/modorganizer-plugin_python) or +[ModOrganizer2](https://github.com/ModOrganizer2/modorganizer) itself). + +## Getting Started + +### 1. With VCPKG + +Add [MO2 VCPKG registry](https://github.com/ModOrganizer2/vcpkg-registry) to your +`vcpkg-configuration.json`: + +```json +{ + "default-registry": { + "kind": "git", + "repository": "https://github.com/Microsoft/vcpkg", + "baseline": "f61a294e765b257926ae9e9d85f96468a0af74e7" + }, + "registries": [ + { + "kind": "git", + "repository": "https://github.com/ModOrganizer2/vcpkg-registry", + "baseline": "27d8adbfe9e4ce88a875be3a45fadab69869eb60", + "packages": ["mo2-cmake"] + } + ] +} +``` + +Add `mo2-cmake` to your VCPKG dependencies (`vcpkg.json`) and then import the utilities +with `find_package` in your CMake configuration: + +```cmake +find_package(mo2-cmake CONFIG REQUIRED) +``` + +### 2. Manually + +Clone this repository somewhere and then `include(mo2.cmake)` in your CMake +configuration files. + +## Usage + +Be aware that using these utilities will automatically set some (not too intrusive) +global variable on your project. + +In order to properly use this package, you should set `CMAKE_INSTALL_PREFIX` to a valid +location. +There are two possible way of using this package controlled by the `MO2_INSTALL_IS_BIN` +option: + +- if `MO2_INSTALL_IS_BIN` is `OFF` (default), this assumes a layout with a `bin`, + `lib`, `include` and `pdb` folder under `CMAKE_INSTALL_PREFIX`, +- if `MO2_INSTALL_IS_BIN` is `ON` (default when building standalone), this assumes + that `CMAKE_INSTALL_PREFIX` point directly to the equivalent `bin` folder. + +Importing the utilities will make the following variables available: + +- `MO2_QT_VERSION` - The Qt version used by MO2, as `major.minor.patch`. +- `MO2_QT_MAJOR_VERSION`, `MO2_QT_MINOR_VERSION` and `MO2_QT_PATCH_VERSION` - + Respectively the major, minor and patch version of Qt used by MO2. +- `MO2_PYTHON_VERSION` - The Python version used by MO2 as `major.minor` (patch version + should not be relevant). + +All functions are prefixed by `mo2_` and should not conflict with other existing +functions. + +### Generic Utilities + +- `mo2_set_if_not_defined` - Set a variable to a given value if the variable is not + defined. +- `mo2_add_subdirectories` - +- `mo2_find_python_executable` - Find Python executable. +- `mo2_find_git_hash` - Find the hash of the current git HEAD. +- `mo2_find_qt_executable` - Find a given Qt executable. +- `mo2_set_project_to_run_from_install` - Configure the debug executable for a VS project. +- `mo2_add_filter` - Add a source group filter. +- `mo2_deploy_qt_for_tests` - Deploy Qt DLLs, etc., for tests. +- `mo2_deploy_qt` - Deploy Qt DLLs for ModOrganizer2. +- `mo2_add_lupdate` - Create a target to run Qt `lupdate`. +- `mo2_add_lrelease` - Create a target to Qt `lrelease`. +- `mo2_add_translations` - Add targets to generate translations for the given target. + +### C++ Utilities + +TODO: + +- `mo2_configure_warnings` - Utility function configure warnings for a target. +- `mo2_configure_sources` - Glob and configure sources, including Qt-related files + (.ui, etc.) for a target. +- `mo2_configure_msvc` - Set some MSVC-specific flag for a target. +- (Deprecated) `mo2_configure_target` - Combine the above function + Extra stuff. +- (Deprecated) `mo2_configure_plugin` +- `mo2_configure_tests` - TO BE CHANGED +- `mo2_install_plugin` - Install a plugin. + +### Python Utilities + +- `mo2_python_uifiles` - Add a target to generate `.py` files from `.ui` files. +- `mo2_python_pip_install` - Install Python packages. +- `mo2_python_requirements` - Install plugin requirements from a `plugin-requirements.txt` + file, ready to ship. +- `mo2_configure_python_module` - Configure a Python module plugin. +- (Deprecated) `mo2_configure_python_simple` - Configure a Python single file plugin. +- `mo2_configure_python` - Wrapper for the two above functions. diff --git a/libs/cmake_common/mo2-cmake-config.cmake b/libs/cmake_common/mo2-cmake-config.cmake new file mode 100644 index 0000000..3b4ed19 --- /dev/null +++ b/libs/cmake_common/mo2-cmake-config.cmake @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.22) + +include(${CMAKE_CURRENT_LIST_DIR}/mo2.cmake) diff --git a/libs/cmake_common/mo2.cmake b/libs/cmake_common/mo2.cmake new file mode 100644 index 0000000..7e8c3f7 --- /dev/null +++ b/libs/cmake_common/mo2.cmake @@ -0,0 +1,48 @@ +cmake_minimum_required(VERSION 3.22) + +if (DEFINED MO2_DEFINED) + return() +endif() + +option(MO2_INSTALL_IS_BIN + "if set, CMAKE_INSTALL_PREFIX is assumed to point to bin rather than one level below") + +if (MO2_INSTALL_IS_BIN) + set(MO2_INSTALL_BIN ".") +else() + set(MO2_INSTALL_BIN "bin") +endif() + +include(${CMAKE_CURRENT_LIST_DIR}/mo2_utils.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/mo2_versions.cmake) + +# this makes VS install everything when building solution +if(MSVC) + set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +endif() + +# this find Python globally rather than virtual environments, even when one is active +set(Python_FIND_VIRTUALENV STANDARD) + +# this set the imported location of targets for missing configurations - this silents +# many CMP0111 warnings from CMake +set(CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL MinSizeRel RelWithDebInfo Release None) +set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO RelWithDebInfo Release MinSizeRel None) +set(CMAKE_MAP_IMPORTED_CONFIG_RELEASE Release RelWithDebInfo MinSizeRel None) + +# allow setting folder property on targets for better organization in VS +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# put code generated by Qt in a autogen group in VS +set_property(GLOBAL PROPERTY AUTOGEN_SOURCE_GROUP autogen) +set_property(GLOBAL PROPERTY AUTOMOC_SOURCE_GROUP autogen) +set_property(GLOBAL PROPERTY AUTORCC_SOURCE_GROUP autogen) + +# put targets generated by Qt into a autogen folder (this is not the same as the above) +set(QT_TARGETS_FOLDER autogen) + +include(${CMAKE_CURRENT_LIST_DIR}/mo2_cpp.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/mo2_python.cmake) + +# mark as included +set(MO2_DEFINED true) diff --git a/libs/cmake_common/mo2_cpp.cmake b/libs/cmake_common/mo2_cpp.cmake new file mode 100644 index 0000000..1ed4250 --- /dev/null +++ b/libs/cmake_common/mo2_cpp.cmake @@ -0,0 +1,358 @@ +cmake_minimum_required(VERSION 3.21) + +include(CMakeParseArguments) +include(${CMAKE_CURRENT_LIST_DIR}/mo2_utils.cmake) + +#! mo2_configure_warnings : configuration warning for C++ target +# +# \param:WARNINGS level of warnings, possible values are ON/All, OFF, or 1, 2, 3, 4 +# for corresponding /W flags (ON is All) (default ON) +# \param:EXTERNAL enable warnings for external libraries, possible values are +# the same as warnings, but ON is 3 (default 1) +# +function(mo2_configure_warnings TARGET) + cmake_parse_arguments(MO2 "" "WARNINGS;EXTERNAL" "" ${ARGN}) + + mo2_set_if_not_defined(MO2_WARNINGS ON) + mo2_set_if_not_defined(MO2_EXTERNAL 1) + + if (${MO2_WARNINGS} STREQUAL "ON") + set(MO2_WARNINGS "All") + endif() + + if (${MO2_EXTERNAL} STREQUAL "ON") + set(MO2_EXTERNAL "3") + endif() + + if(NOT (${MO2_WARNINGS} STREQUAL "OFF")) + if(MSVC) + string(TOLOWER ${MO2_WARNINGS} MO2_WARNINGS) + target_compile_options(${TARGET} PRIVATE "/W${MO2_WARNINGS}" "/wd4464") + + # external warnings + if (${MO2_EXTERNAL} STREQUAL "OFF") + target_compile_options(${TARGET} + PRIVATE "/external:anglebrackets" "/external:W0") + else() + string(TOLOWER ${MO2_EXTERNAL} MO2_EXTERNAL) + target_compile_options(${TARGET} + PRIVATE "/external:anglebrackets" "/external:W${MO2_EXTERNAL}") + endif() + elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(${TARGET} PRIVATE -Wall -Wextra -Wno-unused-parameter) + endif() + endif() + +endfunction() + +#! mo2_target_sources : add sources to a given target, eventually putting them in +# a folder +# +# \param: FILES list of .ui files to add +# \param: RC_FILES list of .qrc or .rc files to add +# +function(mo2_target_sources TARGET) + cmake_parse_arguments(MO2 "" "FOLDER" "PRIVATE;PUBLIC" ${ARGN}) + + mo2_set_if_not_defined(MO2_PRIVATE "") + mo2_set_if_not_defined(MO2_PUBLIC "") + + set(_sources "") + + if (MO2_PRIVATE) + target_sources(${TARGET} PRIVATE ${MO2_PRIVATE}) + list(APPEND _sources ${MO2_PRIVATE}) + endif() + + if (MO2_PUBLIC) + target_sources(${TARGET} PUBLIC ${MO2_PUBLIC}) + list(APPEND _sources ${MO2_PUBLIC}) + endif() + + if ((DEFINED MO2_FOLDER) AND _sources) + source_group(${MO2_FOLDER} FILES ${_sources}) + endif() + +endfunction() + +#! mo2_default_source_group : configure default source groups for MO2 +# +# \param:NO_SRC if set, the src source_group will not be created, default if false +# +function(mo2_default_source_group) + cmake_parse_arguments(MO2 "SOURCE_TREE" "" "" ${ARGN}) + + # remove the CMake Rules autogenerated folder + source_group("CMake Rules" REGULAR_EXPRESSION "^$") + source_group(ui REGULAR_EXPRESSION ".*\\.ui") + source_group(cmake FILES CMakeLists.txt) + source_group(autogen REGULAR_EXPRESSION ".*\\cmake_pch.*|.*\\.rule") + source_group(resources REGULAR_EXPRESSION ".*\\.qrc|.*\\.rc") + + if (NOT NO_SRC) + source_group(src REGULAR_EXPRESSION ".*\\.(h|cpp)$") + endif() +endfunction() + +#! mo2_configure_sources : configure sources for the given C++ target +# +# \param:SOURCE_TREE if set, a source_group will be created using TREE +# +function(mo2_configure_sources TARGET) + cmake_parse_arguments(MO2 "SOURCE_TREE" "" "" ${ARGN}) + + # find source files + if(DEFINED AUTOGEN_BUILD_DIR) + set(UI_HEADERS_DIR ${AUTOGEN_BUILD_DIR}) + else() + set(UI_HEADERS_DIR ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_autogen/include_RelWithDebInfo) + endif() + + file(GLOB_RECURSE source_files CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) + file(GLOB_RECURSE header_files CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.h) + file(GLOB_RECURSE qrc_files CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.qrc) + file(GLOB_RECURSE rc_files CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.rc) + file(GLOB_RECURSE ui_files CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.ui) + file(GLOB_RECURSE ui_header_files CONFIGURE_DEPENDS ${UI_HEADERS_DIR}/*.h) + file(GLOB_RECURSE rule_files CONFIGURE_DEPENDS ${CMAKE_BINARY_DIR}/*.rule) + + + + if (${MO2_SOURCE_TREE}) + mo2_default_source_group(NO_SRC) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} + PREFIX src FILES ${source_files} ${header_files}) + else() + mo2_default_source_group() + endif() + + target_sources(${TARGET} + PRIVATE + ${source_files} + ${header_files} + ${ui_files} + ${ui_header_files} + ${qrc_files} + ${rc_files} + ${misc_files} + ${qm_files}) + +endfunction() + +#! mo2_configure_msvc : set flags for C++ target with MSVC +# +# \param:PERMISSIVE permissive mode (default OFF) +# \param:BIGOBJ enable bigobj (default OFF) +# \param:CLI enable C++/CLR (default OFF) +# +function(mo2_configure_msvc TARGET) + + if (NOT MSVC) + return() + endif() + + cmake_parse_arguments(MO2 "" "PERMISSIVE;BIGOBJ;CLI" "" ${ARGN}) + + set(CXX_STANDARD 23) + if (${MO2_CLI}) + set(CXX_STANDARD 17) + endif() + set_target_properties(${TARGET} PROPERTIES + CXX_STANDARD ${CXX_STANDARD} CXX_EXTENSIONS OFF) + + if(NOT ${MO2_PERMISSIVE}) + target_compile_options(${TARGET} PRIVATE "/permissive-") + endif() + + if(${MO2_BIGOBJ}) + target_compile_options(${TARGET} PRIVATE "/bigobj") + endif() + + # multi-threaded compilation + target_compile_options(${TARGET} PRIVATE "/MP") + + # VS emits a warning for LTCG, at least for uibase, so maybe not required? + target_link_options(${TARGET} + PRIVATE + $<$<CONFIG:RelWithDebInfo>: + # enable link-time code generation + /LTCG + + # disable incremental linking + /INCREMENTAL:NO + + # eliminates functions and data that are never referenced + /OPT:REF + + # perform identical COMDAT folding + /OPT:ICF + >) + + if(${MO2_CLI}) + set_target_properties(${TARGET} PROPERTIES COMMON_LANGUAGE_RUNTIME "") + endif() + + get_property(CURRENT_STARTUP_PROJECT + DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT) + + if (NOT CURRENT_STARTUP_PROJECT) + message(STATUS "MO2: Setting startup project to " ${TARGET} ".") + set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${TARGET}) + endif() + +endfunction() + +#! mo2_configure_target : do basic configuration for a MO2 C++ target +# +# this functions does many things: +# - glob relevant files and add them to the target +# - set many compile flags, definitions, etc. +# - add step to create translations (if not turned OFF) +# +# \param:SOURCE_TREE if set, a source_group will be created using TREE +# \param:NO_SOURCES if set, mo2_configure_sources will not be called +# \param:WARNINGS enable all warnings, possible values are ON/All, OFF, or 1, 2, 3, 4 +# for corresponding /W flags (ON is All) (default ON) +# \param:EXTERNAL_WARNINGS enable warnings for external libraries, possible values are +# the same as warnings, but ON is 3 (default 1) +# \param:PERMISSIVE permissive mode (default OFF) +# \param:BIGOBJ enable bigobj (default OFF) +# \param:CLI enable C++/CLR (default OFF) +# \param:TRANSLATIONS generate translations (default ON) +# \param:AUTOMOC automoc (and autouic, autoqrc), (default ON) +# \param:EXTRA_TRANSLATIONS extra translations to include (folder) +# +function(mo2_configure_target TARGET) + cmake_parse_arguments(MO2 "SOURCE_TREE;NO_SOURCES" + "WARNINGS;EXTERNAL_WARNINGS;PERMISSIVE;BIGOBJ;CLI;TRANSLATIONS;AUTOMOC" + "EXTRA_TRANSLATIONS" + ${ARGN}) + + # configure parameters and compiler flags + mo2_set_if_not_defined(MO2_NO_SOURCES OFF) + mo2_set_if_not_defined(MO2_PERMISSIVE OFF) + mo2_set_if_not_defined(MO2_BIGOBJ OFF) + mo2_set_if_not_defined(MO2_CLI OFF) + mo2_set_if_not_defined(MO2_TRANSLATIONS ON) + mo2_set_if_not_defined(MO2_AUTOMOC ON) + mo2_set_if_not_defined(MO2_EXTRA_TRANSLATIONS "") + + mo2_configure_warnings(${TARGET} ${ARGN}) + mo2_configure_msvc(${TARGET} ${ARGN}) + + if (NOT MO2_NO_SOURCES) + mo2_configure_sources(${TARGET} ${ARGN}) + endif() + + if (${MO2_AUTOMOC}) + find_package(Qt6 COMPONENTS Widgets REQUIRED) + set_target_properties(${TARGET} + PROPERTIES AUTOMOC ON AUTOUIC ON AUTORCC ON) + endif() + + if(${MO2_TRANSLATIONS}) + mo2_add_translations(${TARGET} + INSTALL_RELEASE + SOURCES ${CMAKE_CURRENT_SOURCE_DIR} ${MO2_EXTRA_TRANSLATIONS}) + endif() + + mo2_find_git_hash(GIT_COMMIT_HASH) + target_compile_definitions( + ${TARGET} PRIVATE NOMINMAX QT_MESSAGELOGCONTEXT GITID="${GIT_COMMIT_HASH}") + + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/pch.h) + target_precompile_headers(${PROJECT_NAME} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/pch.h) + endif() +endfunction() + +#! mo2_configure_tests : configure a target as a MO2 C++ tests +# +# this function creates a set of tests available in the ${TARGET}_gtests variable +# +# extra arguments are given to mo2_configure_target, TRANSLATIONS and AUTOMOC are +# OFF by default +# +function(mo2_configure_tests TARGET) + cmake_parse_arguments(MO2 "NO_MOCK;NO_MAIN" "" "" ${ARGN}) + mo2_configure_target(${TARGET} TRANSLATIONS OFF AUTOMOC OFF ${MO2_UNPARSED_ARGUMENTS}) + + find_package(GTest REQUIRED) + target_link_libraries(${TARGET} PRIVATE GTest::gtest) + + if (NOT MO2_NO_MOCK) + target_link_libraries(${TARGET} PRIVATE GTest::gmock) + endif() + if (NOT MO2_NO_MAIN) + target_link_libraries(${TARGET} PRIVATE GTest::gtest_main) + endif() + + # gtest_discover_tests would be nice but it requires Qt DLL, uibase, etc., in the + # path, etc., and is not working right now + # + # there is an open CMake issue: https://gitlab.kitware.com/cmake/cmake/-/issues/21453 + # + # gtest_discover_tests(${TARGET} + # WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin + # PROPERTIES + # VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin + # ) + # + + gtest_add_tests( + TARGET ${TARGET} + TEST_LIST ${TARGET}_gtests + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) + set(${TARGET}_gtests ${${TARGET}_gtests} PARENT_SCOPE) + + if(WIN32) + mo2_deploy_qt_for_tests( + TARGET ${TARGET} + BINARIES "$<FILTER:$<TARGET_RUNTIME_DLLS:${TARGET}>,EXCLUDE,^.*[/\\]Qt[^/\\]*[.]dll>") + + set_tests_properties(${${TARGET}_gtests} + PROPERTIES + ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$<JOIN:$<TARGET_RUNTIME_DLL_DIRS:${TARGET}>,\;>" + ) + endif() +endfunction() + +#! mo2_configure_plugin : configure a target as a MO2 C++ plugin +# +# this function automatically set uibase as a dependency +# +# extra arguments are given to mo2_configure_target +# +function(mo2_configure_plugin TARGET) + mo2_configure_target(${TARGET} ${ARGN}) + if(WIN32) + mo2_set_project_to_run_from_install( + ${TARGET} EXECUTABLE ${CMAKE_INSTALL_PREFIX}/${MO2_INSTALL_BIN}/ModOrganizer.exe) + endif() +endfunction() + +#! mo2_install_plugin : install the given MO2 plugin +# +# for this to work properly, the target must have been configured +# +# \param:FOLDER install the plugin as a folder, instead of a single DLL +# +function(mo2_install_plugin TARGET) + cmake_parse_arguments(MO2 "FOLDER" "" "" ${ARGN}) + + if (${MO2_FOLDER}) + install(TARGETS ${TARGET} RUNTIME DESTINATION ${MO2_INSTALL_BIN}/plugins/$<TARGET_FILE_BASE_NAME:${TARGET}>) + else() + install(TARGETS ${TARGET} RUNTIME DESTINATION ${MO2_INSTALL_BIN}/plugins) + endif() + + if (NOT MO2_INSTALL_IS_BIN) + install(TARGETS ${TARGET} ARCHIVE DESTINATION lib) + if(WIN32) + # install PDB if possible + install(FILES $<TARGET_PDB_FILE:${TARGET}> DESTINATION pdb OPTIONAL) + endif() + endif() + +endfunction() diff --git a/libs/cmake_common/mo2_python.cmake b/libs/cmake_common/mo2_python.cmake new file mode 100644 index 0000000..c8af496 --- /dev/null +++ b/libs/cmake_common/mo2_python.cmake @@ -0,0 +1,356 @@ +cmake_minimum_required(VERSION 3.16) + +include(${CMAKE_CURRENT_LIST_DIR}/mo2_utils.cmake) + +set(MO2_PYLIBS_DIR "${CMAKE_BINARY_DIR}/pylibs" CACHE PATH + "default for path for Python libraries") + +#! mo2_python_pip_install : run "pip install ..." +# +# \param:TARGET target to install Python package for +# \param:DIRECTORY directory to install libraries to, REQUIRED +# \param:PACKAGES packages to install, REQUIRED, can contain version constraints, e.g., +# "PyQt6==6.3.0" +# +function(mo2_python_pip_install TARGET) + cmake_parse_arguments(MO2 + "NO_DEPENDENCIES;PRE_RELEASE;NO_FORCE;USE_CACHE" "DIRECTORY" "PACKAGES;EXTRA_INDEX_URLS" ${ARGN}) + + mo2_set_if_not_defined(MO2_DIRECTORY ${MO2_PYLIBS_DIR}) + mo2_set_if_not_defined(MO2_NO_DEPENDENCIES OFF) + mo2_set_if_not_defined(MO2_PRE_RELEASE OFF) + mo2_set_if_not_defined(MO2_NO_FORCE OFF) + mo2_set_if_not_defined(MO2_USE_CACHE OFF) + mo2_set_if_not_defined(MO2_EXTRA_INDEX_URLS "") + + set(pip_install_arguments "") + + if (MO2_NO_DEPENDENCIES) + list(APPEND pip_install_arguments --no-deps) + endif() + + if (MO2_PRE_RELEASE) + list(APPEND pip_install_arguments --pre) + endif() + + if (NOT MO2_NO_FORCE) + list(APPEND pip_install_arguments --force) + endif() + + if (NOT USE_CACHE) + list(APPEND pip_install_arguments --no-cache-dir) + endif() + + foreach(_extra_index_url ${MO2_EXTRA_INDEX_URLS}) + list(APPEND pip_install_arguments --extra-index-url ${_extra_index_url}) + endforeach() + + mo2_find_python_executable(PYTHON_EXE) + + string(MAKE_C_IDENTIFIER "${MO2_PACKAGES}" PIP_FILE_LOG) + set(pip_log_file "${CMAKE_CURRENT_BINARY_DIR}/${PIP_FILE_LOG}.log") + + add_custom_command( + OUTPUT "${pip_log_file}" + COMMAND ${PYTHON_EXE} + -I + -m pip + install + ${pip_install_arguments} + --upgrade + --disable-pip-version-check + --isolated + --no-cache-dir + --target="${MO2_DIRECTORY}" + --log="${pip_log_file}" + ${MO2_PACKAGES} + ) + + set(pip_target_name "${TARGET}_pip_${PIP_FILE_LOG}") + + add_custom_target(${pip_target_name} ALL DEPENDS "${pip_log_file}") + set_target_properties(${pip_target_name} PROPERTIES FOLDER autogen) + + add_dependencies(${TARGET} ${pip_target_name}) +endfunction() + +#! mo2_python_install_pyqt : install PyQt6 and create a PyQt6 target for it +# +# it is safe to call this function multiple times, PyQt6 will only be installed once +# +function(mo2_python_install_pyqt) + if (TARGET PyQt6) + return() + endif() + + add_custom_target(PyQt6) + set_target_properties(PyQt6 PROPERTIES FOLDER autogen) + mo2_python_pip_install(PyQt6 NO_FORCE + PACKAGES + PyQt${MO2_QT_VERSION_MAJOR}==${MO2_PYQT_VERSION} + sip==${MO2_SIP_VERSION}) +endfunction() + +#! mo2_python_uifiles : create .py files from .ui files for a python target +# +# \param:TARGET target to generate .py files for +# \param:INPLACE if specified, .py files are generated next to the .ui files, useful +# for Python modules, otherwise files are generated in the binary directory +# \param:FILES list of .ui files to generate .py files from +# +function(mo2_python_uifiles TARGET) + cmake_parse_arguments(MO2 "INPLACE" "" "FILES" ${ARGN}) + + if (NOT MO2_FILES) + return() + endif() + + mo2_find_python_executable(PYTHON_EXE) + mo2_python_install_pyqt() + + message(DEBUG "generating .py from ui files: ${MO2_FILES}") + + set(pyui_files "") + foreach (UI_FILE ${MO2_FILES}) + get_filename_component(name "${UI_FILE}" NAME_WLE) + if (${MO2_INPLACE}) + get_filename_component(folder "${UI_FILE}" DIRECTORY) + else() + set(folder "${CMAKE_CURRENT_BINARY_DIR}") + endif() + + set(output "${folder}/${name}.py") + add_custom_command( + OUTPUT "${output}" + COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${CMAKE_BINARY_DIR}/pylibs + ${MO2_PYLIBS_DIR}/bin/pyuic${MO2_QT_VERSION_MAJOR}.exe + -o "${output}" + "${UI_FILE}" + DEPENDS "${UI_FILE}" + ) + + list(APPEND pyui_files "${output}") + endforeach() + + if (${MO2_INPLACE}) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} + PREFIX autogen FILES ${pyui_files}) + endif() + + add_custom_target("${TARGET}_uic" DEPENDS ${pyui_files}) + set_target_properties("${TARGET}_uic" PROPERTIES FOLDER autogen) + + add_dependencies(${TARGET} "${TARGET}_uic") + + add_dependencies("${TARGET}_uic" PyQt6) + +endfunction() + +#! mo2_python_requirements : install requirements for a python target +# +# \param:TARGET target to install requirements for +# \param:LIBDIR library to install requirements to +# +function(mo2_python_requirements TARGET) + cmake_parse_arguments(MO2 "" "LIBDIR" "" ${ARGN}) + + mo2_find_python_executable(PYTHON_EXE) + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/pip.log" + COMMAND ${PYTHON_EXE} + -I + -m pip + install --force --upgrade --disable-pip-version-check + --target="${MO2_LIBDIR}" + --log="${CMAKE_CURRENT_BINARY_DIR}/pip.log" + -r "${PROJECT_SOURCE_DIR}/plugin-requirements.txt" + DEPENDS "${PROJECT_SOURCE_DIR}/plugin-requirements.txt" + ) + add_custom_target("${TARGET}_libs" + ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/pip.log") + set_target_properties("${TARGET}_libs" PROPERTIES FOLDER autogen) + + add_dependencies(${TARGET} "${TARGET}_libs") + + file(MAKE_DIRECTORY "${MO2_LIBDIR}") + + install( + DIRECTORY "${MO2_LIBDIR}" + DESTINATION "${MO2_INSTALL_BIN}/plugins/${TARGET}/" + PATTERN "__pycache__" EXCLUDE + ) + +endfunction() + +#! mo2_configure_python_module : configure a Python plugin module +# +# \param:TARGET target for the Python plugin +# \param:LIBDIR directory to install requirements (if any) to, default is "lib" +# \param:RESDIR directory to install genereated resources (if any) to, default is "res" +# +function(mo2_configure_python_module TARGET) + cmake_parse_arguments(MO2 "" "LIBDIR;RESDIR" "" ${ARGN}) + + mo2_set_if_not_defined(MO2_LIBDIR "lib") + mo2_set_if_not_defined(MO2_RESDIR "res") + + set(res_dir "${PROJECT_SOURCE_DIR}/${MO2_RESDIR}") + set(lib_dir "${PROJECT_SOURCE_DIR}/${MO2_LIBDIR}") + + # py files + file(GLOB_RECURSE py_files CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.py) + + set(all_src_files ${py_files} ${ui_files} ${qrc_files}) + + set(src_files ${all_src_files}) + list(FILTER src_files EXCLUDE REGEX "${lib_dir}[/\\].*") + + set(lib_files ${all_src_files}) + list(FILTER lib_files INCLUDE REGEX "${lib_dir}[/\\].*") + + target_sources(${TARGET} PRIVATE ${src_files}) + source_group(cmake FILES CMakeLists.txt) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} + PREFIX src + FILES ${src_files}) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} + PREFIX ${MO2_LIBDIR} + FILES ${lib_files}) + + # ui files + file(GLOB_RECURSE ui_files CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.ui) + mo2_python_uifiles(${TARGET} INPLACE FILES ${ui_files}) + + # install requirements if there are any + if(EXISTS "${PROJECT_SOURCE_DIR}/plugin-requirements.txt") + mo2_python_requirements(${TARGET} LIBDIR "${lib_dir}") + target_sources(${TARGET} PRIVATE + "${PROJECT_SOURCE_DIR}/plugin-requirements.txt" + ) + source_group(requirements + FILES "${PROJECT_SOURCE_DIR}/plugin-requirements.txt") + endif() + + set(install_dir "${MO2_INSTALL_BIN}/plugins/${TARGET}") + + # directories that go in bin/plugins/${name} + install( + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/" + DESTINATION ${install_dir} + FILES_MATCHING PATTERN "*.py" + PATTERN ".git" EXCLUDE + PATTERN ".github" EXCLUDE + PATTERN ".tox" EXCLUDE + PATTERN ".mypy_cache" EXCLUDE + PATTERN "vsbuild" EXCLUDE) + + # copy the resource directory if it exists + if(EXISTS "${res_dir}") + install( + DIRECTORY "${res_dir}" + DESTINATION ${install_dir} + ) + endif() + +endfunction() + +#! mo2_configure_python_simple : configure a Python plugin (simple file) +# +# \param:TARGET target for the Python plugin +# +function(mo2_configure_python_simple TARGET) + + # this copies all the .py files that are directly in src/ into + # ${install_dir}/ + # + # any folder that contains at least one .py file (recursive) is copied in + # bin/plugins/data + # + + # ui files + file(GLOB ui_files CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.ui) + mo2_python_uifiles(${TARGET} FILES ${ui_files}) + + # .py files directly in the directory + file(GLOB py_files CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.py) + + # .json files directly in the directory + file(GLOB json_files CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.json) + + file(GLOB_RECURSE extra_py_files CONFIGURE_DEPENDS + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/**/*.py) + + set(src_files ${py_files} ${ui_files} ${json_files} ${extra_py_files}) + target_sources(${TARGET} PRIVATE ${src_files}) + source_group(cmake FILES CMakeLists.txt) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} + PREFIX src + FILES ${src_files}) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} + PREFIX data + FILES ${json_files}) + + set(install_dir "${MO2_INSTALL_BIN}/plugins") + + # .py files directly in src/ go to plugins/ + install(FILES ${py_files} DESTINATION ${install_dir}) + + # folders with Python files go into plugins/data + set(extra_py_dirs ${extra_py_files}) + list(TRANSFORM extra_py_dirs REPLACE "[/\\][^/\\]+" "") + list(REMOVE_DUPLICATES extra_py_dirs) + + install(DIRECTORY ${extra_py_dirs} + DESTINATION "${install_dir}/data" + FILES_MATCHING PATTERN "*.py") + + # JSON file go in plugins/data + install(FILES ${json_files} DESTINATION "${install_dir}/data") + + # generated files go in plugins/data + install( + DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/" + DESTINATION "${install_dir}/data" + FILES_MATCHING + PATTERN "*.py" + PATTERN "CMakeFiles" EXCLUDE + PATTERN "x64" EXCLUDE) + +endfunction() + +#! mo2_configure_python : configure a MO2 python target +# +# \param:MODULE indicates if this is a Python module plugin or a file plugin +# \param:TRANSLATIONS ON to generate translations (default), OFF to not generate them +# \param:LIB only for Python module, see mo2_configure_python_module +# \param:RES only for Python module, see mo2_configure_python_module +# +function(mo2_configure_python TARGET) + cmake_parse_arguments(MO2 "MODULE;SIMPLE" "TRANSLATIONS;LIB;RES" "" ${ARGN}) + + mo2_set_if_not_defined(MO2_TRANSLATIONS ON) + + if ((${MO2_MODULE} AND ${MO2_SIMPLE}) OR (NOT(${MO2_MODULE}) AND NOT(${MO2_SIMPLE}))) + message(FATAL_ERROR "mo2_configure_python should be called with either SIMPLE or MODULE") + endif() + + if (${MO2_MODULE}) + mo2_configure_python_module(${TARGET} ${ARGN}) + else() + mo2_configure_python_simple(${TARGET} ${ARGN}) + endif() + + # do this AFTER configure_ to properly handle the the ui files + if(${MO2_TRANSLATIONS}) + mo2_add_translations(${TARGET} SOURCES ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + + file(GLOB_RECURSE py_files CONFIGURE_DEPENDS *.py) + file(GLOB_RECURSE rc_files CONFIGURE_DEPENDS *.rc) + file(GLOB_RECURSE ui_files CONFIGURE_DEPENDS *.ui) + + target_sources(${TARGET} + PRIVATE ${py_files} ${ui_files} ${rc_files} ${qm_files}) + +endfunction() diff --git a/libs/cmake_common/mo2_utils.cmake b/libs/cmake_common/mo2_utils.cmake new file mode 100644 index 0000000..795ad36 --- /dev/null +++ b/libs/cmake_common/mo2_utils.cmake @@ -0,0 +1,464 @@ +# this file contains utility code that is not directly related to MO2 +cmake_minimum_required(VERSION 3.16) + +include(CMakeParseArguments) + +if (DEFINED MO2_UTILS_DEFINED) + return() +endif() + +#! mo2_set_if_not_defined : set a variable to the given value if not defined +# +# \param:NAME name of the variable +# \param:VALUE value of the variable to set (if not defined) +# +function (mo2_set_if_not_defined NAME VALUE) + if (NOT DEFINED ${NAME}) + set(${NAME} ${VALUE} PARENT_SCOPE) + endif() +endfunction() + + +#! mo2_add_subdirectories : add all repositories matching the given list of patterns +# +# \param:FOLDER Folder (layout) to add the subdirectories to +# \param:GLOB List of glob patterns to find repositories +# +function (mo2_add_subdirectories) + cmake_parse_arguments(MO2 "" "FOLDER" "GLOB" ${ARGN}) + + if (NOT DEFINED MO2_FOLDER) + message(FATAL_ERROR "missing FOLDER in add_subdirectories") + endif() + if (NOT DEFINED MO2_GLOB) + message(FATAL_ERROR "missing GLOB in add_subdirectories") + endif() + + file(GLOB directories RELATIVE ${CMAKE_CURRENT_LIST_DIR} LIST_DIRECTORIES TRUE ${MO2_GLOB}) + + set(CMAKE_FOLDER ${MO2_FOLDER}) + foreach(directory ${directories}) + add_subdirectory(${directory}) + endforeach() + unset(CMAKE_FOLDER) + +endfunction() + +#! mo2_find_python_executable : find the full path to the Python executable +# +# \param:VARNAME name of the variable that will contain the path to Python +function(mo2_find_python_executable VARNAME) + if (NOT DEFINED Python_EXECUTABLE) + find_package(Python ${MO2_PYTHON_VERSION} EXACT COMPONENTS Interpreter REQUIRED) + endif() + set(${VARNAME} ${Python_EXECUTABLE} PARENT_SCOPE) +endfunction() + +#! mo2_find_git_hash : find the git hash of HEAD on the current source project +# +# \param:VARNAME variable to store the git hash +function(mo2_find_git_hash VARNAME) + execute_process( + COMMAND git log -1 --format=%h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + set(${VARNAME} ${GIT_COMMIT_HASH} PARENT_SCOPE) +endfunction() + +#! mo2_find_qt_executable : find the path to the executable from Qt +# +function(mo2_find_qt_executable VARNAME EXECUTABLE) + + # retrieve the absolute path to qmake and then use that path to find + # the windeployqt and macdeployqt binaries + get_target_property(_qmake_executable Qt6::qmake IMPORTED_LOCATION) + get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY) + + # need to use a custom varname per executable to use the cache + find_program(QT_${EXECUTABLE} ${EXECUTABLE} HINTS "${_qt_bin_dir}") + if(WIN32 AND NOT QT_${EXECUTABLE}) + message(FATAL_ERROR "${EXECUTABLE} not found") + endif() + + set(${VARNAME} ${QT_${EXECUTABLE}} PARENT_SCOPE) +endfunction() + +#! mo2_set_project_to_run_from_install : set a target to run from a given executable +# +# this function is only meaningful for VS generator +# +# \param:TARGET name of the target +# \param:EXECUTABLE full path to the executable +# \param:WORKDIR working directory (optional, default is the directory of the executable) +# +function(mo2_set_project_to_run_from_install TARGET) + cmake_parse_arguments(MO2 "" "EXECUTABLE;WORKDIR" "" ${ARGN}) + + # extract directory + if (NOT DEFINED MO2_WORKDIR) + get_filename_component(MO2_WORKDIR ${MO2_EXECUTABLE} DIRECTORY) + endif() + + set_target_properties(${TARGET} PROPERTIES + VS_DEBUGGER_WORKING_DIRECTORY "${MO2_WORKDIR}" + VS_DEBUGGER_COMMAND "${MO2_EXECUTABLE}") +endfunction() + +#! mo2_required_variable : check that a variable is defined, fails otherwise +# +# this function checks that a variable with the given NAME is defined, if it's not +# it fails with a FATAL_ERROR, otherwise it caches the variable +# +# \param:NAME name of the variable +# \param:TYPE type of the variable (optional) +# \param:DESC description of the variable (optional) +# +macro(mo2_required_variable) + cmake_parse_arguments(REQ_VAR "" "NAME;TYPE;DESC" "" ${ARGN}) + + if(NOT DEFINED REQ_VAR_DESC) + set(REQ_VAR_DESC "${name}") + endif() + + if(NOT DEFINED ${REQ_VAR_NAME}) + message(FATAL_ERROR "${REQ_VAR_NAME} is not defined") + endif() + + set(${REQ_VAR_NAME} ${${REQ_VAR_NAME}} CACHE ${REQ_VAR_TYPE} "${REQ_VAR_DESC}") +endmacro() + +#! mo2_add_filter : add source_group based on the given names +# +# \param:NAME name of the group +# \param:FILES files to add +# \param:GROUPS basename to add, e.g., "foo" will add "foo.cpp", "foo.h" and "foo.inc" +# +function(mo2_add_filter) + cmake_parse_arguments(PARSE_ARGV 0 add_filter "" "NAME" "FILES;GROUPS") + + set(files ${add_filter_FILES}) + + foreach(f ${add_filter_GROUPS}) + set(files ${files} ${f}.cpp ${f}.h ${f}.inc) + endforeach() + + string(REPLACE "/" "\\" filter_name ${add_filter_NAME}) + source_group(${filter_name} FILES ${files}) +endfunction() + +#! mo2_deploy_qt_for_tests : add comments to deploy Qt for tests +# +# unlike mo2_deploy_qt(), this function does not perform any cleaning +# +# \param:TARGET name of the target to deploy for +# \param:BINARIES names of the binaries to deploy from +# +function(mo2_deploy_qt_for_tests) + if(NOT WIN32) + return() + endif() + + cmake_parse_arguments(DEPLOY "" "TARGET" "BINARIES" ${ARGN}) + + mo2_find_qt_executable(windeployqt windeployqt) + + add_custom_command(TARGET "${DEPLOY_TARGET}" + POST_BUILD + COMMAND ${windeployqt} + ARGS + --dir $<TARGET_FILE_DIR:${DEPLOY_TARGET}> + --no-translations + --verbose 0 + --no-compiler-runtime + "$<TARGET_FILE:${DEPLOY_TARGET}>" + "${DEPLOY_BINARIES}" + VERBATIM + COMMAND_EXPAND_LISTS + WORKING_DIRECTORY $<TARGET_FILE_DIR:${DEPLOY_TARGET}> + ) +endfunction() + +#! mo2_deploy_qt : add commands to deploy Qt from the given binaries +# +# this function attach install() entries that deploy Qt for the given binaries +# +# \param:NOPLUGINS do not deploy Qt plugins +# \param:DIRECTORY directory, relative to CMAKE_INSTALL_PREFIX, to deploy to, default +# to ${MO2_INSTALL_BIN} +# \param:BINARIES names of the binaries (in the install path) to deploy from +# +function(mo2_deploy_qt) + if(NOT WIN32) + return() + endif() + + cmake_parse_arguments(DEPLOY "NOPLUGINS" "DIRECTORY" "BINARIES" ${ARGN}) + + mo2_set_if_not_defined(DEPLOY_DIRECTORY "${MO2_INSTALL_BIN}") + + mo2_find_qt_executable(windeployqt windeployqt) + + set(args + --no-translations + --verbose 0 + --webenginewidgets + --websockets + --openglwidgets + --libdir dlls + --no-compiler-runtime) + + if(MO2_QT_VERSION VERSION_LESS "6.10.0") + list(APPEND args --networkauth) + endif() + + if(${DEPLOY_NOPLUGINS}) + list(APPEND args --no-plugins) + else() + list(APPEND args --plugindir qtplugins) + endif() + + string(REPLACE ";" " " deploy_qt_args "${args}") + + set(bin "${CMAKE_INSTALL_PREFIX}/${DEPLOY_DIRECTORY}") + + set(deploys "") + foreach(binary ${DEPLOY_BINARIES}) + string(APPEND deploys " + execute_process( + COMMAND \"${windeployqt}\" ${deploy_qt_args} \"${binary}\" + WORKING_DIRECTORY \"${bin}\")") + endforeach() + + install(CODE " + ${deploys} + + file(REMOVE_RECURSE \"${bin}/platforms\") + file(REMOVE_RECURSE \"${bin}/styles\") + file(REMOVE_RECURSE \"${bin}/dlls/imageformats\") + file(REMOVE_RECURSE \"${bin}/dlls/tls\") + ") + + # === Begin Qt6 Fix === + + # until there is a cleaner way to do this with windqtdeploy? + + # subfolder of QtQuick + set(qt6_qtquick_to_remove "") + list(APPEND qt6_qtquick_to_remove + Controls Dialogs Layouts LocalStorage NativeStyle Particles Pdf Scene2D + Scene3D Shapes Templates Timeline tooling VirtualKeyboard Window) + + set(qt6_qtdlls_to_remove "") + list(APPEND qt6_qtdlls_to_remove + 3DAnimation 3DCore 3DExtras 3DInput 3DLogic 3DQuickScene2D 3DRender + Pdf PdfQuick QmlLocalStorage QmlXmlListModel QuickControls2 QuickControls2Impl + QuickDialogs2 QuickDialogs2QuickImpl QuickDialogs2Utils QuickLayouts QuickParticles + QuickShapes QuickTemplates2 QuickTimeline Sql StateMachine StateMachineQml VirtualKeyboard) + + set(removals "") + foreach (qt6_qtquick_removal ${qt6_qtquick_to_remove}) + string(APPEND removals " + file(REMOVE_RECURSE \"${bin}/QtQuick/${qt6_qtquick_removal}\")") + endforeach() + foreach (qt6_dll_removal ${qt6_qtdlls_to_remove}) + string(APPEND removals " + file(REMOVE \"${bin}/dlls/Qt6${qt6_dll_removal}.dll\")") + endforeach() + install(CODE "${removals}") + + # === End Qt6 Fix === + + if(NOT ${DEPLOY_NOPLUGINS}) + set(qtwebengine_process_exe $<IF:$<CONFIG:Debug>,QtWebEngineProcessd.exe,QtWebEngineProcess.exe>) + install(CODE " + if(EXISTS \"${bin}/dlls/${qtwebengine_process_exe}\") + message(STATUS \"[MO2] Moving ${qtwebengine_process_exe} to root...\") + file(RENAME \"${bin}/dlls/${qtwebengine_process_exe}\" \"${bin}/${qtwebengine_process_exe}\") + endif() + + foreach(_dir platforms styles) + if(EXISTS \"${bin}/qtplugins/\${_dir}\") + file(REMOVE_RECURSE \"${bin}/\${_dir}\") + file(RENAME \"${bin}/qtplugins/\${_dir}\" \"${bin}/\${_dir}\") + endif() + endforeach() + + foreach(_dir imageformats tls) + if(EXISTS \"${bin}/qtplugins/\${_dir}\") + file(REMOVE_RECURSE \"${bin}/dlls/\${_dir}\") + file(RENAME \"${bin}/qtplugins/\${_dir}\" \"${bin}/dlls/\${_dir}\") + endif() + endforeach() + + file(REMOVE_RECURSE \"${bin}/qtplugins\") + ") + endif() +endfunction() + +#! mo2_add_lupdate : generate .ts files from the given sources +# +# this function adds a ${TARGET}_lupdate target +# +# \param:TARGET target to generate lupdate for +# \param:TS_FILE .ts file to generate +# \param:SOURCES source folders to generate .ts file from +# +function(mo2_add_lupdate TARGET) + cmake_parse_arguments(MO2 "" "TS_FILE" "SOURCES" ${ARGN}) + + set(translation_files "") + set(ui_files "") + + get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES) + + if ("CXX" IN_LIST languages) + set(is_cpp True) + else() + set(is_cpp False) + endif() + + # we glob the source files for Python plugins to avoid duplicate string in .ui and + # .py, for C++, we will directly use MO2_SOURCES (using translation_files broke + # the modorganizer build, probably because there are too many sources?) + foreach (SOURCE ${MO2_SOURCES}) + if (${is_cpp}) + file(GLOB_RECURSE source_sources CONFIGURE_DEPENDS + ${SOURCE}/*.cpp + ${SOURCE}/*.h) + else() + file(GLOB_RECURSE source_sources CONFIGURE_DEPENDS + ${SOURCE}/*.py) + endif() + + # ui files + file(GLOB_RECURSE source_ui_files CONFIGURE_DEPENDS ${SOURCE}/*.ui) + + list(APPEND ui_files ${source_ui_files}) + list(APPEND translation_files ${source_sources} ${source_ui_files}) + endforeach() + + # for Python, we need to remove the .py generated because these can be generated + # in the source folder + list(TRANSFORM ui_files REPLACE "[.]ui$" ".py") + list(REMOVE_ITEM translation_files ${ui_files}) + + message(TRACE "TS_FILE: ${MO2_TS_FILE}, SOURCES: ${MO2_SOURCES}, FILES: ${translation_files}") + + add_custom_target("${TARGET}_lupdate" DEPENDS ${MO2_TS_FILE}) + + if (${is_cpp}) + mo2_find_qt_executable(lupdate lupdate) + set(lupdate_command ${lupdate} ${MO2_SOURCES} -ts ${MO2_TS_FILE}) + else() + mo2_python_install_pyqt() + set(lupdate_command + ${CMAKE_COMMAND} + -E env PYTHONPATH=${MO2_PYLIBS_DIR} + ${MO2_PYLIBS_DIR}/bin/pylupdate${MO2_QT_VERSION_MAJOR}.exe + --ts "${MO2_TS_FILE}" ${translation_files}) + + add_dependencies("${TARGET}_lupdate" PyQt6) + endif() + + add_custom_command(OUTPUT ${MO2_TS_FILE} + COMMAND ${lupdate_command} + DEPENDS ${translation_files} + VERBATIM) + + # we need to set this property otherwise there is an issue with C# projects + # requiring nuget packages (e.g., installer_omod) that tries to resolve Nuget + # packages on these target but fails because there are obviously none + # + # we also "hide" the target by moving them to autogen + set_target_properties(${TARGET}_lupdate PROPERTIES + VS_GLOBAL_ResolveNugetPackages False + FOLDER autogen) + +endfunction() + +#! mo2_add_lrelease : generate .ts files from the given sources +# +# this function adds a ${TARGET}_lrelease target +# +# \param:TARGET target to generate releases for +# \param:INSTALL if set, QM files will be installed +# \param:DIRECTORY if INSTALL is set, path where translations should be installed, +# default to ${MO2_INSTALL_BIN}/translations +# \param:QM_FILE .qm file to generate +# \param:TS_FILES source ts +# +function(mo2_add_lrelease TARGET) + cmake_parse_arguments(MO2 "INSTALL" "DIRECTORY;QM_FILE" "TS_FILES" ${ARGN}) + + mo2_set_if_not_defined(MO2_DIRECTORY "${MO2_INSTALL_BIN}/translations") + + mo2_find_qt_executable(lrelease_command lrelease) + + add_custom_command(OUTPUT ${MO2_QM_FILE} + COMMAND ${lrelease_command} + ARGS ${MO2_TS_FILES} -qm ${MO2_QM_FILE} + DEPENDS "${MO2_TS_FILES}" + VERBATIM) + + add_custom_target("${TARGET}_lrelease" DEPENDS ${MO2_QM_FILE}) + + # we need to set this property otherwise there is an issue with C# projects + # requiring nuget packages (e.g., installer_omod) that tries to resolve Nuget + # packages on these target but fails because there are obviously none + # + # we also "hide" the target by moving them to autogen + set_target_properties(${TARGET}_lrelease PROPERTIES + VS_GLOBAL_ResolveNugetPackages False + FOLDER autogen) + + if (${MO2_INSTALL}) + install(FILES ${MO2_QM_FILE} DESTINATION ${MO2_DIRECTORY}) + endif() + +endfunction() + + +#! mo2_add_translations : generate translation files +# +# this function is a wrapper around mo2_add_lupdate and mo2_add_lrelease +# +# \param:TARGET target to generate translations for +# \param:RELEASE if set, will use mo2_add_lrelease to generate .qm files +# \param:INSTALL_RELEASE if true, will install generated .qm files (force RELEASE) +# \param:INSTALL_DIRECTORY installation directory for .qm files, default to ${MO2_INSTALL_BIN}/translations +# \param:TS_FILE intermediate .ts file to generate +# \param:QM_FILE file .qm file to generate if RELEASE or INSTALL_RELEASE is set +# \param:SOURCES source directories to look for translations, send to mo2_add_lupdate +# +function(mo2_add_translations TARGET) + cmake_parse_arguments(MO2 "RELEASE;INSTALL_RELEASE" "TS_FILE;QM_FILE;INSTALL_DIRECTORY" "SOURCES" ${ARGN}) + + mo2_set_if_not_defined(MO2_TS_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${TARGET}_en.ts) + mo2_set_if_not_defined(MO2_QM_FILE ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_en.qm) + mo2_set_if_not_defined(MO2_INSTALL_DIRECTORY "${MO2_INSTALL_BIN}/translations") + + # force release with install + if (${MO2_INSTALL_RELEASE}) + set(MO2_RELEASE True) + endif() + + mo2_add_lupdate(${TARGET} TS_FILE ${MO2_TS_FILE} SOURCES ${MO2_SOURCES}) + + if (${MO2_RELEASE}) + mo2_add_lrelease(${TARGET} + INSTALL ${MO2_INSTALL_RELEASE} + DIRECTORY ${MO2_INSTALL_DIRECTORY} + TS_FILES ${MO2_TS_FILE} + QM_FILE ${MO2_QM_FILE}) + + add_dependencies(${TARGET}_lrelease ${TARGET}_lupdate) + add_dependencies(${TARGET} ${TARGET}_lrelease) + else() + add_dependencies(${TARGET} ${TARGET}_lupdate) + endif() + +endfunction() + +set(MO2_UTILS_DEFINED TRUE) diff --git a/libs/cmake_common/mo2_versions.cmake b/libs/cmake_common/mo2_versions.cmake new file mode 100644 index 0000000..ee432f4 --- /dev/null +++ b/libs/cmake_common/mo2_versions.cmake @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.22) + +if (DEFINED MO2_VERSIONS_INCLUDED) + return() +endif() + +# define MO2_QT_VERSION and related variables +# - if MO2_QT_VERSION is already defined, simply extract the major, minor and patch +# components for +# - otherwise, if the project is a C++ project, look-up Qt and use the version from +# the package found +# - otherwise, or if Qt was not found in the previous step, use a default version +# +if (NOT DEFINED MO2_QT_VERSION) + if (CMAKE_CXX_COMPILER_LOADED) + find_package(Qt6 CONFIG QUIET) + endif() + + if (Qt6_FOUND) + set(MO2_QT_VERSION "${Qt6_VERSION}") + else() + set(MO2_QT_VERSION "6.7.3") + endif() +endif() + +string(REPLACE "." ";" MO2_QT_VERSION_LIST ${MO2_QT_VERSION}) +list(GET MO2_QT_VERSION_LIST 0 MO2_QT_VERSION_MAJOR) +list(GET MO2_QT_VERSION_LIST 1 MO2_QT_VERSION_MINOR) +list(GET MO2_QT_VERSION_LIST 2 MO2_QT_VERSION_PATCH) +unset(MO2_QT_VERSION_LIST) + +message(STATUS "[MO2] Qt version: ${MO2_QT_VERSION} (${MO2_QT_VERSION_MAJOR}, ${MO2_QT_VERSION_MINOR}, ${MO2_QT_VERSION_PATCH})") + +mo2_set_if_not_defined(MO2_PYTHON_VERSION "3.12") + +if (MO2_QT_VERSION_MAJOR EQUAL 6 AND MO2_QT_VERSION_MINOR EQUAL 10) + mo2_set_if_not_defined(MO2_PYQT_VERSION "6.10.2") + mo2_set_if_not_defined(MO2_SIP_VERSION "6.15.1") +else() + mo2_set_if_not_defined(MO2_PYQT_VERSION "6.7.1") + mo2_set_if_not_defined(MO2_SIP_VERSION "6.8.6") +endif() + +message(STATUS "[MO2] Python version: ${MO2_PYTHON_VERSION}") +message(STATUS "[MO2] PyQt version: ${MO2_PYQT_VERSION}") +message(STATUS "[MO2] SIP version: ${MO2_SIP_VERSION}") + +# mark as included +set(MO2_VERSIONS_INCLUDED TRUE) |
