aboutsummaryrefslogtreecommitdiff
path: root/libs/cmake_common
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
commit7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch)
tree27fb39be241fdb5ac2734c574de678977d1856d0 /libs/cmake_common
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem, Proton/umu-run integration, and Flatpak packaging. Key features: - FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak) - Proton/GE-Proton/umu-run launcher with env var forwarding - Flatpak support (sandbox-aware VFS, NXM handler, umu-run) - Wine prefix management UI - Case-insensitive path resolution for Linux filesystems - QSettings-safe INI handling (avoids Bethesda INI corruption) - Portable instance support with auto-generated launcher scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/cmake_common')
-rw-r--r--libs/cmake_common/.pre-commit-config.yaml15
-rw-r--r--libs/cmake_common/README.md109
-rw-r--r--libs/cmake_common/mo2-cmake-config.cmake3
-rw-r--r--libs/cmake_common/mo2.cmake48
-rw-r--r--libs/cmake_common/mo2_cpp.cmake358
-rw-r--r--libs/cmake_common/mo2_python.cmake356
-rw-r--r--libs/cmake_common/mo2_utils.cmake464
-rw-r--r--libs/cmake_common/mo2_versions.cmake49
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)