diff --git a/CMakeLists.txt b/CMakeLists.txt index 6303ded..932e951 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,13 +6,29 @@ set(CLANG_FORMAT_EXCLUDE_PATTERNS "build/") find_package(hidapi REQUIRED) +# ------------------------------------------------------------------------------ +# Top Level Project Guard +# ------------------------------------------------------------------------------ + +if(CMAKE_VERSION VERSION_LESS "3.21" OR NOT DEFINED PROJECT_IS_TOP_LEVEL) # PROJECT_IS_TOP_LEVEL is available for CMake 3.21 or later. + # Fallback: check if the path to the top level of the source tree is equal to the current project source dir. + if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + set(PROJECT_IS_TOP_LEVEL ON) + else() + set(PROJECT_IS_TOP_LEVEL OFF) + endif() +endif() + # ------------------------------------------------------------------------------ # Subdirectories # ------------------------------------------------------------------------------ add_subdirectory(lib) add_subdirectory(cli) -add_subdirectory(tests) + +if(PROJECT_IS_TOP_LEVEL) + add_subdirectory(tests) +endif() # ------------------------------------------------------------------------------ # C++ Standard @@ -99,7 +115,7 @@ configure_file( # Clang format # ------------------------------------------------------------------------------ -if(ENABLE_CLANG_FORMAT) +if(ENABLE_CLANG_FORMAT AND PROJECT_IS_TOP_LEVEL) # Try to find clang-format-18 first to match CI version find_program(CLANG_FORMAT_BIN @@ -153,7 +169,7 @@ endif() # Clang Tidy # ------------------------------------------------------------------------------ -if(ENABLE_CLANG_TIDY) +if(ENABLE_CLANG_TIDY AND PROJECT_IS_TOP_LEVEL) find_program(CLANG_TIDY_BIN NAMES clang-tidy-9 clang-tidy) find_program(RUN_CLANG_TIDY_BIN NAMES run-clang-tidy-9.py run-clang-tidy.py) @@ -250,159 +266,175 @@ endif() # CLI Executable # ------------------------------------------------------------------------------ -# Create the CLI executable that links against the library -# On Windows, include the resource file for the application icon -if(WIN32) - set(WIN_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.rc) - # Ensure resource compiler can find the icon file - set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/assets") -endif() +if(PROJECT_IS_TOP_LEVEL) -add_executable(headsetcontrol ${CLI_SOURCES} ${WIN_RESOURCES}) -target_link_libraries(headsetcontrol PRIVATE headsetcontrol_lib) -target_include_directories(headsetcontrol PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli) + # Create the CLI executable that links against the library + # On Windows, include the resource file for the application icon + if(WIN32) + set(WIN_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.rc) + # Ensure resource compiler can find the icon file + set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/assets") + endif() -# On MSVC, we need getopt from vcpkg -if(MSVC) - find_package(getopt CONFIG REQUIRED) - target_link_libraries(headsetcontrol PRIVATE $,getopt::getopt_shared,getopt::getopt_static>) -endif() + add_executable(headsetcontrol ${CLI_SOURCES} ${WIN_RESOURCES}) + target_link_libraries(headsetcontrol PRIVATE headsetcontrol_lib) + target_include_directories(headsetcontrol PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli) -# ------------------------------------------------------------------------------ -# Installation -# ------------------------------------------------------------------------------ + # On MSVC, we need getopt from vcpkg + if(MSVC) + find_package(getopt CONFIG REQUIRED) + target_link_libraries(headsetcontrol PRIVATE $,getopt::getopt_shared,getopt::getopt_static>) + endif() -install(TARGETS headsetcontrol DESTINATION bin) -install(TARGETS headsetcontrol_lib DESTINATION lib) + # ------------------------------------------------------------------------------ + # Installation + # ------------------------------------------------------------------------------ -# Install shared library if built -if(BUILD_SHARED_LIBRARY) - install(TARGETS headsetcontrol_shared DESTINATION lib) -endif() + install(TARGETS headsetcontrol DESTINATION bin) + install(TARGETS headsetcontrol_lib DESTINATION lib) -# Install public headers -install(FILES - ${CMAKE_CURRENT_SOURCE_DIR}/lib/headsetcontrol.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/lib/headsetcontrol_c.h - ${CMAKE_CURRENT_SOURCE_DIR}/lib/device.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/lib/result_types.hpp - DESTINATION include/headsetcontrol -) + # Install shared library if built + if(BUILD_SHARED_LIBRARY) + install(TARGETS headsetcontrol_shared DESTINATION lib) + endif() + + # Install public headers + install(FILES + ${CMAKE_CURRENT_SOURCE_DIR}/lib/headsetcontrol.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/lib/headsetcontrol_c.h + ${CMAKE_CURRENT_SOURCE_DIR}/lib/device.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/lib/result_types.hpp + DESTINATION include/headsetcontrol + ) + + # install udev files on linux + if(UNIX AND NOT APPLE AND NOT ${CMAKE_HOST_SYSTEM_NAME} MATCHES "FreeBSD") + set(rules_file 70-headsets.rules) + set(udev_rules_dir lib/udev/rules.d/ + CACHE PATH "Path to the directory where udev rules should be installed") + add_custom_command( + OUTPUT ${rules_file} + COMMAND headsetcontrol -u > ${rules_file} + DEPENDS headsetcontrol) + add_custom_target(udevrules ALL DEPENDS ${rules_file}) + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/${rules_file} + DESTINATION ${udev_rules_dir}) + endif() -# install udev files on linux -if(UNIX AND NOT APPLE AND NOT ${CMAKE_HOST_SYSTEM_NAME} MATCHES "FreeBSD") - set(rules_file 70-headsets.rules) - set(udev_rules_dir lib/udev/rules.d/ - CACHE PATH "Path to the directory where udev rules should be installed") - add_custom_command( - OUTPUT ${rules_file} - COMMAND headsetcontrol -u > ${rules_file} - DEPENDS headsetcontrol) - add_custom_target(udevrules ALL DEPENDS ${rules_file}) - install( - FILES ${CMAKE_CURRENT_BINARY_DIR}/${rules_file} - DESTINATION ${udev_rules_dir}) endif() # ------------------------------------------------------------------------------ # Testing # ------------------------------------------------------------------------------ -include(CTest) -enable_testing() +if(PROJECT_IS_TOP_LEVEL) -# Integration Test: Basic application run -add_test(NAME integration_basic_run - COMMAND headsetcontrol - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) -set_tests_properties(integration_basic_run - PROPERTIES PASS_REGULAR_EXPRESSION "No supported device found;Found") + include(CTest) + enable_testing() -# Test targets -add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure - DEPENDS headsetcontrol) + # Integration Test: Basic application run + add_test(NAME integration_basic_run + COMMAND headsetcontrol + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + set_tests_properties(integration_basic_run + PROPERTIES PASS_REGULAR_EXPRESSION "No supported device found;Found") -add_custom_target(test-verbose - COMMAND ${CMAKE_CTEST_COMMAND} --verbose --output-on-failure - DEPENDS headsetcontrol - COMMENT "Running tests with verbose output" -) + # Test targets + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure + DEPENDS headsetcontrol) + + add_custom_target(test-verbose + COMMAND ${CMAKE_CTEST_COMMAND} --verbose --output-on-failure + DEPENDS headsetcontrol + COMMENT "Running tests with verbose output" + ) + +endif() # ------------------------------------------------------------------------------ # Unit Tests # ------------------------------------------------------------------------------ -option(BUILD_UNIT_TESTS "Build unit tests with mock HID interface" ON) +if(PROJECT_IS_TOP_LEVEL) + + option(BUILD_UNIT_TESTS "Build unit tests with mock HID interface" ON) + + if(BUILD_UNIT_TESTS) + add_executable(headsetcontrol_tests ${TEST_SOURCES}) + target_link_libraries(headsetcontrol_tests PRIVATE headsetcontrol_lib) + target_include_directories(headsetcontrol_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli) -if(BUILD_UNIT_TESTS) - add_executable(headsetcontrol_tests ${TEST_SOURCES}) - target_link_libraries(headsetcontrol_tests PRIVATE headsetcontrol_lib) - target_include_directories(headsetcontrol_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli) + add_test(NAME unit_mock_devices + COMMAND headsetcontrol_tests + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - add_test(NAME unit_mock_devices - COMMAND headsetcontrol_tests - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + add_dependencies(check headsetcontrol_tests) + endif() - add_dependencies(check headsetcontrol_tests) endif() # ------------------------------------------------------------------------------ # CPack Package Generation # ------------------------------------------------------------------------------ -set(CPACK_PACKAGE_NAME "headsetcontrol") -set(CPACK_PACKAGE_VENDOR "Sapd") -set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Control USB headsets on Linux, macOS, and Windows") -set(CPACK_PACKAGE_DESCRIPTION "HeadsetControl is a tool to control USB-connected headsets. \ -It supports setting sidetone, LED lights, equalizer, inactive time, and more for various \ -gaming headsets from Logitech, SteelSeries, Corsair, HyperX, Roccat, and Audeze.") -set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/Sapd/HeadsetControl") -set(CPACK_PACKAGE_CONTACT "https://github.com/Sapd/HeadsetControl") -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") - set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") -endif() +if(PROJECT_IS_TOP_LEVEL) + + set(CPACK_PACKAGE_NAME "headsetcontrol") + set(CPACK_PACKAGE_VENDOR "Sapd") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Control USB headsets on Linux, macOS, and Windows") + set(CPACK_PACKAGE_DESCRIPTION "HeadsetControl is a tool to control USB-connected headsets. \ + It supports setting sidetone, LED lights, equalizer, inactive time, and more for various \ + gaming headsets from Logitech, SteelSeries, Corsair, HyperX, Roccat, and Audeze.") + set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/Sapd/HeadsetControl") + set(CPACK_PACKAGE_CONTACT "https://github.com/Sapd/HeadsetControl") + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") + endif() -# Extract version components from GIT_VERSION (e.g., "2.7.0" or "2.7.0-123-gabcdef") -string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" VERSION_MATCH "${GIT_VERSION}") -if(VERSION_MATCH) - set(CPACK_PACKAGE_VERSION_MAJOR "${CMAKE_MATCH_1}") - set(CPACK_PACKAGE_VERSION_MINOR "${CMAKE_MATCH_2}") - set(CPACK_PACKAGE_VERSION_PATCH "${CMAKE_MATCH_3}") - set(CPACK_PACKAGE_VERSION "${GIT_VERSION}") -else() - # Debian/RPM versions must start with a digit - # If git version doesn't (e.g., "continuous-12-g91b4f06"), prefix with 0.0.0~ - set(CPACK_PACKAGE_VERSION_MAJOR "0") - set(CPACK_PACKAGE_VERSION_MINOR "0") - set(CPACK_PACKAGE_VERSION_PATCH "0") - set(CPACK_PACKAGE_VERSION "0.0.0~${GIT_VERSION}") -endif() + # Extract version components from GIT_VERSION (e.g., "2.7.0" or "2.7.0-123-gabcdef") + string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" VERSION_MATCH "${GIT_VERSION}") + if(VERSION_MATCH) + set(CPACK_PACKAGE_VERSION_MAJOR "${CMAKE_MATCH_1}") + set(CPACK_PACKAGE_VERSION_MINOR "${CMAKE_MATCH_2}") + set(CPACK_PACKAGE_VERSION_PATCH "${CMAKE_MATCH_3}") + set(CPACK_PACKAGE_VERSION "${GIT_VERSION}") + else() + # Debian/RPM versions must start with a digit + # If git version doesn't (e.g., "continuous-12-g91b4f06"), prefix with 0.0.0~ + set(CPACK_PACKAGE_VERSION_MAJOR "0") + set(CPACK_PACKAGE_VERSION_MINOR "0") + set(CPACK_PACKAGE_VERSION_PATCH "0") + set(CPACK_PACKAGE_VERSION "0.0.0~${GIT_VERSION}") + endif() -# Debian package settings -set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Denis Arnst ") -set(CPACK_DEBIAN_PACKAGE_SECTION "utils") -set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") -set(CPACK_DEBIAN_PACKAGE_DEPENDS "libhidapi-hidraw0 | libhidapi-libusb0") -set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/Sapd/HeadsetControl") -set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) - -# RPM package settings -set(CPACK_RPM_PACKAGE_LICENSE "GPLv3") -set(CPACK_RPM_PACKAGE_GROUP "Applications/System") -set(CPACK_RPM_PACKAGE_REQUIRES "hidapi") -set(CPACK_RPM_FILE_NAME RPM-DEFAULT) - -# NSIS (Windows installer) settings -set(CPACK_NSIS_DISPLAY_NAME "HeadsetControl") -set(CPACK_NSIS_PACKAGE_NAME "HeadsetControl") -set(CPACK_NSIS_URL_INFO_ABOUT "https://github.com/Sapd/HeadsetControl") -set(CPACK_NSIS_HELP_LINK "https://github.com/Sapd/HeadsetControl/wiki") -set(CPACK_NSIS_CONTACT "git@sapd.eu") -set(CPACK_NSIS_MODIFY_PATH ON) -set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.ico") - set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.ico") - set(CPACK_NSIS_MUI_UNIICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.ico") -endif() + # Debian package settings + set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Denis Arnst ") + set(CPACK_DEBIAN_PACKAGE_SECTION "utils") + set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libhidapi-hidraw0 | libhidapi-libusb0") + set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/Sapd/HeadsetControl") + set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) + + # RPM package settings + set(CPACK_RPM_PACKAGE_LICENSE "GPLv3") + set(CPACK_RPM_PACKAGE_GROUP "Applications/System") + set(CPACK_RPM_PACKAGE_REQUIRES "hidapi") + set(CPACK_RPM_FILE_NAME RPM-DEFAULT) + + # NSIS (Windows installer) settings + set(CPACK_NSIS_DISPLAY_NAME "HeadsetControl") + set(CPACK_NSIS_PACKAGE_NAME "HeadsetControl") + set(CPACK_NSIS_URL_INFO_ABOUT "https://github.com/Sapd/HeadsetControl") + set(CPACK_NSIS_HELP_LINK "https://github.com/Sapd/HeadsetControl/wiki") + set(CPACK_NSIS_CONTACT "git@sapd.eu") + set(CPACK_NSIS_MODIFY_PATH ON) + set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.ico") + set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.ico") + set(CPACK_NSIS_MUI_UNIICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.ico") + endif() -include(CPack) + include(CPack) + +endif()