cmake_minimum_required(VERSION 3.20) project(ngscopeclient VERSION 0.1) include_directories(cmake) #WORKAROUND needed for find_package(Vulkan) for Vulkan SDK enable_language(CXX) # Warn if platform is not 64-bit if(NOT (CMAKE_SIZEOF_VOID_P EQUAL 8)) message(WARNING "You are attempting to build on a 32-bit platform. While this may work and we may accept fixes " "upstream, it will most likely fail and is unsupported.") endif() # Check for Vulkan SDK env var and prepend it to CMAKE_PREFIX_PATH if set to prefer Vulkan SDK packages if available if(DEFINED ENV{VULKAN_SDK}) #WORKAROUND: Vulkan SDK is incompatible with MinGW due to C++ linkage if(WIN32 AND (NOT CMAKE_GENERATOR MATCHES "Visual Studio")) message(WARNING "Detected VULKAN_SDK at $ENV{VULKAN_SDK}.") message(WARNING "Compiling and linking against the Vulkan SDK is not supported with MSYS. The build system " "will ignore the SDK and you will need to have the Vulkan packages installed from the MSYS repositories.") else() message("Detected and using VULKAN_SDK at $ENV{VULKAN_SDK}") cmake_path(CONVERT "$ENV{VULKAN_SDK}" TO_CMAKE_PATH_LIST VULKAN_SDK_PATH NORMALIZE) list(PREPEND CMAKE_PREFIX_PATH ${VULKAN_SDK_PATH}) endif() endif() # Git is used for git-describe based version generation if we have it find_package(Git) #Set up versioning (with a dummy string for now if Git isn't present) if(Git_FOUND) execute_process( COMMAND ${GIT_EXECUTABLE} describe --always --tags WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE NGSCOPECLIENT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) message("Git reports scopehal-apps version ${NGSCOPECLIENT_VERSION}") execute_process( COMMAND ${GIT_EXECUTABLE} describe --always --tags --long WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE NGSCOPECLIENT_VERSION_LONG OUTPUT_STRIP_TRAILING_WHITESPACE) # TODO: if/when we have a point release, make MSI version 10x+9 # ex: 0.1.2-rc2 is 0.1.22 # ex: 0.1.2 is 0.1.29 # Ugly string parsing to make windows build happy # First path: release candidate tags if(NGSCOPECLIENT_VERSION_LONG MATCHES "v([0-9]*).([0-9]*)-rc([0-9])-([0-9]*)") set(MSI_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}.${CMAKE_MATCH_4}") # Release tags elseif(NGSCOPECLIENT_VERSION_LONG MATCHES "v([0-9]*).([0-9]*)-([0-9]*)") set(MSI_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.9.${CMAKE_MATCH_3}") endif() else() set(NGSCOPECLIENT_VERSION "unknown") set(MSI_VERSION "0.1") message("Git not detected, scopehal-apps version unknown") endif() set(PROJECT_VERSION "${NGSCOPECLIENT_VERSION}") include(CTest) # Configuration settings set(BUILD_DOCS CACHE BOOL "Build end user documentation") set(BUILD_DEVDOCS CACHE BOOL "Build developer documentation") set(ANALYZE CACHE BOOL "Run static analysis on the code, requires cppcheck and clang-analyzer to be installed") set(DISABLE_PCH CACHE BOOL "Disable precompiled headers as this may break certain configurations") set(BUILD_TESTING CACHE BOOL "Build unit tests") # Build with C++ 17 on Linux for maximum compatibility with older system # but for Windows, enable C++ 20 to bypass a deprecation warning # (workaround for https://github.com/KhronosGroup/Vulkan-Hpp/issues/2034) # even though we do not yet use any C++ 20 features in our codebase if(WIN32) set(CMAKE_CXX_STANDARD 20) else() set(CMAKE_CXX_STANDARD 17) endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) # error if compiler doesn't support c++17 set(CMAKE_CXX_EXTENSIONS OFF) # use c++17 instead of gnu++17 # PCH needs to be disabled before targets are created/added/PCH is added to them if(DISABLE_PCH) set(CMAKE_DISABLE_PRECOMPILE_HEADERS ON) endif() # Compiler warnings for GCC/Clang if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wuninitialized) add_compile_options(-Wshadow -Wpedantic -Wcast-align -Wwrite-strings) add_compile_options(-Wmissing-declarations -Wvla) add_compile_options(-Werror -Wno-error=deprecated-declarations -Wno-error=unused-parameter -Wno-error=unused-variable -Wno-error=unused-result -Wno-error=shadow) endif() # Compiler warnings specific to GCC or Clang if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13) add_compile_options(-Woverloaded-virtual=1) elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Woverloaded-virtual -Wpointer-sign -Wno-gnu-zero-variadic-macro-arguments -Wno-unknown-warning-option) endif() add_compile_options(-mtune=native) add_compile_options("$<$:-O3>") add_compile_options("$<$:-O3;-g>") add_compile_options("$<$:-Og;-D_DEBUG;-g>") add_compile_options("$<$:-O0;-D_DEBUG;-g>") # Needed for %zu/%zd on MSYS2, must be added here to avoid redefinition if(WIN32 AND (NOT MSVC)) add_compile_definitions(__USE_MINGW_ANSI_STDIO=1) endif() # Default build type if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release") endif() if(SANITIZE) add_compile_options(-fsanitize=address -fsanitize=undefined) add_link_options(-fsanitize=address -fsanitize=undefined) endif() if(WIN32) add_compile_options(-D_USE_MATH_DEFINES -D_POSIX_THREAD_SAFE_FUNCTIONS) endif() # Package detection find_package(PkgConfig MODULE REQUIRED) # yaml-cpp is used in scopehal and ngscopeclient find_package(yaml-cpp REQUIRED) #WORKAROUND Needed for Debian Bullseye, which does not provide a yaml-cpp::yaml-cpp target if(NOT TARGET yaml-cpp::yaml-cpp) find_library(YAML_CPP_LIBRARIES_FILES NAMES ${YAML_CPP_LIBRARIES}) if(YAML_CPP_LIBRARIES_FILES MATCHES ".so$") add_library(yaml-cpp::yaml-cpp SHARED IMPORTED) elseif(YAML_CPP_LIBRARIES_FILES MATCHES ".a$") add_library(yaml-cpp::yaml-cpp STATIC IMPORTED) else() message(FATAL_ERROR "Unexpected partially-installed yaml-cpp, is it installed correctly?") endif() set_property(TARGET yaml-cpp::yaml-cpp PROPERTY IMPORTED_LOCATION ${YAML_CPP_LIBRARIES_FILES}) #WORKAROUND The cmake file for yaml-cpp on debian bullseye is broken and provides a wrong YAML_CPP_INCLUDE_DIR find_path(YAML_CPP_INCLUDEFILES_DIR yaml-cpp/yaml.h REQUIRED) cmake_path(GET YAML_CPP_INCLUDEFILES_DIR PARENT_PATH YAML_CPP_INCLUDE_DIR) set_property(TARGET yaml-cpp::yaml-cpp PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${YAML_CPP_INCLUDEFILES_DIR}) endif() pkg_check_modules(SIGCXX QUIET IMPORTED_TARGET sigc++-3.0) # look for latest version first if(NOT SIGCXX_FOUND) pkg_check_modules(SIGCXX QUIET IMPORTED_TARGET sigc++-2.0) # look for older version too endif() if(NOT SIGCXX_FOUND) message(FATAL_ERROR "Unable to find any version of sigc++; this is required to build ngscopeclient.") endif() # We still use gtk on Linux for the file browser dialog in "native" mode) if(LINUX) pkg_check_modules(GTK QUIET IMPORTED_TARGET REQUIRED gtk+-3.0) pkg_search_module(WAYLAND_CLIENT wayland-client IMPORTED_TARGET GLOBAL REQUIRED) endif() #WORKAROUND Fedora 38 does not include Threads from the glslang .cmake file, fixed in Fedora 39+ find_package(Threads MODULE REQUIRED) # Configure and enable OpenMP find_package(OpenMP MODULE) if(NOT OpenMP_CXX_FOUND) message(FATAL_ERROR "ngscopeclient requires OpenMP but your C++ compiler does not appear to support it.") endif() # GLFW is required find_package(glfw3 3.2 REQUIRED) # Find Vulkan-related packages #WORKAROUND glslang does not look for SPIRV-Tools-opt but needs it on macOS, harmless elsewhere find_package(SPIRV-Tools-opt REQUIRED) if(DEFINED VULKAN_SDK_PATH) #WORKAROUND The Vulkan SDK ships incomplete cmake config files. # Work around this, which does require FindVulkan.cmake. # FindVulkan does not find glslang correctly on macOS find_package(Vulkan REQUIRED COMPONENTS glslc shaderc_combined SPIRV-Tools) find_package(glslang REQUIRED) add_library(Vulkan::Loader ALIAS Vulkan::Vulkan) else() find_package(glslang REQUIRED) find_package(VulkanHeaders REQUIRED) find_package(VulkanLoader QUIET) if(NOT VulkanLoader_FOUND) #WORKAROUND Alpine Linux has missing cmake files for VulkanLoader find_library(VulkanLoader_LIB libvulkan.so REQUIRED) cmake_path(GET VulkanLoader_LIB FILENAME VulkanLoader_LIB_NAME) add_library(Vulkan::Loader SHARED IMPORTED) set_property(TARGET Vulkan::Loader PROPERTY IMPORTED_LOCATION ${VulkanLoader_LIB}) endif() endif() # The following should work regardless of Vulkan SDK or individual components # shaderc does not provide a .cmake file, but does provide a pkgconfig file # pkgconfig file is not provided by Vulkan SDK, however pkg_check_modules(SHADERC shaderc QUIET IMPORTED_TARGET) # This is needed due to shaderc not providing a programmatic way to derive the path of the glslc executable if(SHADERC_FOUND) cmake_path(GET SHADERC_INCLUDE_DIRS PARENT_PATH SHADERC_PREFIX) endif() # This should find glslc from either shaderc or the Vulkan SDK find_program(Vulkan_GLSLC_EXECUTABLE glslc HINTS SHADERC_PREFIX) if(NOT Vulkan_GLSLC_EXECUTABLE) message(FATAL_ERROR "glslc not found. This is needed to compile shaders. Please install shaderc or load the Vulkan SDK.") else() message("-- Found glslc: ${Vulkan_GLSLC_EXECUTABLE}") endif() # This is needed due to VkFFT not using a standard path for glslang_c_interface.h get_target_property(glslang_INCLUDE_DIR glslang::glslang INTERFACE_INCLUDE_DIRECTORIES) # Find MoltenVK on macOS, no need to link it later if(APPLE) find_library(MOLTENVK_LIBRARIES NAMES MoltenVK libMoltenVK) if( NOT MOLTENVK_LIBRARIES ) message( FATAL_ERROR "scopehal-apps requires MoltenVK to be installed in order to function on macOS.") endif() endif() if(NOT WIN32) include(GNUInstallDirs) endif() # Documentation add_subdirectory("${PROJECT_SOURCE_DIR}/doc") if(NOT BUILD_DOCS) set_property(TARGET doc PROPERTY EXCLUDE_FROM_ALL ON) endif() # Static analysis if(ANALYZE) find_program(CPPCHECK_PATH cppcheck DOC "Path to cppcheck when ANALYZE is enabled") if(CPPCHECK_PATH) execute_process(COMMAND ${CPPCHECK_PATH} "--version" OUTPUT_VARIABLE CPPCHECK_VER_STR ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) string(REPLACE "Cppcheck " "" CPPCHECK_VERSION ${CPPCHECK_VER_STR}) if(CPPCHECK_VERSION VERSION_GREATER_EQUAL "2") set(CMAKE_CXX_CPPCHECK "${CPPCHECK_PATH};-DFT_USE_AUTOCONF_SIZEOF_TYPES;-D__GNUC__;--enable=warning,performance,portability;--suppress=*:*sigc*;--suppress=*:*glibmm*;--suppress=*:*gtkmm*;--inline-suppr;-q;--std=c++11") message(STATUS "Found CPPCheck: ${CPPCHECK_PATH} (found version \"${CPPCHECK_VERSION}\")") else() message(STATUS "Found CPPCheck: ${CPPCHECK_PATH} but ignored it as it was ${CPPCHECK_VERSION} < 2") endif() else() message(FATAL_ERROR "CPPCheck not found, required for ANALYZE") endif() # The actual clang-analyzer compiler wrapper doesn't get installed on $PATH, only scan-build which is useless to us find_program(CLANGANALYZER_SCANBUILD_PATH scan-build DOC "Path to clang-analyzer's scan-build tool, used as a hint to find the rest of the clang-analyzer") get_filename_component(CLANGANALYZER_SCANBUILD_BIN ${CLANGANALYZER_SCANBUILD_PATH} REALPATH) get_filename_component(CLANGANALYZER_BIN_PATH ${CLANGANALYZER_SCANBUILD_BIN} DIRECTORY) find_program(CLANGANALYZER_CXXANALYZER_PATH "c++-analyzer" HINTS "${CLANGANALYZER_BIN_PATH}/../libexec" DOC "Path to clang-analyzer's c++-analyzer") if(CLANGANALYZER_CXXANALYZER_PATH) set(CMAKE_CXX_COMPILER_LAUNCHER "${CLANGANALYZER_CXXANALYZER_PATH}") message(STATUS "Found clang-analyzer: ${CLANGANALYZER_CXXANALYZER_PATH}") else() message(FATAL_ERROR "clang-analyzer not found, required for ANALYZE") endif() endif() # Main project code add_subdirectory("${PROJECT_SOURCE_DIR}/lib/scopehal") add_subdirectory("${PROJECT_SOURCE_DIR}/lib/scopeprotocols") add_subdirectory("${PROJECT_SOURCE_DIR}/lib/xptools") add_subdirectory("${PROJECT_SOURCE_DIR}/lib/log") add_subdirectory("${PROJECT_SOURCE_DIR}/src/ngscopeclient") add_subdirectory("${PROJECT_SOURCE_DIR}/src/nativefiledialog-extended") add_subdirectory(devdoc) # Unit tests if(Git_FOUND AND BUILD_TESTING) find_package(Catch2 REQUIRED) include(Catch) #Catch2 v3.x.y have a breaking change: # Must include catch2/catch_all.hpp instead of catch2/catch.hpp # So we set a compile flag to let the code know. if(NOT Catch2_VERSION MATCHES "^[0-2]\\.") add_compile_options(-D_CATCH2_V3) endif() add_subdirectory("${PROJECT_SOURCE_DIR}/tests") endif() # Example code and other utilities, don't build on non-POSIX yet if(NOT WIN32) add_subdirectory("${PROJECT_SOURCE_DIR}/src/examples/curvetrace") #add_subdirectory("${PROJECT_SOURCE_DIR}/src/examples/usbcsv") endif() # Make sure all of our shared libraries are built relocatable set_property(TARGET scopehal PROPERTY POSITION_INDEPENDENT_CODE ON) set_property(TARGET log PROPERTY POSITION_INDEPENDENT_CODE ON) set_property(TARGET xptools PROPERTY POSITION_INDEPENDENT_CODE ON) set_property(TARGET scopeprotocols PROPERTY POSITION_INDEPENDENT_CODE ON) # CPack package generation set(CPACK_PACKAGE_NAME "ngscopeclient") set(CPACK_PACKAGE_VENDOR "ngscopeclient.org project") set(CPACK_PACKAGE_VERSION_PATCH "0+${NGSCOPECLIENT_VERSION}") set(CPACK_PACKAGE_DESCRIPTION "Cross platform T&M remote control and signal analysis suite") set(CPACK_PACKAGE_HOMEPAGE_URL "https://www.ngscopeclient.org/") set(CPACK_THREADS 0) set(CPACK_STRIP_FILES TRUE) # Figure out what distro version we're on if(LINUX) find_program(LSB_RELEASE_EXEC lsb_release REQUIRED) execute_process(COMMAND ${LSB_RELEASE_EXEC} -is OUTPUT_VARIABLE DISTRO_NAME OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY) execute_process(COMMAND ${LSB_RELEASE_EXEC} -rs OUTPUT_VARIABLE DISTRO_VER OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY) message(STATUS "Linux distribution target for packaging: name ${DISTRO_NAME}, version ${DISTRO_VER}") # Debian specific packaging config set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Andrew Zonenberg ") if(DISTRO_NAME STREQUAL "Debian") # Bookworm # Versions are based on what Bookworm is shipping as of 2024-10-30 if(DISTRO_VER STREQUAL "12") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.36-9), libpng16-16(>= 1.6.39-2), libsigc++-2.0-0v5 (>= 2.12.0-1), libyaml-cpp0.7(>= 0.7.0), libgomp1(>= 12.2.0), libvulkan1(>= 1.3.239), libglfw3(>= 3.3.8), libgtk-3-0(>= 3.24.38), zlib1g(>= 1.2.13), libhidapi-hidraw0(>= 0.13.1), liblxi1(>= 1.18), libtirpc3 (>= 1.3.3)" ) # Trixie # Versions are based on what Trixie is shipping as of 2025-09-20 elseif(DISTRO_VER STREQUAL "13") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.41-12), libpng16-16t64(>= 1.6.48), libsigc++-2.0-0v5 (>= 2.12.1), libyaml-cpp0.8(>= 0.8.0), libgomp1(>= 14.2.0-19), libvulkan1(>= 1.4.309.0), libglfw3(>= 3.4-3), libgtk-3-0t64(>= 3.24.49), zlib1g(>= 1:1.3.dfsg+really1.3.1-1), libhidapi-hidraw0(>= 0.14.0-1), liblxi1(>= 1.22-1), libtirpc3t64 (>= 1.3.6+ds-1)" ) endif() # Ubuntu specific packaging config elseif(DISTRO_NAME STREQUAL "Ubuntu") if(DISTRO_VER STREQUAL "22.04") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.35-0), libpng16-16(>= 1.6.37-3), libsigc++-2.0-0v5 (>= 2.10.4-2), libyaml-cpp0.7(>= 0.7.0), libgomp1(>= 12.2.0), libvulkan1(>= 1.3.204.1-2), libglfw3(>= 3.3.6-1), libgtk-3-0(>= 3.24.33-1), zlib1g(>= 1:1.2.11), libhidapi-hidraw0(>= 0.11.2-1), liblxi1(>= 1.16-1), libtirpc3 (>= 1.3.2-2)" ) elseif(DISTRO_VER STREQUAL "24.04") #set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.36-9), libpng16-16(>= 1.6.39-2), libsigc++-2.0-0v5 (>= 2.12.0-1), libyaml-cpp0.7(>= 0.7.0), libgomp1(>= 12.2.0), libvulkan1(>= 1.3.239), libglfw3(>= 3.3.8), libgtk-3-0(>= 3.24.38), zlib1g(>= 1.2.13), libhidapi-hidraw0(>= 0.13.1), liblxi1(>= 1.18), libtirpc3 (>= 1.3.3)" ) endif() # Unrecognized Debian-derived distro settings (assume Bookworm for now) else() set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.36-9), libpng16-16(>= 1.6.39-2), libsigc++-2.0-0v5 (>= 2.12.0-1), libyaml-cpp0.7(>= 0.7.0), libgomp1(>= 12.2.0), libvulkan1(>= 1.3.239), libglfw3(>= 3.3.8), libgtk-3-0(>= 3.24.38), zlib1g(>= 1.2.13), libhidapi-hidraw0(>= 0.13.1), liblxi1(>= 1.18), libtirpc3 (>= 1.3.3)" ) endif() endif() if(APPLE) set(CPACK_BUNDLE "ON") set(CPACK_BUNDLE_PLIST "${PROJECT_SOURCE_DIR}/src/ngscopeclient/macos/Info.plist") # Must be generated with: # iconutil -c icns ngscopeclient.iconset set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/src/ngscopeclient/icons/macos/ngscopeclient.icns") set(CPACK_PACKAGE_ICON "${PROJECT_SOURCE_DIR}/src/ngscopeclient/icons/macos/ngscopeclient.icns") set(CPACK_BUNDLE_NAME "ngscopeclient") set(CPACK_BUNDLE_STARTUP_COMMAND "${PROJECT_SOURCE_DIR}/src/ngscopeclient/macos/ngscopeclient.sh") set_target_properties(ngscopeclient PROPERTIES INSTALL_RPATH "@executable_path/../lib;/usr/local/lib;/opt/homebrew/lib") endif() # this must be at the very end *after* we've done all the config include(CPack)