aboutsummaryrefslogtreecommitdiff
path: root/libs/cmake_common/mo2_utils.cmake
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/mo2_utils.cmake
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/mo2_utils.cmake')
-rw-r--r--libs/cmake_common/mo2_utils.cmake464
1 files changed, 464 insertions, 0 deletions
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)