deploy qt and its dependencies with cpm/cmake

This commit is contained in:
cheezwiz7899
2026-05-07 16:41:59 +12:00
committed by Ploo
parent b1ac919b9f
commit 8cddd542b6
10 changed files with 583 additions and 96 deletions
+24 -56
View File
@@ -89,11 +89,21 @@ CMAKE_DEPENDENT_OPTION(CITRON_ROOM "Compile LDN room server" ON "NOT ANDROID" OF
CMAKE_DEPENDENT_OPTION(CITRON_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF)
# CITRON_USE_CPM is the primary way to fetch dependencies on Linux and for Clangtron.
# vcpkg is supported primarily for native MSVC and Android builds.
option(CITRON_USE_BUNDLED_VCPKG "Use vcpkg for citron dependencies" "${MSVC}")
option(CITRON_USE_CPM "Use CPM.cmake to fetch dependencies" OFF)
option(CITRON_CHECK_SUBMODULES "Check if submodules are present" ON)
# Auto-enable CPM for Linux/MinGW if not using vcpkg
if (NOT CITRON_USE_BUNDLED_VCPKG AND (UNIX OR MINGW))
set(CITRON_USE_CPM ON CACHE BOOL "" FORCE)
endif()
option(CITRON_USE_CPM "Use CPM.cmake to fetch dependencies instead of git submodules (Clangtron builds)" OFF)
if (CITRON_USE_CPM)
set(CITRON_CHECK_SUBMODULES OFF CACHE BOOL "" FORCE)
else()
option(CITRON_CHECK_SUBMODULES "Check if submodules are present" ON)
endif()
option(CITRON_ENABLE_LTO "Enable link-time optimization" OFF)
@@ -113,52 +123,6 @@ if (ANDROID OR WIN32 OR APPLE)
endif()
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
if (ANDROID AND CITRON_DOWNLOAD_ANDROID_VVL)
set(vvl_version "vulkan-sdk-1.4.328.1")
set(vvl_version_plain "1.4.328.1")
set(vvl_zip_file "${CMAKE_BINARY_DIR}/externals/vvl-android.zip")
set(vvl_extract_dir "${CMAKE_BINARY_DIR}/externals")
if (NOT EXISTS "${vvl_zip_file}")
# Download and extract validation layer release to externals directory
set(vvl_base_url "https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download")
file(DOWNLOAD "${vvl_base_url}/${vvl_version}/android-binaries-${vvl_version_plain}.zip"
"${vvl_zip_file}" SHOW_PROGRESS)
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${vvl_zip_file}"
WORKING_DIRECTORY "${vvl_extract_dir}")
endif()
# Copy the arm64 binary to src/android/app/main/jniLibs
# New structure: android-binaries-X.X.X.X/android-binaries-X.X.X.X/arm64-v8a/libVkLayer_khronos_validation.so
set(vvl_lib_path "${CMAKE_CURRENT_SOURCE_DIR}/src/android/app/src/main/jniLibs/arm64-v8a/")
set(vvl_source_file "${vvl_extract_dir}/android-binaries-${vvl_version_plain}/android-binaries-${vvl_version_plain}/arm64-v8a/libVkLayer_khronos_validation.so")
if (EXISTS "${vvl_source_file}")
file(COPY "${vvl_source_file}" DESTINATION "${vvl_lib_path}")
message(STATUS "Successfully copied VVL ${vvl_version_plain} to ${vvl_lib_path}")
else()
message(WARNING "Could not find Vulkan Validation Layer at: ${vvl_source_file}")
message(STATUS "Please manually download from: ${vvl_base_url}/${vvl_version}/android-binaries-${vvl_version_plain}.zip")
endif()
endif()
# EXPERIMENTAL LIBC++
if ((ANDROID OR APPLE OR UNIX) AND (NOT (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") OR ANDROID) AND NOT WIN32)
if(CXX_APPLE OR CXX_CLANG)
# libc++ has stop_token and jthread as experimental
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fexperimental-library")
else()
# Uses glibc, mostly?
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LIBCPP_ENABLE_EXPERIMENTAL=1")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_LIBCPP_ENABLE_EXPERIMENTAL=1")
endif()
endif()
if (ANDROID)
set(CMAKE_SKIP_INSTALL_RULES ON)
endif()
if (CITRON_USE_BUNDLED_VCPKG)
if (ANDROID)
set(ENV{ANDROID_NDK_HOME} "${ANDROID_NDK}")
@@ -166,8 +130,6 @@ if (CITRON_USE_BUNDLED_VCPKG)
if (CMAKE_ANDROID_ARCH_ABI STREQUAL "arm64-v8a")
set(VCPKG_TARGET_TRIPLET "arm64-android")
# this is to avoid CMake using the host pkg-config to find the host
# libraries when building for Android targets
set(PKG_CONFIG_EXECUTABLE "aarch64-none-linux-android-pkg-config" CACHE FILEPATH "" FORCE)
elseif (CMAKE_ANDROID_ARCH_ABI STREQUAL "x86_64")
set(VCPKG_TARGET_TRIPLET "x64-android")
@@ -185,9 +147,7 @@ if (CITRON_USE_BUNDLED_VCPKG)
if (NOT EXISTS ${NASM_DESTINATION_PATH})
file(DOWNLOAD ${NASM_DOWNLOAD_URL} ${NASM_DESTINATION_PATH} SHOW_PROGRESS STATUS NASM_STATUS)
if (NOT NASM_STATUS EQUAL 0)
# Warn and not fail since vcpkg is supposed to download this package for us in the first place
message(WARNING "External nasm vcpkg package download from ${NASM_DOWNLOAD_URL} failed with status ${NASM_STATUS}")
endif()
endif()
@@ -199,17 +159,15 @@ if (CITRON_USE_BUNDLED_VCPKG)
if (ENABLE_WEB_SERVICE)
list(APPEND VCPKG_MANIFEST_FEATURES "web-service")
endif()
if (ANDROID)
list(APPEND VCPKG_MANIFEST_FEATURES "android")
endif()
include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
# Disable manifest mode (use vcpkg classic mode) when using a custom vcpkg installation
option(VCPKG_MANIFEST_MODE "")
include("$ENV{VCPKG_TOOLCHAIN_FILE}")
endif()
option(CITRON_USE_PRECOMPILED_HEADERS "Use Precompiled Headers" ON)
if (CITRON_USE_PRECOMPILED_HEADERS)
if (MSVC AND CCACHE)
# buildcache does not properly cache PCH files, leading to compilation errors.
@@ -345,6 +303,16 @@ endif()
# Output binaries to bin/
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
if (CITRON_USE_CPM AND CITRON_USE_BUNDLED_QT AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
# Build-tree binaries are the portable deliverable for local Linux CPM builds.
# Keep their loader search path relative to bin/ instead of embedding Qt or
# dependency cache directories from the build host.
set(CMAKE_BUILD_RPATH "$ORIGIN/lib")
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
add_link_options("-Wl,--disable-new-dtags")
endif()
# System imported libraries
# =======================================================================
+146 -24
View File
@@ -67,44 +67,166 @@ function(copy_citron_Qt6_deps target_dir)
qschannelbackend$<$<CONFIG:Debug>:d>.*
qopensslbackend$<$<CONFIG:Debug>:d>.*
)
elseif(MINGW)
# For MinGW builds with CITRON_USE_BUNDLED_QT=ON the bundled Qt tree is
# at build/externals/qt/<ver>/mingw_64/. CopyMinGWDeps.cmake handles the
# runtime DLLs; this function handles the plugin subdirectories that
# windeployqt would normally deploy but which are not on the DLL search path.
elseif(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
# For MinGW and Native Linux builds with CITRON_USE_BUNDLED_QT=ON,
# copy the plugins and libraries to subdirectories to keep bin/ clean.
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LIB_DEST "${DLL_DEST}lib/")
set(PLUGINS_DEST "${DLL_DEST}plugins/")
set(USE_LIB_FOLDER ON)
else()
# Windows/MinGW: DLLs must be next to the EXE
set(LIB_DEST "${DLL_DEST}")
set(PLUGINS_DEST "${DLL_DEST}")
set(USE_LIB_FOLDER OFF)
endif()
# Resolve the plugin root from Qt6_DIR (which points to lib/cmake/Qt6).
set(Qt6_BUNDLED_PLUGINS "${Qt6_DIR}/../../../plugins")
foreach(plugin_dir platforms styles imageformats iconengines)
foreach(plugin_dir platforms styles imageformats iconengines tls wayland-graphics-integration wayland-shell-integration)
if (EXISTS "${Qt6_BUNDLED_PLUGINS}/${plugin_dir}")
add_custom_command(TARGET ${target_dir} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${DLL_DEST}${plugin_dir}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${PLUGINS_DEST}${plugin_dir}"
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${Qt6_BUNDLED_PLUGINS}/${plugin_dir}"
"${DLL_DEST}${plugin_dir}"
COMMENT "Copying Qt6 ${plugin_dir} plugins (MinGW bundled)"
"${PLUGINS_DEST}${plugin_dir}"
COMMENT "Bundling Qt6 ${plugin_dir} plugins"
)
endif()
endforeach()
# TLS plugins — critical for HTTPS/SSL.
if (EXISTS "${Qt6_BUNDLED_PLUGINS}/tls")
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
# For Linux, also copy the shared libraries.
set(_qt_libs Core Gui Widgets Network Svg OpenGL DBus WaylandClient WaylandEglClient WaylandEglClientHwIntegration XcbQpa)
if (CITRON_USE_QT_MULTIMEDIA)
list(APPEND _qt_libs Multimedia MultimediaWidgets)
endif()
foreach(_lib ${_qt_libs})
if (EXISTS "${Qt6_DLL_DIR}libQt6${_lib}.so.6")
add_custom_command(TARGET ${target_dir} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${LIB_DEST}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${Qt6_DLL_DIR}libQt6${_lib}.so.6"
"${LIB_DEST}"
COMMENT "Bundling libQt6${_lib}.so.6"
)
endif()
endforeach()
find_program(PATCHELF_EXE patchelf)
if (NOT PATCHELF_EXE)
message(FATAL_ERROR
"patchelf is required for portable Linux CPM bundles. "
"Run ./build-citron-linux.sh setup or install patchelf with your package manager.")
endif()
# Bundle ICU libraries (required by QtCore)
if (DEFINED ICU_BINARY_DIR)
# If we built it ourselves, we know what we expect.
# Note: We can't use GLOB at configuration time if it's a fresh build.
set(_icu_libs data i18n io test tu uc)
foreach(_icu_comp ${_icu_libs})
set(_icu_lib "${ICU_BINARY_DIR}/libicu${_icu_comp}.so.73")
add_custom_command(TARGET ${target_dir} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${LIB_DEST}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_icu_lib}" "${LIB_DEST}"
COMMENT "Bundling ICU library: ${_icu_lib}"
)
endforeach()
else()
file(GLOB _icu_libs "${Qt6_DLL_DIR}libicu*.so.[0-9]*")
foreach(_icu_lib ${_icu_libs})
add_custom_command(TARGET ${target_dir} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${LIB_DEST}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_icu_lib}" "${LIB_DEST}"
COMMENT "Bundling ICU library: ${_icu_lib}"
)
endforeach()
endif()
# Bundle additional XCB support libraries
set(_xcb_deps
xcb.so.1 Xau.so.6 xcb-xkb.so.1
xcb-cursor.so.0 xcb-icccm.so.4 xcb-image.so.0 xcb-keysyms.so.1
xcb-util.so.1
xcb-render-util.so.0 xcb-xinerama.so.0 xcb-xinput.so.0
xcb-shm.so.0 xcb-render.so.0 xcb-randr.so.0 xcb-shape.so.0
xcb-xfixes.so.0 xcb-sync.so.1 xcb-dri3.so.0
# Additional system libs needed by Qt XCB plugin
xkbcommon.so.0 xkbcommon-x11.so.0 X11-xcb.so.1 Xdmcp.so.6
)
if (DEFINED XCB_BINARY_DIR)
set(_xcb_search_dir "${XCB_BINARY_DIR}/")
else()
set(_xcb_search_dir "SYSTEM")
endif()
foreach(_xcb_lib ${_xcb_deps})
if (_xcb_search_dir STREQUAL "SYSTEM" OR _xcb_lib MATCHES "xkbcommon|X11-xcb")
execute_process(COMMAND ${CMAKE_C_COMPILER} -print-file-name=lib${_xcb_lib} OUTPUT_VARIABLE _LIB_PATH OUTPUT_STRIP_TRAILING_WHITESPACE)
if (NOT EXISTS "${_LIB_PATH}")
# Fallback for some systems where print-file-name doesn't work well
set(_LIB_PATH "/usr/lib/x86_64-linux-gnu/lib${_xcb_lib}")
endif()
else()
set(_LIB_PATH "${_xcb_search_dir}lib${_xcb_lib}")
endif()
# Always add the bundling command for built libs, or if system lib exists
add_custom_command(TARGET ${target_dir} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${LIB_DEST}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_LIB_PATH}" "${LIB_DEST}"
COMMENT "Bundling XCB support library: ${_xcb_lib}"
)
endforeach()
# Bundle libstdc++ and libgcc_s
execute_process(COMMAND ${CMAKE_C_COMPILER} -print-file-name=libstdc++.so.6 OUTPUT_VARIABLE _STDCXX_PATH OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${CMAKE_C_COMPILER} -print-file-name=libgcc_s.so.1 OUTPUT_VARIABLE _GCC_S_PATH OUTPUT_STRIP_TRAILING_WHITESPACE)
if (EXISTS "${_STDCXX_PATH}")
add_custom_command(TARGET ${target_dir} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${LIB_DEST}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_STDCXX_PATH}" "${LIB_DEST}"
COMMENT "Bundling libstdc++.so.6"
)
endif()
if (EXISTS "${_GCC_S_PATH}")
add_custom_command(TARGET ${target_dir} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${LIB_DEST}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_GCC_S_PATH}" "${LIB_DEST}"
COMMENT "Bundling libgcc_s.so.1"
)
endif()
set(_runtime_dependency_dirs "${LIB_DEST}" "${Qt6_DLL_DIR}")
if (DEFINED ICU_BINARY_DIR)
list(APPEND _runtime_dependency_dirs "${ICU_BINARY_DIR}")
endif()
if (DEFINED XCB_BINARY_DIR)
list(APPEND _runtime_dependency_dirs "${XCB_BINARY_DIR}")
endif()
list(REMOVE_DUPLICATES _runtime_dependency_dirs)
string(REPLACE ";" "|" _runtime_dependency_dirs_arg "${_runtime_dependency_dirs}")
add_custom_command(TARGET ${target_dir} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${TLS}"
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${Qt6_BUNDLED_PLUGINS}/tls"
"${TLS}"
COMMENT "Copying Qt6 TLS plugins (MinGW bundled)"
COMMAND ${CMAKE_COMMAND}
-DBUNDLE_BIN_DIR="${DLL_DEST}"
-DPATCHELF_EXE="${PATCHELF_EXE}"
-DRUNTIME_DEPENDENCY_DIRS="${_runtime_dependency_dirs_arg}"
-P "${CMAKE_SOURCE_DIR}/CMakeModules/FixLinuxBundleRpaths.cmake"
COMMENT "Copying Linux runtime dependencies and normalizing RPATHs"
)
else()
message(WARNING "Qt6 TLS plugin directory not found at ${Qt6_BUNDLED_PLUGINS}/tls")
message(WARNING "SSL/HTTPS will not work in this build.")
endif()
endif()
# Create an empty qt.conf so Qt locates its plugins relative to the executable.
add_custom_command(TARGET citron POST_BUILD
COMMAND ${CMAKE_COMMAND} -E touch ${DLL_DEST}qt.conf
)
# Create a qt.conf next to the executable.
file(MAKE_DIRECTORY "${DLL_DEST}")
if (USE_LIB_FOLDER)
file(WRITE "${DLL_DEST}qt.conf" "[Paths]\nPlugins = plugins\n")
else()
file(WRITE "${DLL_DEST}qt.conf" "")
endif()
endfunction(copy_citron_Qt6_deps)
+152
View File
@@ -0,0 +1,152 @@
# SPDX-FileCopyrightText: 2026 citron Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
if (NOT DEFINED BUNDLE_BIN_DIR)
message(FATAL_ERROR "BUNDLE_BIN_DIR is required")
endif()
if (NOT DEFINED PATCHELF_EXE OR NOT EXISTS "${PATCHELF_EXE}")
message(FATAL_ERROR "patchelf is required to make the Linux bundle portable")
endif()
set(_bin_dir "${BUNDLE_BIN_DIR}")
set(_lib_dir "${_bin_dir}/lib")
set(_plugins_dir "${_bin_dir}/plugins")
file(MAKE_DIRECTORY "${_lib_dir}")
if (DEFINED RUNTIME_DEPENDENCY_DIRS)
string(REPLACE "|" ";" _runtime_dependency_dirs "${RUNTIME_DEPENDENCY_DIRS}")
endif()
list(APPEND _runtime_dependency_dirs "${_lib_dir}")
list(REMOVE_DUPLICATES _runtime_dependency_dirs)
foreach(_candidate citron citron-cmd citron-room shader_tool)
if (EXISTS "${_bin_dir}/${_candidate}")
list(APPEND _executables "${_bin_dir}/${_candidate}")
endif()
endforeach()
file(GLOB _bundled_libraries "${_lib_dir}/*.so" "${_lib_dir}/*.so.*")
if (EXISTS "${_plugins_dir}")
file(GLOB_RECURSE _bundled_modules "${_plugins_dir}/*.so")
endif()
if (_executables OR _bundled_libraries OR _bundled_modules)
file(GET_RUNTIME_DEPENDENCIES
RESOLVED_DEPENDENCIES_VAR _resolved_dependencies
UNRESOLVED_DEPENDENCIES_VAR _unresolved_dependencies
CONFLICTING_DEPENDENCIES_PREFIX _conflicting
EXECUTABLES ${_executables}
LIBRARIES ${_bundled_libraries}
MODULES ${_bundled_modules}
DIRECTORIES ${_runtime_dependency_dirs}
PRE_EXCLUDE_REGEXES
[[^libgcc_s\.so\..*]]
[[^libstdc\+\+\.so\..*]]
[[^libglapi\.so\..*]]
[[^libGL\.so\..*]]
[[^libEGL\.so\..*]]
[[^libvulkan\.so\..*]]
[[^libXau\.so\..*]]
[[^libXdmcp\.so\..*]]
[[^libxcb\.so\..*]]
POST_EXCLUDE_REGEXES
[[/ld-linux[^/]*\.so(\..*)?$]]
[[/libc\.so(\..*)?$]]
[[/libm\.so(\..*)?$]]
[[/libdl\.so(\..*)?$]]
[[/libpthread\.so(\..*)?$]]
[[/librt\.so(\..*)?$]]
[[/libresolv\.so(\..*)?$]]
[[/libutil\.so(\..*)?$]]
[[/libnss_[^/]*\.so(\..*)?$]]
[[/libanl\.so(\..*)?$]]
[[/libgcc_s\.so(\..*)?$]]
[[/libstdc\+\+\.so(\..*)?$]]
)
endif()
foreach(_dependency ${_resolved_dependencies})
get_filename_component(_dependency_name "${_dependency}" NAME)
if (NOT EXISTS "${_lib_dir}/${_dependency_name}")
file(COPY "${_dependency}" DESTINATION "${_lib_dir}" FOLLOW_SYMLINK_CHAIN)
endif()
endforeach()
if (_unresolved_dependencies)
list(REMOVE_DUPLICATES _unresolved_dependencies)
message(WARNING "Unresolved Linux bundle dependencies: ${_unresolved_dependencies}")
endif()
function(_citron_is_elf output_var file_path)
execute_process(
COMMAND readelf -h "${file_path}"
RESULT_VARIABLE _readelf_result
OUTPUT_QUIET
ERROR_QUIET
)
if (_readelf_result EQUAL 0)
set(${output_var} TRUE PARENT_SCOPE)
else()
set(${output_var} FALSE PARENT_SCOPE)
endif()
endfunction()
function(_citron_set_rpath file_path rpath)
_citron_is_elf(_is_elf "${file_path}")
if (NOT _is_elf)
return()
endif()
execute_process(
COMMAND "${PATCHELF_EXE}" --force-rpath --set-rpath "${rpath}" "${file_path}"
RESULT_VARIABLE _patchelf_result
OUTPUT_VARIABLE _patchelf_stdout
ERROR_VARIABLE _patchelf_stderr
)
if (NOT _patchelf_result EQUAL 0)
message(FATAL_ERROR
"Failed to set RPATH on ${file_path}\n"
"${_patchelf_stdout}\n${_patchelf_stderr}")
endif()
endfunction()
foreach(_executable ${_executables})
_citron_set_rpath("${_executable}" "$ORIGIN/lib")
endforeach()
file(GLOB _final_libraries "${_lib_dir}/*.so" "${_lib_dir}/*.so.*")
foreach(_library ${_final_libraries})
_citron_set_rpath("${_library}" "$ORIGIN")
endforeach()
if (EXISTS "${_plugins_dir}")
file(GLOB_RECURSE _final_modules "${_plugins_dir}/*.so")
foreach(_module ${_final_modules})
_citron_set_rpath("${_module}" "$ORIGIN/../../lib")
endforeach()
endif()
set(_rpath_failures)
foreach(_file ${_executables} ${_final_libraries} ${_final_modules})
_citron_is_elf(_is_elf "${_file}")
if (NOT _is_elf)
continue()
endif()
execute_process(
COMMAND readelf -d "${_file}"
OUTPUT_VARIABLE _dynamic_section
ERROR_QUIET
)
if (_dynamic_section MATCHES "(RPATH|RUNPATH)[^\n]*\\[/" OR
_dynamic_section MATCHES "(RPATH|RUNPATH)[^\n]*:/")
list(APPEND _rpath_failures "${_file}")
endif()
endforeach()
if (_rpath_failures)
list(REMOVE_DUPLICATES _rpath_failures)
message(FATAL_ERROR "Absolute RPATH/RUNPATH remains in Linux bundle: ${_rpath_failures}")
endif()
message(STATUS "Linux bundle dependencies copied and RPATHs normalized in ${_bin_dir}")
+29
View File
@@ -17,6 +17,14 @@
# Static linking: BUILD_SHARED_LIBS is forced OFF in externals/CMakeLists.txt;
# all CPM packages inherit this setting.
# ── Submodule & vcpkg Policy ──────────────────────────────────────────────────
# Force-disable reliance on git submodules and vcpkg. All external dependencies
# must be fetched and managed via CPM to ensure portability and build-time
# environment isolation.
set(CITRON_CHECK_SUBMODULES OFF CACHE BOOL "Force disable submodule presence checks" FORCE)
set(CITRON_USE_BUNDLED_VCPKG OFF CACHE BOOL "Force disable vcpkg usage" FORCE)
if (NOT COMMAND CPMAddPackage)
message(FATAL_ERROR "CPM.cmake not loaded — include CMakeModules/CPM.cmake before this file")
endif()
@@ -574,6 +582,23 @@ elseif (CITRON_USE_BUNDLED_FFMPEG)
endif()
endif()
# ── Dependency Versions (Qt, ICU, XCB) ─────────────────────────────────────────
set(CITRON_QT_VERSION "6.9.3" CACHE STRING "Qt version to download via aqt")
set(CITRON_ICU_REPO "unicode-org/icu" CACHE STRING "ICU GitHub repository")
set(CITRON_ICU_TAG "release-73-2" CACHE STRING "ICU git tag/version")
set(CITRON_XCB_MACROS_VER "1.20.2" CACHE STRING "XCB util-macros version")
set(CITRON_XCB_PROTO_VER "1.17.0" CACHE STRING "XCB proto version")
set(CITRON_XCB_XAU_VER "1.0.11" CACHE STRING "XCB libXau version")
set(CITRON_XCB_LIBXCB_VER "1.16" CACHE STRING "XCB libxcb version")
set(CITRON_XCB_UTIL_VER "0.4.1" CACHE STRING "XCB util version")
set(CITRON_XCB_CURSOR_VER "0.1.6" CACHE STRING "XCB cursor version")
set(CITRON_XCB_IMAGE_VER "0.4.1" CACHE STRING "XCB image version")
set(CITRON_XCB_KEYSYMS_VER "0.4.1" CACHE STRING "XCB keysyms version")
set(CITRON_XCB_RENDERUTIL_VER "0.3.10" CACHE STRING "XCB renderutil version")
set(CITRON_XCB_WM_VER "0.4.2" CACHE STRING "XCB wm version")
# ═══════════════════════════════════════════════════════════════════════════════
# Qt
# ═══════════════════════════════════════════════════════════════════════════════
@@ -582,6 +607,10 @@ endif()
# See CMakeModules/qt_download.cmake for details.
if (ENABLE_QT AND NOT USE_SYSTEM_QT)
include(${CMAKE_SOURCE_DIR}/CMakeModules/qt_download.cmake)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
include(${CMAKE_SOURCE_DIR}/CMakeModules/icu_build.cmake)
include(${CMAKE_SOURCE_DIR}/CMakeModules/xcb_build.cmake)
endif()
endif()
message(STATUS "[CPM] All dependency packages configured")
+67
View File
@@ -0,0 +1,67 @@
# SPDX-FileCopyrightText: 2026 citron Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# ICU build script for Linux portability.
# Fetches ICU via CPM and builds it as shared libraries using Autotools.
if (NOT UNIX OR APPLE)
return()
endif()
if (NOT DEFINED CITRON_ICU_REPO)
set(CITRON_ICU_REPO "unicode-org/icu")
endif()
if (NOT DEFINED CITRON_ICU_TAG)
set(CITRON_ICU_TAG "release-73-2")
endif()
CPMAddPackage(
NAME icu_src
GITHUB_REPOSITORY "${CITRON_ICU_REPO}"
GIT_TAG "${CITRON_ICU_TAG}"
DOWNLOAD_ONLY YES
)
if (icu_src_ADDED)
set(ICU_PREFIX "${icu_src_SOURCE_DIR}/icu4c")
set(ICU_BUILD_DIR "${CMAKE_BINARY_DIR}/externals/icu-build")
file(MAKE_DIRECTORY "${ICU_BUILD_DIR}")
# Identify the shared libraries we expect to produce
set(_icu_components data i18n io test tu uc)
set(ICU_LIBRARIES)
foreach(_comp ${_icu_components})
list(APPEND ICU_LIBRARIES "${ICU_BUILD_DIR}/lib/libicu${_comp}.so.73")
endforeach()
execute_process(COMMAND nproc OUTPUT_VARIABLE SYSTEM_THREADS OUTPUT_STRIP_TRAILING_WHITESPACE)
set(_icu_work_dir "${ICU_BUILD_DIR}/build")
file(MAKE_DIRECTORY "${_icu_work_dir}")
add_custom_command(
OUTPUT "${_icu_work_dir}/Makefile"
COMMAND "${ICU_PREFIX}/source/configure"
--prefix="${ICU_BUILD_DIR}"
--enable-shared
--disable-static
--disable-samples
--disable-tests
WORKING_DIRECTORY "${_icu_work_dir}"
COMMENT "Configuring ICU via Autotools"
)
add_custom_command(
OUTPUT ${ICU_LIBRARIES}
COMMAND make -j${SYSTEM_THREADS}
COMMAND make install
DEPENDS "${_icu_work_dir}/Makefile"
WORKING_DIRECTORY "${_icu_work_dir}"
COMMENT "Building and installing ICU"
)
add_custom_target(icu-build ALL DEPENDS ${ICU_LIBRARIES})
# Export for CopyCitronQt6Deps
set(ICU_BINARY_DIR "${ICU_BUILD_DIR}/lib" CACHE INTERNAL "Location of CPM-built ICU libs")
endif()
+18 -4
View File
@@ -17,8 +17,20 @@
#
# Prerequisites: Python3 + aqt must be installed (build script's ensure_aqt() handles this).
set(CITRON_QT_VERSION "6.9.3" CACHE STRING "Qt version to download via aqt")
set(CITRON_QT_BASE_DIR "${CMAKE_BINARY_DIR}/externals/qt-cpm" CACHE PATH
# The version is defined in dependencies.cmake as a CACHE variable.
if (NOT DEFINED CITRON_QT_VERSION)
set(CITRON_QT_VERSION "6.9.3")
endif()
if (DEFINED ENV{CPM_SOURCE_CACHE})
set(_DEFAULT_QT_BASE_DIR "$ENV{CPM_SOURCE_CACHE}/qt-bin")
elseif (DEFINED CPM_SOURCE_CACHE)
set(_DEFAULT_QT_BASE_DIR "${CPM_SOURCE_CACHE}/qt-bin")
else()
set(_DEFAULT_QT_BASE_DIR "${CMAKE_BINARY_DIR}/externals/qt-cpm")
endif()
set(CITRON_QT_BASE_DIR "${_DEFAULT_QT_BASE_DIR}" CACHE PATH
"Base directory for aqt-managed Qt downloads")
# ── Find aqt ──────────────────────────────────────────────────────────────────
@@ -44,12 +56,14 @@ if (WIN32)
set(_QT_OS "windows")
set(_QT_TARGET "desktop")
set(_QT_ARCH "win64_llvm_mingw")
set(_QT_DIR_NAME "llvm-mingw_64")
set(_QT_CMAKE_SUB "lib/cmake/Qt6")
else()
# Native Linux (or macOS — extend here if needed)
set(_QT_OS "linux")
set(_QT_TARGET "desktop")
set(_QT_ARCH "linux_gcc_64")
set(_QT_DIR_NAME "gcc_64")
set(_QT_CMAKE_SUB "lib/cmake/Qt6")
endif()
@@ -57,7 +71,7 @@ endif()
if (Qt6_DIR AND EXISTS "${Qt6_DIR}/Qt6Config.cmake")
message(STATUS "[Qt] Using target Qt from Qt6_DIR: ${Qt6_DIR}")
else()
set(_QT_TARGET_DIR "${CITRON_QT_BASE_DIR}/${CITRON_QT_VERSION}/${_QT_ARCH}")
set(_QT_TARGET_DIR "${CITRON_QT_BASE_DIR}/${CITRON_QT_VERSION}/${_QT_DIR_NAME}")
set(_QT_TARGET_CMAKE "${_QT_TARGET_DIR}/${_QT_CMAKE_SUB}/Qt6Config.cmake")
if (NOT EXISTS "${_QT_TARGET_CMAKE}")
@@ -92,7 +106,7 @@ else()
${_QT_OS} ${_QT_TARGET}
${CITRON_QT_VERSION} ${_QT_ARCH}
--outputdir "${CITRON_QT_BASE_DIR}"
--modules qtmultimedia qtimageformats qtsvg
--modules qtmultimedia qtimageformats
RESULT_VARIABLE _qt_mm_result
OUTPUT_QUIET ERROR_QUIET
)
+108
View File
@@ -0,0 +1,108 @@
# SPDX-FileCopyrightText: 2026 citron Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# XCB utility build script for Linux portability.
# Fetches XCB components via CPM and builds them as shared libraries.
if (NOT UNIX OR APPLE)
return()
endif()
# We need a list of components to fetch and build.
# 'macros' and 'proto' must come first as others depend on them.
set(_xcb_components macros proto xau libxcb util image keysyms renderutil wm cursor)
set(XCB_BUILD_ROOT "${CMAKE_BINARY_DIR}/externals/xcb-build")
file(MAKE_DIRECTORY "${XCB_BUILD_ROOT}")
set(XCB_BINARY_DIR "${XCB_BUILD_ROOT}/lib" CACHE INTERNAL "Location of CPM-built XCB libs")
# Define versions for each component to fetch the correct tarball
# These are defined in dependencies.cmake as CACHE variables.
set(_xcb_macros_ver "${CITRON_XCB_MACROS_VER}")
set(_xcb_proto_ver "${CITRON_XCB_PROTO_VER}")
set(_xcb_xau_ver "${CITRON_XCB_XAU_VER}")
set(_xcb_libxcb_ver "${CITRON_XCB_LIBXCB_VER}")
set(_xcb_cursor_ver "${CITRON_XCB_CURSOR_VER}")
set(_xcb_util_ver "${CITRON_XCB_UTIL_VER}")
set(_xcb_image_ver "${CITRON_XCB_IMAGE_VER}")
set(_xcb_keysyms_ver "${CITRON_XCB_KEYSYMS_VER}")
set(_xcb_renderutil_ver "${CITRON_XCB_RENDERUTIL_VER}")
set(_xcb_wm_ver "${CITRON_XCB_WM_VER}")
set(CITRON_XCB_TARGETS "" CACHE INTERNAL "List of XCB build targets")
foreach(_comp ${_xcb_components})
# (re-pasting the existing logic to avoid deletion)
if (_comp STREQUAL "macros")
set(_url "https://xorg.freedesktop.org/archive/individual/util/util-macros-${_xcb_macros_ver}.tar.xz")
set(_name "util-macros")
elseif(_comp STREQUAL "xau")
set(_url "https://xorg.freedesktop.org/archive/individual/lib/libXau-${_xcb_xau_ver}.tar.xz")
set(_name "libXau")
elseif(_comp STREQUAL "libxcb")
set(_url "https://xcb.freedesktop.org/dist/libxcb-${_xcb_libxcb_ver}.tar.xz")
set(_name "libxcb")
elseif(_comp STREQUAL "proto")
set(_url "https://xcb.freedesktop.org/dist/xcb-proto-${_xcb_proto_ver}.tar.xz")
set(_name "xcb-proto")
else()
set(_name "xcb-util")
if (NOT _comp STREQUAL "util")
set(_name "xcb-util-${_comp}")
endif()
set(_ver "${_xcb_${_comp}_ver}")
set(_url "https://xcb.freedesktop.org/dist/${_name}-${_ver}.tar.xz")
endif()
CPMAddPackage(
NAME xcb_${_comp}_src
URL "${_url}"
DOWNLOAD_ONLY YES
)
if (xcb_${_comp}_src_ADDED)
set(_src_dir "${xcb_${_comp}_src_SOURCE_DIR}")
set(_build_dir "${XCB_BUILD_ROOT}/${_comp}")
file(MAKE_DIRECTORY "${_build_dir}")
# Check if it has a CMakeLists.txt (newer versions do) or needs Autotools
if (EXISTS "${_src_dir}/CMakeLists.txt")
# Build via CMake
add_subdirectory("${_src_dir}" "${_build_dir}")
else()
# Build via Autotools (standard fallback)
set(_env_vars
"ACLOCAL_PATH=${XCB_BUILD_ROOT}/share/aclocal"
"PKG_CONFIG_PATH=${XCB_BUILD_ROOT}/lib/pkgconfig:${XCB_BUILD_ROOT}/share/pkgconfig"
"PYTHON=python3"
"LD_RUN_PATH=\$\$ORIGIN"
)
set(_target_name "xcb-${_comp}-build")
list(APPEND CITRON_XCB_TARGETS ${_target_name})
set(CITRON_XCB_TARGETS ${CITRON_XCB_TARGETS} CACHE INTERNAL "List of XCB build targets")
# We use autoreconf -fi to refresh libtool/aclocal files from the host system
# to avoid mismatch errors (e.g. 'ltmain.sh is newer').
add_custom_command(
OUTPUT "${_build_dir}/Makefile"
COMMAND ${CMAKE_COMMAND} -E env ${_env_vars} autoreconf -fi "${_src_dir}"
COMMAND ${CMAKE_COMMAND} -E env ${_env_vars} "${_src_dir}/configure" --prefix="${XCB_BUILD_ROOT}" --enable-shared
WORKING_DIRECTORY "${_build_dir}"
COMMENT "Configuring ${_name} via Autotools"
)
add_custom_target(${_target_name} ALL
COMMAND ${CMAKE_COMMAND} -E env ${_env_vars} make install
DEPENDS "${_build_dir}/Makefile"
WORKING_DIRECTORY "${_build_dir}"
COMMENT "Building ${_name}"
)
# Enforce sequential build ordering to ensure all dependencies are installed
# in XCB_BUILD_ROOT before downstream components are configured.
if (_last_xcb_target)
add_dependencies(${_target_name} ${_last_xcb_target})
endif()
set(_last_xcb_target ${_target_name})
endif()
endif()
endforeach()
Regular → Executable
+23 -10
View File
@@ -129,6 +129,7 @@
# python3 + aqtinstall Qt binary download (invoked by cmake)
# autoconf + automake + make FFmpeg autotools build
# glslang (glslc) Vulkan shader compilation
# patchelf bundle RPATH normalization
# perf propeller stage only
# =============================================================================
@@ -350,7 +351,7 @@ stage_setup() {
*)
warn "Unrecognised package manager. Install manually:"
warn " clang (${CLANG_VERSION}+), lld, llvm-profdata, cmake, ninja, git,"
warn " nasm, perl, python3, python3-pip, autoconf, automake, make, glslang-tools"
warn " nasm, perl, python3, python3-pip, autoconf, automake, make, glslang-tools, patchelf"
;;
esac
@@ -370,6 +371,7 @@ _setup_apt() {
nasm yasm perl \
autoconf automake make \
glslang-tools \
patchelf \
lsb-release software-properties-common gnupg \
libelf-dev libssl-dev libzstd-dev \
linux-tools-common linux-tools-generic 2>/dev/null || true
@@ -384,7 +386,7 @@ _setup_pacman() {
nasm yasm perl \
autoconf automake make \
glslang clang lld llvm \
perf 2>/dev/null || true
patchelf perf 2>/dev/null || true
# Arch ships unversioned tools — symlink to versioned names
for tool in clang clang++ lld llvm-profdata llvm-bolt merge-fdata; do
local versioned="/usr/local/bin/${tool}-${CLANG_VERSION}"
@@ -402,7 +404,7 @@ _setup_dnf() {
python3 python3-pip curl wget xz \
nasm yasm perl \
autoconf automake make \
glslang clang lld \
glslang clang lld patchelf \
elfutils-libelf-devel openssl-devel \
perf 2>/dev/null || true
sudo dnf install -y "clang${CLANG_VERSION}" "llvm${CLANG_VERSION}" 2>/dev/null \
@@ -417,7 +419,7 @@ _setup_yum() {
python3 python3-pip curl wget xz \
nasm yasm perl \
autoconf automake make \
clang lld \
clang lld patchelf \
elfutils-libelf-devel openssl-devel \
perf 2>/dev/null || true
warn "yum/CentOS: LLVM ${CLANG_VERSION} may not be in repos. Check SCL or llvm.org."
@@ -430,7 +432,7 @@ _setup_zypper() {
python3 python3-pip curl wget xz \
nasm yasm perl \
autoconf automake make \
glslang clang lld llvm \
glslang clang lld llvm patchelf \
libelf-devel libopenssl-devel \
perf 2>/dev/null || true
}
@@ -444,6 +446,7 @@ _setup_emerge() {
sys-devel/clang sys-devel/lld \
dev-build/autoconf dev-build/automake \
media-libs/glslang \
dev-util/patchelf \
dev-libs/elfutils dev-libs/openssl 2>/dev/null || true
}
@@ -503,7 +506,7 @@ _verify_tools() {
info "Verifying installation..."
local ok=1
for tool in "${CLANG}" "${CLANGPP}" "${LLD}" "${LLVM_PROFDATA}" \
cmake ninja git nasm perl python3; do
cmake ninja git nasm perl python3 patchelf; do
if command -v "${tool}" &>/dev/null; then
success " ${tool} -> $(command -v "${tool}")"
else
@@ -663,7 +666,9 @@ common_cmake_args() {
"-DUSE_SYSTEM_QT=OFF" \
"-DENABLE_QT6=ON" \
"-DCITRON_USE_BUNDLED_FFMPEG=ON" \
"-DBUILD_TESTING=OFF" \
"-DCITRON_TESTS=OFF" \
"-DCITRON_DOWNLOAD_TIME_ZONE_DATA=ON" \
"-DCITRON_CHECK_SUBMODULES=OFF" \
"-DCITRON_USE_LLVM_DEMANGLE=OFF" \
"-DCITRON_USE_QT_MULTIMEDIA=ON" \
@@ -822,6 +827,7 @@ stage_generate() {
"-DCMAKE_EXE_LINKER_FLAGS_${bt_upper}=${linker_flags}"
write_gen_sentinel
mkdir -p "${BUILD_GENERATE}/bin/user"
success "Instrumented build: ${BUILD_GENERATE}/bin/citron"
print_profiling_instructions "${BUILD_GENERATE}/bin/citron"
}
@@ -894,6 +900,7 @@ stage_csgenerate() {
"-DCMAKE_CXX_FLAGS_${bt_upper}=${compile_flags}" \
"-DCMAKE_EXE_LINKER_FLAGS_${bt_upper}=${linker_flags}"
mkdir -p "${BUILD_CSGENERATE}/bin/user"
success "CS-IRPGO instrumented build: ${BUILD_CSGENERATE}/bin/citron"
echo ""
echo -e "${YELLOW}════════════════════════════════════════════════════════════════${RESET}"
@@ -963,6 +970,7 @@ stage_use() {
header "Build: Baseline Release (no PGO, LTO=${LTO_MODE})"
apply_source_patches
_build_with_flags "${BUILD_ROOT}/use-nopgo" nopgo
mkdir -p "${BUILD_ROOT}/use-nopgo/bin/user"
success "Baseline build: ${BUILD_ROOT}/use-nopgo/bin/citron"
return 0
fi
@@ -1004,6 +1012,7 @@ stage_use() {
echo ""
success "════════════════════════════════════════════════════════════════"
success " Stage use complete"
mkdir -p "${BUILD_USE}/bin/user"
success " Binary: ${BUILD_USE}/bin/citron"
success " PGO: ${pgo_label}"
success " LTO: ${LTO_MODE}${LTO_MODE:+ ($(lto_clang_flag))}"
@@ -1123,8 +1132,8 @@ stage_bolt() {
fi
local elf_binary="${relocs_dir}/bin/citron"
mkdir -p "${BOLT_PROFILE_DIR}" "${BUILD_BOLT}"
local instrumented="${BUILD_BOLT}/citron-bolt-instrumented"
mkdir -p "${BOLT_PROFILE_DIR}" "${BUILD_BOLT}/bin"
local instrumented="${BUILD_BOLT}/bin/citron-bolt-instrumented"
local fdata_pattern="${BOLT_PROFILE_DIR}/citron-%p.fdata"
local merged_fdata="${BOLT_PROFILE_DIR}/citron-merged.fdata"
@@ -1167,15 +1176,17 @@ stage_bolt() {
--split-all-cold \
--split-eh \
--dyno-stats \
-o "${BUILD_BOLT}/citron" \
-o "${BUILD_BOLT}/bin/citron" \
|| error "BOLT optimization failed."
echo ""
success "════════════════════════════════════════════════════════════════"
success " Stage bolt complete"
success " Binary: ${BUILD_BOLT}/citron"
mkdir -p "${BUILD_BOLT}/user"
success " Binary: ${BUILD_BOLT}/bin/citron"
success " Optimizations: PGO + LTO + BOLT basic-block reordering"
success "════════════════════════════════════════════════════════════════"
echo ""
}
# =============================================================================
@@ -1341,9 +1352,11 @@ stage_propeller() {
echo ""
success "════════════════════════════════════════════════════════════════"
success " Stage propeller complete"
mkdir -p "${BUILD_PROPELLER}/bin/user"
success " Binary: ${BUILD_PROPELLER}/bin/citron"
success " Optimizations: PGO + LTO + Propeller BB+function layout"
success "════════════════════════════════════════════════════════════════"
echo ""
}
# =============================================================================
+3 -1
View File
@@ -40,6 +40,9 @@ if (NOT WIN32 AND NOT ANDROID)
set(FFmpeg_FOUND YES)
endif()
if (NOT DEFINED FFmpeg_COMPONENTS)
set(FFmpeg_COMPONENTS avcodec avfilter avutil swscale)
endif()
unset(FFmpeg_LIBRARIES CACHE)
foreach(COMPONENT ${FFmpeg_COMPONENTS})
set(FFmpeg_${COMPONENT}_PREFIX "${FFmpeg_BUILD_DIR}/lib${COMPONENT}")
@@ -168,7 +171,6 @@ if (NOT WIN32 AND NOT ANDROID)
--disable-ffmpeg
--disable-ffprobe
--disable-network
--disable-postproc
--disable-swresample
--enable-decoder=h264
--enable-decoder=vp8
+13 -1
View File
@@ -14,7 +14,13 @@ set(CMAKE_JOB_POOL_LINK link_pool)
# Set the RPATH for Qt Libraries
# This must be done before the `citron` target is created
if (CITRON_USE_BUNDLED_QT AND (${CMAKE_SYSTEM_NAME} STREQUAL "Linux"))
set(CMAKE_BUILD_RPATH "${CMAKE_BINARY_DIR}/bin/lib/")
# Use relative RPATH so the binary finds libraries in the 'lib' subfolder.
# $ORIGIN refers to the directory containing the executable.
# We use --disable-new-dtags to force RPATH instead of RUNPATH,
# ensuring recursive dependency resolution for bundled libraries.
set(CMAKE_BUILD_RPATH "$ORIGIN/lib/")
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib/")
add_link_options("-Wl,--disable-new-dtags")
endif()
add_executable(citron
@@ -652,6 +658,12 @@ endif()
if (CITRON_USE_BUNDLED_QT)
include(CopyCitronQt6Deps)
if (TARGET icu-build)
add_dependencies(citron icu-build)
endif()
if (CITRON_XCB_TARGETS)
add_dependencies(citron ${CITRON_XCB_TARGETS})
endif()
copy_citron_Qt6_deps(citron)
endif()