Run file(GET_RUNTIME_DEPENDENCIES) before windeployqt so the transitive MinGW/Qt DLL closure resolves from a single path. Running windeployqt first copied Qt6Core.dll into staging, causing "Multiple conflicting paths" aborts. Also set CMP0207 to NEW and exclude SysWOW64. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
135 lines
5.1 KiB
CMake
135 lines
5.1 KiB
CMake
cmake_minimum_required(VERSION 3.19)
|
|
|
|
project(ApartConcept1Controller
|
|
VERSION 1.0.0
|
|
DESCRIPTION "RS232 controller for the Apart Concept 1 amplifier"
|
|
LANGUAGES CXX)
|
|
|
|
set(CMAKE_CXX_STANDARD 17)
|
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
set(CMAKE_AUTOMOC ON)
|
|
set(CMAKE_AUTORCC ON)
|
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
|
|
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
|
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
|
|
endif()
|
|
|
|
# Required Qt6 component
|
|
find_package(Qt6 REQUIRED COMPONENTS Widgets)
|
|
|
|
# Optional Qt6 SerialPort: real hardware support is compiled in when available.
|
|
# Without it the app still builds and runs in simulator mode.
|
|
find_package(Qt6 QUIET OPTIONAL_COMPONENTS SerialPort)
|
|
|
|
set(SOURCES
|
|
src/main.cpp
|
|
src/protocol/Concept1Protocol.cpp
|
|
src/transport/SimulatorTransport.cpp
|
|
src/core/Concept1Controller.cpp
|
|
src/ui/ThemeManager.cpp
|
|
src/ui/MainWindow.cpp
|
|
)
|
|
|
|
set(HEADERS
|
|
src/protocol/Concept1Protocol.h
|
|
src/protocol/Concept1State.h
|
|
src/transport/ITransport.h
|
|
src/transport/SimulatorTransport.h
|
|
src/core/Concept1Controller.h
|
|
src/ui/ThemeManager.h
|
|
src/ui/MainWindow.h
|
|
)
|
|
|
|
if(Qt6SerialPort_FOUND)
|
|
list(APPEND SOURCES src/transport/SerialTransport.cpp)
|
|
list(APPEND HEADERS src/transport/SerialTransport.h)
|
|
message(STATUS "Qt6 SerialPort found -> real RS232 hardware support ENABLED")
|
|
else()
|
|
message(WARNING "Qt6 SerialPort NOT found -> building with SIMULATOR only. "
|
|
"Install it (e.g. 'sudo pacman -S qt6-serialport') for hardware support.")
|
|
endif()
|
|
|
|
qt_add_resources(SOURCES resources/app.qrc)
|
|
|
|
# WIN32 makes this a GUI app on Windows (no console window). Ignored elsewhere.
|
|
add_executable(apart-controller WIN32 ${SOURCES} ${HEADERS})
|
|
|
|
target_include_directories(apart-controller PRIVATE src)
|
|
|
|
target_link_libraries(apart-controller PRIVATE Qt6::Widgets)
|
|
|
|
if(Qt6SerialPort_FOUND)
|
|
target_link_libraries(apart-controller PRIVATE Qt6::SerialPort)
|
|
target_compile_definitions(apart-controller PRIVATE HAVE_QT_SERIALPORT)
|
|
endif()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Installation + packaging (Windows installer via CPack/NSIS)
|
|
# ---------------------------------------------------------------------------
|
|
install(TARGETS apart-controller RUNTIME DESTINATION .)
|
|
|
|
if(WIN32)
|
|
get_filename_component(_mingw_bin "${CMAKE_CXX_COMPILER}" DIRECTORY)
|
|
find_program(WINDEPLOYQT_EXECUTABLE windeployqt
|
|
HINTS "${_mingw_bin}" "${Qt6_DIR}/../../../bin")
|
|
|
|
# Pass config-time paths into the install-time script below.
|
|
install(CODE "set(_WINDEPLOYQT \"${WINDEPLOYQT_EXECUTABLE}\")")
|
|
install(CODE "set(_MINGW_BIN \"${_mingw_bin}\")")
|
|
|
|
install(CODE [[
|
|
if(POLICY CMP0207)
|
|
cmake_policy(SET CMP0207 NEW)
|
|
endif()
|
|
|
|
# 1) Resolve the exe's full transitive DLL closure from the mingw bin
|
|
# dir. This MUST run before windeployqt: at this point the package
|
|
# contains only the .exe, so each dependency (Qt6Core.dll, libpng,
|
|
# zlib, freetype, harfbuzz, pcre2, zstd, brotli, icu, libgcc/
|
|
# libstdc++/winpthread, ...) resolves to exactly one path. Running
|
|
# after windeployqt would find duplicates (staging vs. mingw) and
|
|
# abort with "Multiple conflicting paths". System DLLs are excluded.
|
|
file(GET_RUNTIME_DEPENDENCIES
|
|
EXECUTABLES "${CMAKE_INSTALL_PREFIX}/apart-controller.exe"
|
|
RESOLVED_DEPENDENCIES_VAR _resolved
|
|
UNRESOLVED_DEPENDENCIES_VAR _unresolved
|
|
DIRECTORIES "${_MINGW_BIN}"
|
|
PRE_EXCLUDE_REGEXES "api-ms-win-.*" "ext-ms-.*"
|
|
POST_EXCLUDE_REGEXES ".*[Ss]ystem32.*" ".*[Ss]ysWOW64.*")
|
|
foreach(_dll ${_resolved})
|
|
file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
|
TYPE SHARED_LIBRARY FOLLOW_SYMLINK_CHAIN FILES "${_dll}")
|
|
endforeach()
|
|
|
|
# 2) windeployqt now adds the runtime-loaded Qt *plugins*
|
|
# (platforms/qwindows.dll, styles, imageformats) plus any helper
|
|
# DLLs those plugins need. It is fine for it to re-copy Qt DLLs.
|
|
if(_WINDEPLOYQT)
|
|
execute_process(COMMAND "${_WINDEPLOYQT}"
|
|
--release --no-translations --no-system-d3d-compiler
|
|
--dir "${CMAKE_INSTALL_PREFIX}"
|
|
"${CMAKE_INSTALL_PREFIX}/apart-controller.exe")
|
|
endif()
|
|
]])
|
|
endif()
|
|
|
|
set(CPACK_PACKAGE_NAME "Apart Concept 1 Controller")
|
|
set(CPACK_PACKAGE_VENDOR "ApartController")
|
|
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
|
|
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_DESCRIPTION}")
|
|
set(CPACK_PACKAGE_INSTALL_DIRECTORY "Apart Concept 1 Controller")
|
|
|
|
if(WIN32)
|
|
set(CPACK_GENERATOR "NSIS")
|
|
set(CPACK_NSIS_DISPLAY_NAME "Apart Concept 1 Controller")
|
|
set(CPACK_NSIS_PACKAGE_NAME "Apart Concept 1 Controller")
|
|
set(CPACK_NSIS_INSTALLED_ICON_NAME "apart-controller.exe")
|
|
set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON)
|
|
set(CPACK_NSIS_MODIFY_PATH OFF)
|
|
# Start-menu + desktop shortcut pointing at the exe.
|
|
set(CPACK_PACKAGE_EXECUTABLES "apart-controller" "Apart Concept 1 Controller")
|
|
set(CPACK_NSIS_CREATE_DESKTOP_LINKS "apart-controller")
|
|
endif()
|
|
|
|
include(CPack)
|