CMAKE_MINIMUM_REQUIRED(VERSION 3.26) # The VERSION file is the single source of truth for the version, and JFJOCH_VERSION carries it # verbatim - including a pre-release suffix such as "-rc.161" - into the package names, the DKMS # install path and the version every binary reports. PROJECT(VERSION) cannot hold that string: it # only accepts numeric major.minor.patch, so the numeric part is cut out of the same file rather # than written out a second time, and PROJECT_VERSION can never drift from VERSION. FILE(STRINGS VERSION JFJOCH_VERSION) STRING(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" JFJOCH_VERSION_NUMERIC "${JFJOCH_VERSION}") # Oldest macOS the binaries run on (ignored elsewhere). Without it CMake takes the version of the # build machine, and a .dmg would refuse to start on anything older. 13 is what the Qt the .dmg # bundles (6.11) is built for, so a lower value only makes the linker warn about every framework; # our own needs (, ) stop at 11. Has to be set before PROJECT(). SET(CMAKE_OSX_DEPLOYMENT_TARGET "13.0" CACHE STRING "Minimum macOS version") PROJECT(jfjoch VERSION ${JFJOCH_VERSION_NUMERIC} LANGUAGES C CXX) SET(CMAKE_POLICY_DEFAULT_CMP0077 NEW) INCLUDE(GNUInstallDirs) SET(CMAKE_CXX_STANDARD 20) SET(CMAKE_CXX_STANDARD_REQUIRED True) # The Metal Bragg integration engine (image_analysis/bragg_integration) is Objective-C++. Enabled here, # above every target that links it, as enable_language asks. IF (APPLE) ENABLE_LANGUAGE(OBJCXX) SET(CMAKE_OBJCXX_STANDARD 20) SET(CMAKE_OBJCXX_STANDARD_REQUIRED True) ENDIF() # MSVC gets C++ exception handling only from /EHsc, and CMake puts that flag in the DEFAULT value of # CMAKE_CXX_FLAGS ("/DWIN32 /D_WINDOWS /GR /EHsc"), which is used only when the configure does not # set CMAKE_CXX_FLAGS itself. CI does (-DCMAKE_CXX_FLAGS="/arch:AVX"), and that drops /EHsc: MSVC then # does not unwind the stack on a throw (no destructors run) and does not define _CPPUNWIND, so # Catch2 compiles itself with CATCH_CONFIG_DISABLE_EXCEPTIONS. Adding it here, before any # FetchContent, reaches every C++ target whatever CMAKE_CXX_FLAGS holds. CUDA gets it from its own # default CMAKE_CUDA_FLAGS, which nothing overrides. # # CUDA's CCCL headers (cub, thrust, libcu++) stop with an #error under MSVC's traditional # preprocessor, which miscompiles their macros. nvcc preprocesses and compiles host code with cl.exe, # so the standard-conforming preprocessor is switched on for .cu sources here, reaching every CUDA # target, fetched ones included (VS 2019 16.5 or newer). ffbidx already sets the same for its own # CUB-using kernel, which is why the other CUDA targets never needed it. C++ sources keep the # default: none includes a CCCL header, and the switch would change the preprocessing of Qt, the # Windows SDK and every fetched dependency with nothing to gain. IF (MSVC) ADD_COMPILE_OPTIONS($<$:/EHsc>) ADD_COMPILE_OPTIONS($<$:-Xcompiler=/Zc:preprocessor>) ENDIF() # No contraction of a*b+c into a fused multiply-add. GCC contracts whenever the target has FMA, so a # -march=x86-64-v3 build (CI, production) and a baseline build rounded differently, and knife-edge # decisions - which candidate cell a first pass indexes with, a resolution cut, a symmetry gate - # came out differently from the same source and data. Off, every x86-64 level computes the same bits # (measured: identical p.mtz on nine battery sets, no measurable time cost), and aarch64, where FMA is # always there and GCC contracts by default, follows the same arithmetic. MSVC does not contract under # its default /fp:precise. Host code of .cu sources gets it through nvcc, and the Metal kernels say so in # their source (BraggIntegrationEngineMetal.mm). IF (NOT MSVC) ADD_COMPILE_OPTIONS($<$:-ffp-contract=off>) ADD_COMPILE_OPTIONS($<$:-Xcompiler=-ffp-contract=off>) ENDIF() # The same for device code. nvcc contracts by default and does it separately for every architecture # it compiles for, so a T4 (sm_75) and an RTX 5080 (sm_120) ran the same kernels to different bits # and a run that hit a knife edge came out differently on the two machines. Off, the architectures # compute the same bits (measured: identical p.hkl from sm_75 and sm_120 builds, no measurable time cost). ADD_COMPILE_OPTIONS($<$:--fmad=false>) SET(JFJOCH_WRITER_ONLY OFF CACHE BOOL "Compile HDF5 writer only") SET(JFJOCH_INSTALL_DRIVER_SOURCE OFF CACHE BOOL "Install kernel driver source (ignored if building writer only; necessary for RPM building)") SET(JFJOCH_USE_CUDA ON CACHE BOOL "Compile Jungfraujoch with CUDA") SET(JFJOCH_VIEWER_BUILD OFF CACHE BOOL "Compile Jungfraujoch viewer") # The portable products come out of two build modes; every artifact holds exactly one program: # # JFJOCH_VIEWER_ONLY jfjoch_viewer .tar.gz (Linux) / installer (Windows) / .dmg (macOS) # and also rugnux (.tar.gz; .zip on Windows) and, on Linux only, the # XDS plugin (.tar.gz) # JFJOCH_RUGNUX_ONLY rugnux .tar.gz (Linux, incl. cross-built aarch64) / .zip (Windows) # # The viewer build compiles everything rugnux needs anyway, so one build packs all three; the # rugnux-only mode remains for where there is no Qt (the aarch64 cross build). # # They used to be one package: the viewer tarball also carried rugnux and two CLI tools, which made # the download confusing (a GUI package containing a batch processor) and meant a rugnux user had to # take Qt with it. Splitting them is possible because cuFFT is now linked statically, so neither # artifact has to ship a runtime library beside its executable. SET(JFJOCH_RUGNUX_ONLY OFF CACHE BOOL "Compile only rugnux, the offline analysis CLI (no Qt)") # macOS only: the Developer ID Application certificate to sign the shipped program with at install # time - hardened runtime and a secure timestamp, which is what Apple's notarization demands. Given # as the certificate's SHA-1 hash, the 40 hex digits `security find-identity -v -p codesigning` # prints in front of the name: the name itself ("Developer ID Application: ()") has # spaces, and Qt's generated deploy script would split it into separate macdeployqt arguments. Empty # (the default) keeps the ad-hoc signature, which runs locally but is refused by Gatekeeper after a # download. Notarizing the packed artefact is a separate step after cpack: tools/notarize_macos.sh. SET(JFJOCH_MACOS_SIGNING_IDENTITY "" CACHE STRING "macOS: SHA-1 of the Developer ID Application certificate to codesign with (empty: ad-hoc)") # Only the viewer (and its portable dependency tree) is supported on Windows and macOS -- the # broker/receiver/FPGA/writer server stack is Linux-only. Force viewer-only on those platforms so a # plain configure builds the right subset; on Linux it remains a user-togglable option. A rugnux-only # build is portable too (it is the same subtree minus viewer/), so asking for it explicitly wins. # TRUE for either portable product. Both build the same subtree of analysis libraries and neither # wants any server-side target -- the broker service, the writer executables, the FPGA/detector # tools or the frontend -- so the guards that exclude those key on this rather than on one product's # option. Set below, once JFJOCH_VIEWER_ONLY has been resolved. IF ((WIN32 OR APPLE) AND NOT JFJOCH_RUGNUX_ONLY) SET(JFJOCH_VIEWER_ONLY ON CACHE BOOL "Compile only jfjoch_viewer and its dependencies" FORCE) ELSEIF (JFJOCH_RUGNUX_ONLY) SET(JFJOCH_VIEWER_ONLY OFF CACHE BOOL "Compile only jfjoch_viewer and its dependencies" FORCE) ELSE() SET(JFJOCH_VIEWER_ONLY OFF CACHE BOOL "Compile only jfjoch_viewer and its dependencies") ENDIF() IF (JFJOCH_VIEWER_ONLY OR JFJOCH_RUGNUX_ONLY) SET(JFJOCH_PORTABLE_ONLY ON) ELSE() SET(JFJOCH_PORTABLE_ONLY OFF) ENDIF() # GPU architectures to generate device code for. Overridable so a build that targets one machine # can pin its own set instead of paying for the full fat binary -- e.g. a cross build for # GB10 (DGX Spark, sm_121) and GH200 (Grace Hopper, sm_90) passes -DJFJOCH_CUDA_ARCHITECTURES="90;121". SET(JFJOCH_CUDA_ARCHITECTURES "" CACHE STRING "Override the CUDA architecture list (empty = the default set below)") OPTION(SLS9 "Build with sls_detector_package v9.2.0" OFF) SET(BUILD_SHARED_LIBS OFF) SET(BUILD_TESTING OFF) SET(ZSTD_LEGACY_SUPPORT OFF) SET(ZSTD_MULTITHREAD_SUPPORT OFF) SET(ZSTD_BUILD_PROGRAMS OFF) SET(ZSTD_BUILD_SHARED OFF) SET(SLS_USE_RECEIVER OFF) SET(SLS_USE_RECEIVER_BINARIES OFF) SET(SLS_BUILD_SHARED_LIBRARIES OFF) SET(BUILD_FAST_INDEXER OFF) SET(BUILD_FAST_INDEXER_STATIC ON) INCLUDE(CheckLanguage) INCLUDE(CheckIncludeFile) # Locate nvcc ourselves when it isn't already pinned (-DCMAKE_CUDA_COMPILER / $CUDACXX). CHECK_LANGUAGE # below only searches PATH, which is missed routinely on Windows and intermittently on Linux; the # standard CUDA install locations (CUDA_PATH on Windows, /usr/local/cuda on Linux) cover both, so the # compiler need not be passed by hand. Only set it when actually found, leaving CHECK_LANGUAGE to run # its normal detection otherwise. IF (JFJOCH_USE_CUDA AND NOT CMAKE_CUDA_COMPILER AND NOT DEFINED ENV{CUDACXX}) FIND_PROGRAM(_jfjoch_nvcc nvcc HINTS ENV CUDA_PATH ENV CUDA_HOME ENV CUDA_ROOT /usr/local/cuda /opt/cuda PATH_SUFFIXES bin) IF (_jfjoch_nvcc) SET(CMAKE_CUDA_COMPILER "${_jfjoch_nvcc}") ENDIF() ENDIF() CHECK_LANGUAGE(CUDA) # GPU architectures to generate device code for: Turing (T4), Ampere (A100; RTX A4000), Ada (L4), # Hopper, and Blackwell in both its data-centre and consumer forms. A bare entry emits SASS *and* # PTX for that architecture, so the newest one here also covers any future GPU: the driver # JIT-compiles its PTX on first launch. Volta (V100) is added below, once the nvcc version is known. IF (JFJOCH_CUDA_ARCHITECTURES) SET(CMAKE_CUDA_ARCHITECTURES ${JFJOCH_CUDA_ARCHITECTURES}) ELSE() SET(CMAKE_CUDA_ARCHITECTURES 75 80 86 89 90 100 120) ENDIF() SET(CMAKE_CUDA_STANDARD 20) SET(CMAKE_CUDA_STANDARD_REQUIRED True) SET(CMAKE_CUDA_FLAGS_RELEASE "-O3 -DNDEBUG -lineinfo") SET(CMAKE_CUDA_RUNTIME_LIBRARY Static) SET(JFJOCH_CUDA_AVAILABLE OFF) IF (CMAKE_CUDA_COMPILER) IF (JFJOCH_USE_CUDA) ENABLE_LANGUAGE(CUDA) MESSAGE(STATUS "CUDA VERSION: ${CMAKE_CUDA_COMPILER_VERSION}") IF (CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL "12.8") FIND_PACKAGE(CUDAToolkit REQUIRED) ADD_COMPILE_DEFINITIONS(JFJOCH_USE_CUDA) SET(JFJOCH_CUDA_AVAILABLE ON) # Blackwell GB10 (DGX Spark) is sm_121, only known to nvcc >= 12.9; add it there so the # binary runs natively on Spark instead of having the driver JIT sm_120 PTX for it. IF (CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL "12.9" AND NOT JFJOCH_CUDA_ARCHITECTURES) LIST(APPEND CMAKE_CUDA_ARCHITECTURES 121) ENDIF() # Volta (V100) is sm_70, and PTX only ever JITs *forwards* - without an sm_70 entry a # V100 has no runnable code in the fatbin at all and every launch fails with "no kernel # image is available for execution on the device". CUDA 13 removed offline compilation # for Volta, so only a CUDA 12 toolkit can emit it; 12.8/12.9 still do, but warn on # every .cu, hence -Wno-deprecated-gpu-targets. Volta is the floor - older GPUs are # not in use here. IF (CMAKE_CUDA_COMPILER_VERSION VERSION_LESS "13.0" AND NOT JFJOCH_CUDA_ARCHITECTURES) LIST(APPEND CMAKE_CUDA_ARCHITECTURES 70) SET(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Wno-deprecated-gpu-targets") ENDIF() ELSE() MESSAGE(WARNING "CUDA older than 12.8 not supported") ENDIF() ELSE() MESSAGE(WARNING "CUDA Available, but disabled by user") ENDIF() ENDIF() INCLUDE_DIRECTORIES(include) include(FetchContent) # Dependencies are fetched as PINNED TARBALLS, not git clones. GitHub throttles unauthenticated # git-upload-pack and picks its targets by TLS fingerprint, so a clone answers 200 to the initial # ref advertisement and then 401 to the fetch itself; git asks for a password, finds no terminal, # and the configure stops dead with no error. Which images it hits follows their OpenSSL version, # which is why it looks like a broken build image rather than a rate limit. The archive endpoint is # not part of that campaign, and a tarball is a fraction of the transfer (hdf5: 40 MB against a # 271 MB shallow clone), so it is both the fix and the cheaper fetch. # # Every URL is pinned by tag or commit and its bytes by URL_HASH, so a silently changed archive # fails the configure instead of entering the build. To bump one: change the version in the URL and # replace the hash with `sha256sum` of the new tarball. # # Ceres needs one extra step for its abseil submodule; see image_analysis/CMakeLists.txt. # zlib, via zlib-ng in its zlib-compatible mode: same zlib.h, same API and symbol names, faster # deflate. It used to be the one compression library the project asked the host for; providing it # here is what makes a build need no zlib-devel. # # It is built HERE, during the configure, into a small install prefix in the build tree, and # ZLIB_ROOT then points every find_package(ZLIB) at it -- ours and those of HDF5, libtiff, # cpp-httplib. Two alternatives were rejected: # # * FetchContent OVERRIDE_FIND_PACKAGE is the mechanism banned by the Eigen note further down # (it segfaults the CMake bundled with Visual Studio), and a subproject's find_package(ZLIB) # resolving an override through a nested FetchContent_MakeAvailable is exactly the faulting # pattern. # * Adding zlib-ng with add_subdirectory and aliasing its target to ZLIB::ZLIB looks simpler, but # ZLIB::ZLIB is then an alias of a target built in this project, and a consumer that puts # ZLIB::ZLIB in CMAKE_REQUIRED_LIBRARIES for its feature checks (libcurl did, when the viewer used # it) breaks: try_compile only accepts IMPORTED targets, so that is a hard configure error, not a # degraded check (verified). # # Building it first and finding it as an ordinary installed library avoids both: every consumer # runs the stock FindZLIB module against a real libz.a and gets a genuine imported target. # # Passing -DZLIB_ROOT= skips the build below and uses the zlib found there instead. IF (NOT ZLIB_ROOT) SET(ZLIB_ROOT "${CMAKE_BINARY_DIR}/zlib-ng-install") IF (NOT EXISTS "${ZLIB_ROOT}/include/zlib.h") FetchContent_Declare(zlib URL https://github.com/zlib-ng/zlib-ng/archive/refs/tags/2.3.3.tar.gz URL_HASH SHA256=f9c65aa9c852eb8255b636fd9f07ce1c406f061ec19a2e7d508b318ca0c907d1 SOURCE_SUBDIR cmake) # no CMakeLists.txt there: populate only, do not add_subdirectory FetchContent_MakeAvailable(zlib) # The nested configure inherits the toolchain, not the environment: pass the compiler, the # flags and the platform selection explicitly, or a cross build (or an MSVC build with a # non-default platform/toolset/runtime) would silently produce a library for the wrong one. SET(JFJOCH_ZLIB_CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${ZLIB_ROOT} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} -DCMAKE_POSITION_INDEPENDENT_CODE=ON # xds-plugin is a shared library -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTING=OFF -DINSTALL_UTILS=OFF -DZLIB_COMPAT=ON # provide zlib.h and the zlib API, not zlib-ng.h -DWITH_GZFILEOP=ON # gzopen/gzread: GEMMI reads gzipped MTZ and CIF -DWITH_NATIVE_INSTRUCTIONS=OFF) # portable binary; SIMD is chosen at run time SET(JFJOCH_ZLIB_FORWARD CMAKE_TOOLCHAIN_FILE CMAKE_MSVC_RUNTIME_LIBRARY CMAKE_OSX_ARCHITECTURES CMAKE_OSX_SYSROOT CMAKE_OSX_DEPLOYMENT_TARGET) # CMAKE_SYSTEM_NAME only when actually cross-compiling: setting it is what PUTS a build into # cross mode, so forwarding it unconditionally would make even a native zlib-ng think it is # being cross-compiled. IF (CMAKE_CROSSCOMPILING) LIST(APPEND JFJOCH_ZLIB_FORWARD CMAKE_SYSTEM_NAME CMAKE_SYSTEM_PROCESSOR) ENDIF() FOREACH (_var IN LISTS JFJOCH_ZLIB_FORWARD) IF (${_var}) LIST(APPEND JFJOCH_ZLIB_CMAKE_ARGS "-D${_var}=${${_var}}") ENDIF() ENDFOREACH() IF (CMAKE_GENERATOR_PLATFORM) LIST(APPEND JFJOCH_ZLIB_CMAKE_ARGS -A "${CMAKE_GENERATOR_PLATFORM}") ENDIF() IF (CMAKE_GENERATOR_TOOLSET) LIST(APPEND JFJOCH_ZLIB_CMAKE_ARGS -T "${CMAKE_GENERATOR_TOOLSET}") ENDIF() # Single-config generators pick the configuration at configure time, multi-config ones at # build time; give each the one it reads and keep both on this build's configuration. IF (CMAKE_CONFIGURATION_TYPES OR NOT CMAKE_BUILD_TYPE) SET(JFJOCH_ZLIB_CONFIG Release) ELSE() SET(JFJOCH_ZLIB_CONFIG ${CMAKE_BUILD_TYPE}) ENDIF() IF (NOT CMAKE_CONFIGURATION_TYPES) LIST(APPEND JFJOCH_ZLIB_CMAKE_ARGS -DCMAKE_BUILD_TYPE=${JFJOCH_ZLIB_CONFIG}) ENDIF() MESSAGE(STATUS "Building zlib-ng (zlib-compatible) into ${ZLIB_ROOT}") EXECUTE_PROCESS( COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" ${JFJOCH_ZLIB_CMAKE_ARGS} -S "${zlib_SOURCE_DIR}" -B "${zlib_BINARY_DIR}" RESULT_VARIABLE JFJOCH_ZLIB_RESULT OUTPUT_VARIABLE JFJOCH_ZLIB_LOG ERROR_VARIABLE JFJOCH_ZLIB_LOG) IF (JFJOCH_ZLIB_RESULT EQUAL 0) EXECUTE_PROCESS( COMMAND ${CMAKE_COMMAND} --build "${zlib_BINARY_DIR}" --config ${JFJOCH_ZLIB_CONFIG} --target install RESULT_VARIABLE JFJOCH_ZLIB_RESULT OUTPUT_VARIABLE JFJOCH_ZLIB_LOG ERROR_VARIABLE JFJOCH_ZLIB_LOG) ENDIF() IF (NOT JFJOCH_ZLIB_RESULT EQUAL 0) MESSAGE(FATAL_ERROR "Building the bundled zlib-ng failed:\n${JFJOCH_ZLIB_LOG}\n" "Re-run the configure with -DZLIB_ROOT= to use a zlib " "already on this machine.") ENDIF() ENDIF() ENDIF() SET(ZLIB_USE_STATIC_LIBS TRUE) FIND_PACKAGE(ZLIB REQUIRED) SET(HDF5_ENABLE_SZIP_SUPPORT OFF CACHE BOOL "" FORCE) SET(HDF5_ENABLE_SZIP_ENCODING OFF CACHE BOOL "" FORCE) SET(HDF5_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) SET(HDF5_BUILD_CPP_LIB OFF CACHE BOOL "" FORCE) SET(HDF5_ENABLE_ZLIB_SUPPORT ON CACHE BOOL "" FORCE) SET(HDF5_EXTERNALLY_CONFIGURED 1 CACHE BOOL "" FORCE) SET(SPDLOG_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) SET(SPDLOG_BUILD_TESTS OFF CACHE BOOL "" FORCE) SET(SPDLOG_BUILD_BENCH OFF CACHE BOOL "" FORCE) set(SPDLOG_BUILD_SHARED OFF CACHE BOOL "" FORCE) SET(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) SET(HTTPLIB_USE_NON_BLOCKING_GETADDRINFO OFF CACHE BOOL "" FORCE) SET(HTTPLIB_REQUIRE_ZLIB ON CACHE BOOL "" FORCE) FetchContent_Declare( spdlog URL https://github.com/gabime/spdlog/archive/refs/tags/v1.17.0.tar.gz URL_HASH SHA256=d8862955c6d74e5846b3f580b1605d2428b11d97a410d86e2fb13e857cd3a744 EXCLUDE_FROM_ALL ) FetchContent_Declare( zstd URL https://github.com/facebook/zstd/archive/refs/tags/v1.5.7.tar.gz URL_HASH SHA256=37d7284556b20954e56e1ca85b80226768902e2edabd3b649e9e72c0c9012ee3 SOURCE_SUBDIR build/cmake EXCLUDE_FROM_ALL ) FetchContent_Declare(hdf5 URL https://github.com/HDFGroup/hdf5/archive/refs/tags/2.2.0.tar.gz URL_HASH SHA256=5b8d75125ae7b4fef55d3b39e8d3dbfd238cdf63b6518afd67fa69ac80f06542 EXCLUDE_FROM_ALL) SET(SLS_DETECTOR_VERSION "8.0.2") SET(SLS_DETECTOR_SHA256 "783b557ebff281a537ec70a20a775dd412dd345bfcefff85988ecfafe703222a") IF(SLS9) SET(SLS_DETECTOR_VERSION "9.2.0") SET(SLS_DETECTOR_SHA256 "020a7791934e5a9d034cfa4c1c69df7109488ab2f80a4e7fca07b3e51978933f") ENDIF() FetchContent_Declare( sls_detector_package URL https://github.com/slsdetectorgroup/slsDetectorPackage/archive/refs/tags/${SLS_DETECTOR_VERSION}.tar.gz URL_HASH SHA256=${SLS_DETECTOR_SHA256} EXCLUDE_FROM_ALL ) FetchContent_Declare( catch2 URL https://github.com/catchorg/Catch2/archive/refs/tags/v3.16.0.tar.gz URL_HASH SHA256=0957cae5821b17ce07f0833aaa52b5137643a8382203221f363a8303c109af34 EXCLUDE_FROM_ALL ) FetchContent_Declare( httplib URL https://github.com/yhirose/cpp-httplib/archive/refs/tags/v0.56.0.tar.gz URL_HASH SHA256=41f214d844e3b9117b734a8cc305b6141c5b47d63009681944e03bc31d92b9ca EXCLUDE_FROM_ALL ) # httplib enables Brotli content-encoding whenever it finds system Brotli; we don't use it # (gzip/zlib is enough for the broker), so disable it to avoid linking libbrotli. SET(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF CACHE BOOL "" FORCE) # httplib auto-enables HTTPS by linking system OpenSSL whenever it finds it. The broker serves # plain HTTP only (TLS, if ever wanted, is terminated by a front reverse proxy), so disable it -- # otherwise jfjoch_broker would pull in libssl/libcrypto for no reason. SET(HTTPLIB_USE_OPENSSL_IF_AVAILABLE OFF CACHE BOOL "" FORCE) # ZeroMQ (libzmq): fetch it here, at the top level, so that THIS project - not # slsDetectorPackage - controls the version. slsDetectorPackage bundles its own libzmq # archive, but only populates it behind `if(NOT libzmq_POPULATED)`; by making libzmq # available before sls below, sls reuses this copy and a single libzmq-static target is # built (no duplicate target / double-symbol clash). A future viewer-only / Windows build # (which does not build sls) fetches the same libzmq standalone. SET(BUILD_SHARED OFF CACHE BOOL "" FORCE) # libzmq: static only (matches sls) SET(BUILD_TESTS OFF CACHE BOOL "" FORCE) # libzmq: no test build SET(WITH_PERF_TOOL OFF CACHE BOOL "" FORCE) # libzmq: no perf tools SET(WITH_DOCS OFF CACHE BOOL "" FORCE) # libzmq: no docs SET(ENABLE_CPACK OFF CACHE BOOL "" FORCE) # libzmq: no CPack injection FetchContent_Declare( libzmq URL https://github.com/zeromq/libzmq/archive/refs/tags/v4.3.5.tar.gz URL_HASH SHA256=6c972d1e6a91a0ecd79c3236f04cf0126f2f4dfbbad407d72b4606a7ba93f9c6 EXCLUDE_FROM_ALL ) # CMake >= 4.0 (e.g. the 4.x bundled with Visual Studio 2026) makes any # cmake_minimum_required(VERSION < 3.5) a fatal error. Some fetched dependencies still # declare such old floors (libzmq: 3.0.2), so raise the policy-version floor for the # FetchContent subprojects. Ignored (harmless) on CMake < 3.30, which lacks this variable. SET(CMAKE_POLICY_VERSION_MINIMUM 3.5) # libzmq must be made available BEFORE sls_detector_package for the override above to take effect. FetchContent_MakeAvailable(libzmq) IF (JFJOCH_PORTABLE_ONLY) # A portable build still needs zstd/hdf5/spdlog/httplib (JFJochReader uses httplib), and catch2 # for jfjoch_portable_test. Only sls_detector_package (detector) is not built here -- it does # not configure under MSVC. FetchContent_MakeAvailable(zstd catch2 hdf5 spdlog httplib) ELSE() FetchContent_MakeAvailable(zstd sls_detector_package catch2 hdf5 spdlog httplib) ENDIF() # libtiff (used by JFJochPreview in every build mode): build it ourselves. Its C++ binding # (tiffxx) is packaged inconsistently across distros (missing on Rocky 9) and absent on # Windows. Library only; the SET()s below are libtiff's own codec/tool switches. # We only need DEFLATE (zlib) + the internal LZW codec, matching what Python tifffile writes # by default. The remaining codecs default to ON whenever their library is found on the build # host, so each must be explicitly turned OFF to keep the dependency set minimal -- otherwise # webp pulls in libwebp + libsharpyuv and lerc pulls in libLerc. SET(jbig OFF) SET(zstd OFF) SET(lzma OFF) SET(jpeg OFF) SET(old-jpeg OFF) SET(webp OFF) SET(lerc OFF) SET(tiff-tools OFF) SET(tiff-tests OFF) FetchContent_Declare(tiff URL https://gitlab.com/libtiff/libtiff/-/archive/v4.7.2/libtiff-v4.7.2.tar.gz URL_HASH SHA256=62b15f44c10106065211efa22a2dc77937bd97ee1f6e3afd2c9659010de112eb EXCLUDE_FROM_ALL) FetchContent_MakeAvailable(tiff) # FFTW single precision (target fftw3f): the CPU fallback indexer, enables JFJOCH_USE_FFTW. # Built from the release tarball -- the git repo ships no pre-generated codelets (needs the # OCaml genfft). SET(ENABLE_FLOAT ON CACHE BOOL "" FORCE) SET(BUILD_TESTS OFF CACHE BOOL "" FORCE) # SIMD codelets are runtime-dispatched (cpuid), so enabling AVX2 does NOT require an AVX2 CPU # -- FFTW falls back at runtime. The codelet sets are arch-specific, so guard by processor: # x86 gets SSE2/AVX/AVX2, aarch64 (Apple Silicon, Grace on DGX Spark) gets NEON - added below the # FetchContent, as FFTW's CMake build has no switch for it; other arches build scalar. IF (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64|amd64") SET(ENABLE_SSE2 ON CACHE BOOL "" FORCE) SET(ENABLE_AVX ON CACHE BOOL "" FORCE) SET(ENABLE_AVX2 ON CACHE BOOL "" FORCE) ENDIF() FetchContent_Declare(fftw URL https://www.fftw.org/fftw-3.3.10.tar.gz URL_HASH SHA256=56c932549852cddcfafdab3820b0200c7742675be92179e59e6215b340e26467 EXCLUDE_FROM_ALL) FetchContent_MakeAvailable(fftw) # FFTW exposes fftw3.h only via $, so consuming fftw3f straight from # the build tree (FetchContent) leaves the header unreachable. On Linux a system-installed # fftw3.h masks this; on Windows it does not. Add the source-tree api/ dir for build-tree use. TARGET_INCLUDE_DIRECTORIES(fftw3f INTERFACE $) # NEON: only FFTW's autotools build knows it (--enable-neon); its CMake build silently builds scalar # code on ARM (and ignores an ENABLE_NEON, as this file once set). So the NEON codelets are added # the way its CMakeLists adds the SSE2/AVX ones: the sources, and HAVE_NEON so that dft/conf.c and # rdft/conf.c register them - as a definition, because its config.h template carries HAVE_NEON only # as a comment. NEON is part of every aarch64 CPU, so no compiler flag is needed. It is single # precision, which is all that is built here. Measured on Apple Silicon against the scalar build # (same results to 1e-8): the indexer's batched 1D r2c 1.8x faster at a power-of-two length and 1.3x # at 730, the 2D r2c of the beam-centre search 1.9x, a 128^3 c2r map 2.6x. IF (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64") FILE(GLOB _fftw_neon_sources ${fftw_SOURCE_DIR}/dft/simd/neon/*.c ${fftw_SOURCE_DIR}/rdft/simd/neon/*.c) TARGET_SOURCES(fftw3f PRIVATE ${_fftw_neon_sources}) TARGET_COMPILE_DEFINITIONS(fftw3f PRIVATE HAVE_NEON=1) ENDIF() ADD_COMPILE_DEFINITIONS(JFJOCH_USE_FFTW) # Eigen (header-only), consumed by the analysis libraries directly and by Ceres and ffbidx through # their own find_package(Eigen3). The headers are fetched here and a two-file config package for # them is written into the build tree; Eigen3_DIR then points at it, so every find_package(Eigen3) # in the build -- ours, Ceres', ffbidx' -- resolves through the ordinary config path and gets this # one copy. Nothing needs Eigen's own CMake, so the source is populated but never added. # # The obvious route, FetchContent_Declare(Eigen3 ... OVERRIDE_FIND_PACKAGE), IS BANNED and must not # be reintroduced: it makes the CMake bundled with Visual Studio (cmake 4.x "-msvc") intermittently # SEGFAULT during configure -- ~1 in 3 fresh configures, and 100% with Ceres CUDA on. The fault is # in CMake's own FetchContent variable-stack cleanup, reached when Ceres' find_package(Eigen3) # resolves the override through a nested FetchContent_MakeAvailable. Stock Kitware CMake runs the # identical scripts fine, so it is a bug in the VS-bundled cmake binary. A generated config package # never enters that machinery: find_package finds an ordinary Eigen3Config.cmake on disk, exactly as # it would for an Eigen installed on the system. # # Passing -DEigen3_DIR= skips the fetch and uses the Eigen whose config package lives there. SET(JFJOCH_EIGEN_VERSION 3.4.1) IF (Eigen3_DIR) MESSAGE(STATUS "Eigen3_DIR is set (${Eigen3_DIR}) -- using that Eigen instead of the bundled one") ELSE() FetchContent_Declare(eigen3 URL https://gitlab.com/libeigen/eigen/-/archive/${JFJOCH_EIGEN_VERSION}/eigen-${JFJOCH_EIGEN_VERSION}.tar.gz URL_HASH SHA256=b93c667d1b69265cdb4d9f30ec21f8facbbe8b307cf34c0b9942834c6d4fdbe2 SOURCE_SUBDIR cmake) # no CMakeLists.txt there: populate only, do not add_subdirectory FetchContent_MakeAvailable(eigen3) SET(JFJOCH_EIGEN_CONFIG_DIR "${CMAKE_BINARY_DIR}/eigen3-config") FILE(WRITE "${JFJOCH_EIGEN_CONFIG_DIR}/Eigen3Config.cmake" "# Generated by the Jungfraujoch build -- do not edit.\n" "if (NOT TARGET Eigen3::Eigen)\n" " add_library(Eigen3::Eigen INTERFACE IMPORTED GLOBAL)\n" " set_target_properties(Eigen3::Eigen PROPERTIES\n" " INTERFACE_INCLUDE_DIRECTORIES \"${eigen3_SOURCE_DIR}\")\n" "endif()\n" "set(EIGEN3_INCLUDE_DIR \"${eigen3_SOURCE_DIR}\")\n" "set(EIGEN3_INCLUDE_DIRS \"${eigen3_SOURCE_DIR}\")\n" "set(EIGEN3_VERSION_STRING \"${JFJOCH_EIGEN_VERSION}\")\n" "set(Eigen3_FOUND TRUE)\n") # AnyNewerVersion, not Eigen's own SameMajorVersion rule: Ceres asks for a version RANGE, # find_package(Eigen3 3.3.4...5 NO_MODULE), and a same-major file declines any range whose two # endpoints differ in major version. Eigen's own config file does exactly that, which is how a # build could end up compiling Ceres against one Eigen and everything else against another. INCLUDE(CMakePackageConfigHelpers) WRITE_BASIC_PACKAGE_VERSION_FILE("${JFJOCH_EIGEN_CONFIG_DIR}/Eigen3ConfigVersion.cmake" VERSION ${JFJOCH_EIGEN_VERSION} COMPATIBILITY AnyNewerVersion ARCH_INDEPENDENT) SET(Eigen3_DIR "${JFJOCH_EIGEN_CONFIG_DIR}") ENDIF() # getopt/getopt_long shim for Windows: the MSVC CRT has no . Vendored OpenBSD/NetBSD # implementation (BSD-licensed). Built only on Windows and defined here, before the subdirectories, # so the portable CLI tools in tools/ can link it. A no-op on Linux/macOS, where getopt lives in # libc (macOS uses the system getopt even though it is also a forced viewer-only platform). IF (WIN32) ADD_LIBRARY(wingetopt STATIC tools/wingetopt/getopt.c tools/wingetopt/getopt.h) TARGET_INCLUDE_DIRECTORIES(wingetopt PUBLIC tools/wingetopt) ENDIF() # Mbed TLS: the TLS backend of cpp-httplib in the jfjoch_viewer HTTP client (viewer/JFJochHttpReader). # Fetched and STATICALLY linked ONLY for viewer builds, so the broker/writer never pull in a TLS # stack. One backend on every platform: it builds with MSVC and needs nothing from the host, unlike # OpenSSL, which the macOS SDK does not have. cpp-httplib loads the trusted roots from the OS store # (Windows certificate store, macOS Keychain, the CA bundle on Linux). The release tarball, not # the GitHub archive: only it carries the tf-psa-crypto and framework submodules. IF (JFJOCH_VIEWER_BUILD OR JFJOCH_VIEWER_ONLY) SET(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE) SET(ENABLE_TESTING OFF CACHE BOOL "" FORCE) SET(MBEDTLS_FATAL_WARNINGS OFF CACHE BOOL "" FORCE) SET(TF_PSA_CRYPTO_FATAL_WARNINGS OFF CACHE BOOL "" FORCE) SET(USE_STATIC_MBEDTLS_LIBRARY ON CACHE BOOL "" FORCE) SET(USE_SHARED_MBEDTLS_LIBRARY OFF CACHE BOOL "" FORCE) SET(USE_SHARED_TF_PSA_CRYPTO_LIBRARY OFF CACHE BOOL "" FORCE) FetchContent_Declare(mbedtls URL https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-4.2.0/mbedtls-4.2.0.tar.bz2 URL_HASH SHA256=2bed9d713b4668f76553b097e72b8aa30bc8f112a940d7ae228d524bbde6ffea EXCLUDE_FROM_ALL) FetchContent_MakeAvailable(mbedtls) ENDIF() # The notices every package ships (installed at the end of this file). Set before the subdirectories # because the macOS viewer puts them into its .app bundle itself - see viewer/CMakeLists.txt. SET(JFJOCH_NOTICE_FILES ${CMAKE_SOURCE_DIR}/LICENSE ${CMAKE_SOURCE_DIR}/THIRD_PARTY_NOTICES.md ${CMAKE_SOURCE_DIR}/docs/ACKNOWLEDGEMENT.md) IF (JFJOCH_RUGNUX_ONLY) # The viewer-only subtree minus viewer/ and tools/: just rugnux and what it links. broker/ is # here for JFJochAPI alone (JFJochReader links it); its service targets are gated out. preview/ # arrives through JFJochWriter -> JFJochPreview, so libjpeg-turbo and libtiff are built too. ADD_SUBDIRECTORY(jungfrau) ADD_SUBDIRECTORY(compression) ADD_SUBDIRECTORY(common) ADD_SUBDIRECTORY(gemmi_gph) ADD_SUBDIRECTORY(frame_serialize) ADD_SUBDIRECTORY(preview) ADD_SUBDIRECTORY(writer) ADD_SUBDIRECTORY(image_analysis) ADD_SUBDIRECTORY(broker) ADD_SUBDIRECTORY(reader) ADD_SUBDIRECTORY(rugnux) ADD_SUBDIRECTORY(tests) # jfjoch_portable_test only ELSEIF (JFJOCH_VIEWER_ONLY) # Minimal subtree: jfjoch_viewer and only the libraries it transitively links. # (broker here provides JFJochAPI only; its service targets are gated out.) ADD_SUBDIRECTORY(jungfrau) ADD_SUBDIRECTORY(compression) ADD_SUBDIRECTORY(common) ADD_SUBDIRECTORY(gemmi_gph) ADD_SUBDIRECTORY(frame_serialize) ADD_SUBDIRECTORY(preview) ADD_SUBDIRECTORY(writer) ADD_SUBDIRECTORY(image_analysis) ADD_SUBDIRECTORY(broker) ADD_SUBDIRECTORY(reader) ADD_SUBDIRECTORY(rugnux) ADD_SUBDIRECTORY(viewer) ADD_SUBDIRECTORY(tools) # builds only the portable analysis tools (rugnux/extract_hkl) ADD_SUBDIRECTORY(tests) # jfjoch_portable_test only IF (NOT WIN32 AND NOT APPLE) # no native XDS for Windows; the macOS plugin is not tested ADD_SUBDIRECTORY(xds-plugin) ENDIF() ELSE() ADD_SUBDIRECTORY(jungfrau) ADD_SUBDIRECTORY(compression) ADD_SUBDIRECTORY(common) ADD_SUBDIRECTORY(writer) ADD_SUBDIRECTORY(frame_serialize) ADD_SUBDIRECTORY(reader) ADD_SUBDIRECTORY(detector_control) ADD_SUBDIRECTORY(image_puller) ADD_SUBDIRECTORY(preview) ADD_SUBDIRECTORY(gemmi_gph) ADD_SUBDIRECTORY(xds-plugin) IF (JFJOCH_WRITER_ONLY) MESSAGE(STATUS "Compiling HDF5 writer only") ELSE() ADD_SUBDIRECTORY(image_pusher) ADD_SUBDIRECTORY(broker) ADD_SUBDIRECTORY(fpga) ADD_SUBDIRECTORY(acquisition_device) ADD_SUBDIRECTORY(receiver) ADD_SUBDIRECTORY(image_analysis) ADD_SUBDIRECTORY(rugnux) ADD_SUBDIRECTORY(tests) ADD_SUBDIRECTORY(tools) ENDIF() IF (JFJOCH_VIEWER_BUILD) ADD_SUBDIRECTORY(viewer) ENDIF() ENDIF() IF (NOT JFJOCH_WRITER_ONLY AND NOT JFJOCH_PORTABLE_ONLY) ADD_CUSTOM_COMMAND(OUTPUT frontend/dist/index.html COMMAND npm ci COMMAND npm run build COMMAND npm run licenses # write dist/THIRD_PARTY_LICENSES.txt (npm deps attribution) COMMAND npm run redocly COMMAND npm run docs # bundle Sphinx docs into dist/docs (served at /frontend/docs) WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/frontend) ADD_CUSTOM_TARGET(frontend DEPENDS frontend/dist/index.html) ADD_CUSTOM_TARGET(update_version COMMAND bash update_version.sh WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) IF (JFJOCH_INSTALL_DRIVER_SOURCE) INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/fpga/pcie_driver/ DESTINATION /usr/src/jfjoch-${JFJOCH_VERSION} COMPONENT driver-dkms FILES_MATCHING PATTERN "dkms.conf") INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/fpga/pcie_driver/ DESTINATION /usr/src/jfjoch-${JFJOCH_VERSION}/src COMPONENT driver-dkms FILES_MATCHING PATTERN "*.c" PATTERN "*.h" PATTERN "Makefile") ENDIF() FILE(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/frontend/dist/) INSTALL(DIRECTORY ${CMAKE_SOURCE_DIR}/frontend/dist/ DESTINATION share/jfjoch/frontend COMPONENT jfjoch ) ENDIF() # Windows has no static cuFFT (the toolkit ships no cufft_static.lib), so there the indexer links # the import library and the self-contained products must carry the DLL beside the executable -- # the same idea the Linux builds no longer need, since they link libcufft_static.a. CUDA 13 keeps # the redistributable DLLs in bin/x64, earlier toolkits in bin; glob both. IF (WIN32 AND JFJOCH_CUDA_AVAILABLE AND JFJOCH_PORTABLE_ONLY AND NOT TARGET CUDA::cufft_static) FILE(GLOB _cufft_dll "${CUDAToolkit_BIN_DIR}/x64/cufft64_*.dll" "${CUDAToolkit_BIN_DIR}/cufft64_*.dll") IF (NOT _cufft_dll) MESSAGE(FATAL_ERROR "cuFFT runtime DLL not found under ${CUDAToolkit_BIN_DIR}") ENDIF() INSTALL(FILES ${_cufft_dll} DESTINATION bin COMPONENT rugnux) IF (JFJOCH_VIEWER_ONLY) INSTALL(FILES ${_cufft_dll} DESTINATION bin COMPONENT viewer) ENDIF() ENDIF() IF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) SET(CMAKE_INSTALL_PREFIX /opt/jfjoch CACHE PATH "Default directory" FORCE) ENDIF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) # Set Package Name SET(CPACK_PACKAGE_NAME "jfjoch") # Select the components to package based on build mode if (JFJOCH_RUGNUX_ONLY) set(CPACK_COMPONENTS_ALL rugnux) elseif (JFJOCH_VIEWER_ONLY AND (WIN32 OR APPLE)) set(CPACK_COMPONENTS_ALL viewer rugnux) # the XDS plugin is built on Linux only elseif (JFJOCH_VIEWER_ONLY) set(CPACK_COMPONENTS_ALL viewer rugnux xds_plugin) else() # rugnux is packaged separately from everything else, as "rugnux" rather than "jfjoch-rugnux" # (see the DEB/RPM branches below). It is the offline analysis CLI: it links none of the # hardware libraries and talks to no service, so a machine that only processes data can install # it alone. Up to 1.0.0-rc.163 it rode inside the jfjoch-viewer package, which is why both # branches below declare the file move. set(CPACK_COMPONENTS_ALL jfjoch writer rugnux) if (JFJOCH_INSTALL_DRIVER_SOURCE) list(APPEND CPACK_COMPONENTS_ALL driver-dkms) endif() if (JFJOCH_VIEWER_BUILD) list(APPEND CPACK_COMPONENTS_ALL viewer) endif() endif() # Common metadata set(CPACK_PACKAGE_CONTACT "Filip Leonarski ") set(CPACK_PACKAGE_VENDOR "Paul Scherrer Institut") set(CPACK_PACKAGE_VERSION ${JFJOCH_VERSION}) # Names of the portable .tar.gz archives (CI renames them to .tgz). rugnux and the plugin always # carry their architecture, because rugnux is built for more than one: x86_64 natively and aarch64 # through the cross toolchain. CMAKE_SYSTEM_PROCESSOR is the TARGET architecture (the toolchain file # declares it), never the build host's. The Linux viewer is x86_64-only and keeps its existing name. # The plugin has no -cuda/-cpu tag: it does not use CUDA, so both variants build the same library. if (APPLE) set(_jfjoch_tgz_os "macos") else() set(_jfjoch_tgz_os "linux") endif() if (JFJOCH_CUDA_AVAILABLE) set(_jfjoch_tgz_variant "cuda${CUDAToolkit_VERSION_MAJOR}") else() set(_jfjoch_tgz_variant "cpu") endif() set(CPACK_ARCHIVE_VIEWER_FILE_NAME "jfjoch_viewer-${JFJOCH_VERSION}-${_jfjoch_tgz_os}-${_jfjoch_tgz_variant}") if (WIN32) set(CPACK_ARCHIVE_RUGNUX_FILE_NAME "rugnux-${JFJOCH_VERSION}-win64-${_jfjoch_tgz_variant}") else() set(CPACK_ARCHIVE_RUGNUX_FILE_NAME "rugnux-${JFJOCH_VERSION}-${_jfjoch_tgz_os}-${CMAKE_SYSTEM_PROCESSOR}-${_jfjoch_tgz_variant}") endif() set(CPACK_ARCHIVE_XDS_PLUGIN_FILE_NAME "jfjoch_xds_plugin-${JFJOCH_VERSION}-linux-${CMAKE_SYSTEM_PROCESSOR}") # macOS and Windows pack the viewer as a .dmg / NSIS installer and the other programs of the same # build (rugnux) as an archive. CPack reads # CPACK_PROJECT_CONFIG_FILE once per generator, which is where each generator gets its components. file(WRITE ${CMAKE_BINARY_DIR}/CPackProjectConfig.cmake [=[ if (CPACK_GENERATOR STREQUAL "TGZ" OR CPACK_GENERATOR STREQUAL "ZIP") list(REMOVE_ITEM CPACK_COMPONENTS_ALL viewer) set(CPACK_ARCHIVE_COMPONENT_INSTALL ON) set(CPACK_COMPONENTS_GROUPING IGNORE) else() set(CPACK_COMPONENTS_ALL viewer) endif() ]=]) # OS-aware packaging: DragNDrop (.dmg) on macOS, NSIS installer on Windows, DEB on Debian/Ubuntu, # RPM on RHEL/Rocky. macOS/Windows are checked first because the /etc/* probes below are Linux-only. if (APPLE AND NOT JFJOCH_RUGNUX_ONLY) # .dmg containing jfjoch_viewer.app (Qt runtime already deployed into the bundle). Named like the # Windows and Linux artifacts; CMAKE_SYSTEM_PROCESSOR is arm64 on Apple Silicon. # plus rugnux as .tar.gz, see CPackProjectConfig.cmake above set(CPACK_GENERATOR "DragNDrop;TGZ") set(CPACK_PROJECT_CONFIG_FILE ${CMAKE_BINARY_DIR}/CPackProjectConfig.cmake) set(CPACK_PACKAGE_FILE_NAME "jfjoch-viewer-${JFJOCH_VERSION}-macos-${CMAKE_SYSTEM_PROCESSOR}") set(CPACK_DMG_VOLUME_NAME "Jungfraujoch Viewer ${JFJOCH_VERSION}") elseif (WIN32 AND NOT JFJOCH_RUGNUX_ONLY) # NSIS installer .exe (Qt runtime deployed next to the binary by windeployqt), plus rugnux as # a .zip, see CPackProjectConfig.cmake above. set(CPACK_GENERATOR "NSIS;ZIP") set(CPACK_PROJECT_CONFIG_FILE ${CMAKE_BINARY_DIR}/CPackProjectConfig.cmake) # GPLv3 text shown as the click-through license page of the installer. set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") # Branding is split across three CPack knobs so the CUDA/CPU variant surfaces exactly where we # want it and nowhere else: # - Install folder + Start Menu group come from CPACK_PACKAGE_INSTALL_DIRECTORY and # CPACK_NSIS_PACKAGE_NAME (the latter feeds $(^Name), the Start Menu group's default folder). # Both stay plain "Jungfraujoch", so the Start Menu group carries no variant tag and the two # builds install to the same place (CUDA is a strict superset -- they replace, not coexist). # - CPACK_NSIS_DISPLAY_NAME is the Add/Remove Programs entry -- tagged "(CUDA)"/"(CPU)". # - CPACK_PACKAGE_FILE_NAME is the installer .exe filename -- tagged "-cuda"/"-cpu", so # the (much larger) CUDA download is self-identifying, with the CUDA major version baked in. # The tag follows JFJOCH_CUDA_AVAILABLE automatically; CUDAToolkit_VERSION_MAJOR is set whenever # it is ON (find_package(CUDAToolkit) ran in the same guard above). set(CPACK_PACKAGE_INSTALL_DIRECTORY "Jungfraujoch") set(CPACK_NSIS_PACKAGE_NAME "Jungfraujoch") if (JFJOCH_CUDA_AVAILABLE) set(CPACK_NSIS_DISPLAY_NAME "Jungfraujoch (CUDA)") set(CPACK_PACKAGE_FILE_NAME "jfjoch-viewer-${JFJOCH_VERSION}-win64-cuda${CUDAToolkit_VERSION_MAJOR}") else() set(CPACK_NSIS_DISPLAY_NAME "Jungfraujoch (CPU)") set(CPACK_PACKAGE_FILE_NAME "jfjoch-viewer-${JFJOCH_VERSION}-win64-cpu") endif() # Start Menu shortcut for the viewer (";