Files
Jungfraujoch/CMakeLists.txt
T
leonarski_fandClaude Opus 5 491312f263
Build Packages / build:rugnux:aarch64 (cross) (push) Failing after 2m8s
Build Packages / build:rugnux:windows (push) Successful in 14m6s
Build Packages / build:rugnux-tgz (x86_64) (push) Failing after 15m4s
Build Packages / build:windows:nocuda (push) Successful in 16m1s
Build Packages / build:windows:cuda (push) Successful in 18m48s
Build Packages / build:viewer-tgz:cpu (push) Successful in 20m9s
Build Packages / build:viewer-tgz:cuda (push) Successful in 23m1s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 22m54s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 24m23s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 27m45s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 27m49s
Build Packages / build:rpm (rocky8) (push) Canceled after 7m42s
Build Packages / XDS test (durin plugin) (push) Canceled after 0s
Build Packages / XDS test (JFJoch plugin) (push) Canceled after 0s
Build Packages / XDS test (neggia plugin) (push) Canceled after 0s
Build Packages / Generate python client (push) Canceled after 0s
Build Packages / Build documentation (push) Canceled after 0s
Build Packages / Create release (push) Canceled after 0s
Build Packages / build:rpm (ubuntu2204) (push) Canceled after 4m8s
Build Packages / build:rpm (ubuntu2404) (push) Canceled after 2m52s
Build Packages / build:rpm (rocky8_sls9) (push) Canceled after 15m38s
Build Packages / build:rpm (rocky9) (push) Canceled after 7m40s
Build Packages / build:rpm (rocky9_sls9) (push) Canceled after 10m37s
Build Packages / DIALS test (push) Canceled after 2m45s
Build Packages / Unit tests (push) Canceled after 30m48s
Fix the packaging split where it was only exercised on one configuration
Three failures, all from validating the rugnux path alone and assuming the rest
followed.

viewer/CMakeLists.txt had an unbalanced ENDIF: removing the cuFFT bundling block
took its opening IF(JFJOCH_CUDA_AVAILABLE) with it and left the closer behind, so
every configuration that adds viewer/ died on "flow control statements are not
properly nested" -- both Windows jobs and the viewer tarball.

The cross toolchain could not find Eigen. CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY
confines find_package to the target roots, and the ubuntu2404 image installs Eigen
from source in /opt/eigen-3.4 rather than as libeigen3-dev, so it sat outside them.
It is header-only, so serving it from outside the target roots is safe; the root is
now listed explicitly.

Static cuFFT is not available everywhere. The Windows toolkit ships no
cufft_static.lib, so CUDA::cufft_static does not exist there and linking it fails
at generate time. Condition on the target rather than the platform, so a toolkit
that gains or loses the static library needs no further change, and restore the
Windows DLL bundling for both products -- now in the top-level CMakeLists, since
rugnux needs it too and does not add viewer/. rugnux.exe therefore ships beside
cufft64_*.dll rather than alone; the Linux and cross artifacts are still one file.

That condition also has to keep the JFJOCH_PORTABLE_ONLY half. Testing only for
the target quietly moved the SERVER build onto static cuFFT as well, where every
executable -- broker, tests, tools -- would then need the CUDA device-link step and
would fail on an undefined __cudaRegisterLinkedBinary_* at link. Configure still
succeeds, so nothing catches that until the link; the server build keeps libcufft.so
exactly as before.

Checked by configuring all four flavours in the image CI actually uses -- rugnux,
viewer and server on x86_64 plus the aarch64 cross -- and confirming each links the
cuFFT it should.

The rugnux archives now also carry their target architecture, since that build
produces more than one: rugnux-<version>-linux-x86_64-cuda13.tar.gz beside the
aarch64 one. CMAKE_SYSTEM_PROCESSOR is the target's, never the build host's. The
viewer is x86_64-only and keeps its existing name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQjneRUssfhi1k9rq8Ts3h
2026-08-25 20:53:49 +02:00

693 lines
34 KiB
CMake

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}")
PROJECT(jfjoch VERSION ${JFJOCH_VERSION_NUMERIC} LANGUAGES C CXX)
SET(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
SET(CMAKE_CXX_STANDARD 20)
SET(CMAKE_CXX_STANDARD_REQUIRED True)
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 two shippable products are built separately, one per configure, and each produces exactly one
# artifact holding exactly one program:
#
# JFJOCH_VIEWER_ONLY jfjoch_viewer .tar.gz (Linux) / installer (Windows) / .dmg (macOS)
# JFJOCH_RUGNUX_ONLY rugnux .tar.gz (Linux, incl. cross-built aarch64) / .zip (Windows)
#
# 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)")
# 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)")
SET (ZLIB_USE_STATIC_LIBS TRUE)
FIND_PACKAGE(ZLIB REQUIRED)
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 -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)
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
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.17.0
EXCLUDE_FROM_ALL
)
FetchContent_Declare(
zstd
GIT_REPOSITORY https://github.com/facebook/zstd
GIT_TAG 794ea1b0afca0f020f4e57b6732332231fb23c70
SOURCE_SUBDIR build/cmake
EXCLUDE_FROM_ALL
)
FetchContent_Declare(hdf5
GIT_REPOSITORY https://github.com/HDFGroup/hdf5/
GIT_TAG hdf5_2.1.0
GIT_SHALLOW 1
EXCLUDE_FROM_ALL)
SET(SLS_DETECTOR_GIT_TAG "8.0.2")
IF(SLS9)
SET(SLS_DETECTOR_GIT_TAG "9.2.0")
ENDIF()
FetchContent_Declare(
sls_detector_package
GIT_REPOSITORY https://github.com/slsdetectorgroup/slsDetectorPackage
GIT_TAG ${SLS_DETECTOR_GIT_TAG}
EXCLUDE_FROM_ALL
)
FetchContent_Declare(
catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2
GIT_TAG v3.13.0
EXCLUDE_FROM_ALL
)
FetchContent_Declare(
httplib
GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git
GIT_TAG v0.39.0
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
GIT_REPOSITORY https://github.com/zeromq/libzmq.git
GIT_TAG v4.3.5
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).
# Only sls_detector_package (detector) and catch2 (tests) are not built here -- and sls
# in particular does not configure under MSVC -- so skip just those two.
FetchContent_MakeAvailable(zstd 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
GIT_REPOSITORY https://gitlab.com/libtiff/libtiff.git
GIT_TAG v4.7.1
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 (e.g. Grace on DGX Spark) gets NEON; 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)
ELSEIF (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64")
SET(ENABLE_NEON 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 $<INSTALL_INTERFACE:include>, 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 $<BUILD_INTERFACE:${fftw_SOURCE_DIR}/api>)
ADD_COMPILE_DEFINITIONS(JFJOCH_USE_FFTW)
# Eigen is an EXTERNAL dependency (like ZLIB), resolved by find_package(Eigen3) in the subdirectories
# that need it (image_analysis, and transitively Ceres and ffbidx). It is deliberately NOT vendored
# via FetchContent: declaring Eigen with OVERRIDE_FIND_PACKAGE makes the CMake bundled with Visual
# Studio (cmake 4.x "-msvc") intermittently SEGFAULT during configure. 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 -- ~1 in 3 fresh configures, and 100% with Ceres CUDA on.
# Stock Kitware CMake runs the identical scripts fine, so it is a bug in the VS-bundled cmake binary;
# providing Eigen externally avoids that code path entirely and is stable (verified, both Ceres CUDA
# on and off). Provide it via the system package on Linux (eigen3-devel / libeigen3-dev) or a build
# prefix on Windows (point CMAKE_PREFIX_PATH / Eigen3_DIR at it), exactly as for ZLIB.
# getopt/getopt_long shim for Windows: the MSVC CRT has no <getopt.h>. 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()
# libcurl: the jfjoch_viewer HTTP client (viewer/JFJochHttpReader). Fetched and STATICALLY linked
# ONLY for viewer builds, so the broker/writer never pull in a TLS/Kerberos stack. Backends are
# OS-native per platform to keep the Windows build self-contained: Schannel (TLS) + SSPI
# (Negotiate/Kerberos) on Windows -- no external OpenSSL/krb5 -- and system OpenSSL + GSSAPI (krb5)
# on Linux. Trimmed to HTTP(S) only, mirroring the libtiff codec pruning above.
IF (JFJOCH_VIEWER_BUILD OR JFJOCH_VIEWER_ONLY)
SET(BUILD_CURL_EXE OFF CACHE BOOL "" FORCE)
SET(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) # static libcurl only, never a shared lib
SET(BUILD_STATIC_LIBS ON CACHE BOOL "" FORCE)
SET(BUILD_TESTING OFF CACHE BOOL "" FORCE)
SET(CURL_DISABLE_INSTALL ON CACHE BOOL "" FORCE)
SET(HTTP_ONLY ON CACHE BOOL "" FORCE) # HTTP/HTTPS only; drop FTP/LDAP/SMTP/...
SET(CURL_USE_LIBPSL OFF CACHE BOOL "" FORCE) # avoid the libpsl dependency
SET(CURL_USE_LIBSSH2 OFF CACHE BOOL "" FORCE)
SET(CURL_ZLIB ON CACHE BOOL "" FORCE) # reuse the project's ZLIB
IF (WIN32)
SET(CURL_USE_SCHANNEL ON CACHE BOOL "" FORCE) # native Windows TLS (no OpenSSL)
SET(CURL_USE_SSPI ON CACHE BOOL "" FORCE) # native Negotiate/Kerberos (no krb5)
ELSE()
SET(CURL_USE_OPENSSL ON CACHE BOOL "" FORCE)
SET(CURL_USE_GSSAPI ON CACHE BOOL "" FORCE) # Kerberos via system krb5 (needs krb5-dev)
ENDIF()
FetchContent_Declare(curl
GIT_REPOSITORY https://github.com/curl/curl.git
GIT_TAG curl-8_11_1
EXCLUDE_FROM_ALL)
FetchContent_MakeAvailable(curl)
# The viewer links libcurl_static (curl's concrete static-lib target) directly rather than the
# CURL::libcurl alias: the alias can be shadowed by a system libcurl (find_package / dependency
# provider / a distro CURL config), silently linking the OS curl with the wrong TLS/Kerberos
# backend. libcurl_static is unambiguous; guard that FetchContent actually produced it.
IF (NOT TARGET libcurl_static)
MESSAGE(FATAL_ERROR "libcurl_static missing after FetchContent_MakeAvailable(curl) -- "
"the vendored static libcurl was not built.")
ENDIF()
ENDIF()
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)
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)
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()
IF (JFJOCH_RUGNUX_ONLY)
INSTALL(FILES ${_cufft_dll} DESTINATION bin COMPONENT rugnux)
ELSE()
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)
set(CPACK_COMPONENTS_ALL viewer)
else()
set(CPACK_COMPONENTS_ALL jfjoch writer)
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 <filip.leonarski@psi.ch>")
set(CPACK_PACKAGE_VENDOR "Paul Scherrer Institut")
set(CPACK_PACKAGE_VERSION ${JFJOCH_VERSION})
# 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).
set(CPACK_GENERATOR "DragNDrop")
elseif (WIN32 AND NOT JFJOCH_RUGNUX_ONLY)
# NSIS installer .exe (Qt runtime deployed next to the binary by windeployqt).
set(CPACK_GENERATOR "NSIS")
# 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<major>"/"-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 ("<exe basename>;<label>"). The shortcut and the
# Add/Remove Programs entry both use the .exe's embedded icon.
set(CPACK_PACKAGE_EXECUTABLES "jfjoch_viewer;Jungfraujoch Viewer")
# Forward slash on purpose: CPack writes this value verbatim into CPackConfig.cmake, and a
# backslash there ("bin\jfjoch...") is an invalid escape that cmake 4.x (CMP0010 strict, e.g. the
# VS-bundled cmake) rejects when cpack re-parses it. Windows accepts the forward slash at runtime.
set(CPACK_NSIS_INSTALLED_ICON_NAME "bin/jfjoch_viewer.exe")
elseif (WIN32)
# rugnux on Windows is a single self-contained rugnux.exe (cuFFT, cudart and the fast-feedback
# indexer are all static, so nothing rides alongside it). An NSIS installer would be ceremony
# around one file that a user drops wherever they like, so ship a .zip: the .exe plus the
# licence notices the component installs.
set(CPACK_GENERATOR "ZIP")
set(CPACK_ARCHIVE_COMPONENT_INSTALL ON)
set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)
if (JFJOCH_CUDA_AVAILABLE)
set(CPACK_PACKAGE_FILE_NAME "rugnux-${JFJOCH_VERSION}-win64-cuda${CUDAToolkit_VERSION_MAJOR}")
else()
set(CPACK_PACKAGE_FILE_NAME "rugnux-${JFJOCH_VERSION}-win64-cpu")
endif()
elseif (JFJOCH_VIEWER_ONLY OR JFJOCH_RUGNUX_ONLY)
# Linux portable viewer: a single self-contained .tar.gz of jfjoch_viewer (Qt is linked
# statically here, so there is nothing external to ship alongside the binary). Built on the
# oldest supported distro (RHEL 8) for a low glibc floor, so the archive runs on any newer
# Linux -- unlike the per-distro .rpm/.deb this is meant to replace in the release. Named to
# mirror the Windows/macOS artifacts, with the same -cuda<major>/-cpu tag so the (larger) GPU
# build is self-identifying. CI renames the .tar.gz to .tgz before upload.
set(CPACK_GENERATOR "TGZ")
# Package only the components in CPACK_COMPONENTS_ALL (here: viewer). Without this the archive
# generator falls back to a monolithic install and walks every install() rule in the tree --
# including the fetched HDF5 command-line tools, abseil's EXCLUDE_FROM_ALL sub-libraries and the
# non-viewer components -- none of which the viewer-only build produces, so cpack aborts with
# "cannot find <artifact>". ALL_COMPONENTS_IN_ONE keeps the output a single tarball named
# CPACK_PACKAGE_FILE_NAME (no per-component suffix), matching the CI upload glob. This mirrors
# CPACK_DEB_COMPONENT_INSTALL / CPACK_RPM_COMPONENT_INSTALL in the DEB/RPM branches below.
set(CPACK_ARCHIVE_COMPONENT_INSTALL ON)
set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)
# The rugnux archive always carries its architecture, because it 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, so a cross-built tarball
# cannot be mistaken for a native one. The viewer is x86_64-only and keeps its existing name.
if (JFJOCH_RUGNUX_ONLY)
set(_jfjoch_tgz_name "rugnux")
set(_jfjoch_tgz_arch "-${CMAKE_SYSTEM_PROCESSOR}")
else()
set(_jfjoch_tgz_name "jfjoch_viewer")
set(_jfjoch_tgz_arch "")
endif()
if (JFJOCH_CUDA_AVAILABLE)
set(CPACK_PACKAGE_FILE_NAME "${_jfjoch_tgz_name}-${JFJOCH_VERSION}-linux${_jfjoch_tgz_arch}-cuda${CUDAToolkit_VERSION_MAJOR}")
else()
set(CPACK_PACKAGE_FILE_NAME "${_jfjoch_tgz_name}-${JFJOCH_VERSION}-linux${_jfjoch_tgz_arch}-cpu")
endif()
elseif (EXISTS "/etc/debian_version")
set(CPACK_PACKAGE_LICENSE "GPL-3.0-only")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
# Debian/Ubuntu: .deb with components
set(CPACK_GENERATOR "DEB")
set(CPACK_DEB_COMPONENT_INSTALL ON)
# Every component package is named "<CPACK_PACKAGE_NAME>-<component>" unless the component
# overrides it, so the broker component came out as "jfjoch-jfjoch". Name it plain "jfjoch", to
# match what the RPM generator produces for CPACK_RPM_MAIN_COMPONENT below. (There is no
# CPACK_DEBIAN_MAIN_COMPONENT: only CPackRPM.cmake has that variable, and the DEB spelling this
# used to set was silently ignored.)
set(CPACK_DEBIAN_JFJOCH_PACKAGE_NAME "jfjoch")
set(CPACK_DEBIAN_FILE_NAME "DEB-DEFAULT")
# Up to 1.0.0-rc.160 that package was "jfjoch-jfjoch". It owns the same files as "jfjoch" does
# now, so dpkg would refuse to unpack over it; declare the rename so an existing installation
# upgrades instead of conflicting.
set(CPACK_DEBIAN_JFJOCH_PACKAGE_REPLACES "jfjoch-jfjoch")
set(CPACK_DEBIAN_JFJOCH_PACKAGE_CONFLICTS "jfjoch-jfjoch")
# Enable automatic shlib dependency discovery
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
# Optional DEB metadata (tune as needed)
set(CPACK_DEBIAN_PACKAGE_SECTION "science")
# set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://example.org/jfjoch")
# DKMS component (maps from RPM settings)
if (JFJOCH_INSTALL_DRIVER_SOURCE)
# Dependencies and arch for the DKMS component
# Note: component name "driver-dkms" maps to variable suffix "DRIVER_DKMS"
set(CPACK_DEBIAN_DRIVER_DKMS_PACKAGE_DEPENDS "dkms, gcc, bash, sed")
set(CPACK_DEBIAN_DRIVER_DKMS_PACKAGE_ARCHITECTURE "all")
# Provide Debian control scripts (postinst/prerm).
# We rename existing scripts to Debian control names at configure time.
set(_dkms_ctrl_dir "${CMAKE_CURRENT_BINARY_DIR}/cpack-debian-driver-dkms")
file(MAKE_DIRECTORY "${_dkms_ctrl_dir}")
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fpga/pcie_driver/postinstall.sh
${_dkms_ctrl_dir}/postinst COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fpga/pcie_driver/preuninstall.sh
${_dkms_ctrl_dir}/prerm COPYONLY)
set(CPACK_DEBIAN_DRIVER_DKMS_PACKAGE_CONTROL_EXTRA
"${_dkms_ctrl_dir}/postinst;${_dkms_ctrl_dir}/prerm")
endif()
elseif (EXISTS "/etc/redhat-release")
# RHEL/Rocky: .rpm with components (original behavior)
set(CPACK_GENERATOR "RPM")
set(CPACK_RPM_COMPONENT_INSTALL ON)
set(CPACK_RPM_MAIN_COMPONENT jfjoch)
set(CPACK_RPM_PACKAGE_RELEASE_DIST ON)
set(CPACK_RPM_FILE_NAME "RPM-DEFAULT")
set(CPACK_RPM_PACKAGE_VERSION ${JFJOCH_VERSION})
set(CPACK_RPM_PACKAGE_RELEASE 1)
set(CPACK_RPM_PACKAGE_SUMMARY "Jungfraujoch data acquisition system")
set(CPACK_RPM_PACKAGE_DESCRIPTION "Jungfraujoch")
set(CPACK_RPM_PACKAGE_LICENSE "GPLv3")
set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION
/usr/src /usr/share /usr/share/dbus-1 /usr/share/dbus-1/services /usr/share/pixmaps /usr/share/applications)
# DKMS component scripts and requirements for RPM
if (JFJOCH_INSTALL_DRIVER_SOURCE)
set(CPACK_RPM_DRIVER-DKMS_PACKAGE_REQUIRES "dkms, gcc, bash, sed")
set(CPACK_RPM_DRIVER-DKMS_PACKAGE_ARCHITECTURE "noarch")
set(CPACK_RPM_DRIVER-DKMS_POST_INSTALL_SCRIPT_FILE
${CMAKE_CURRENT_SOURCE_DIR}/fpga/pcie_driver/postinstall.sh)
set(CPACK_RPM_DRIVER-DKMS_PRE_UNINSTALL_SCRIPT_FILE
${CMAKE_CURRENT_SOURCE_DIR}/fpga/pcie_driver/preuninstall.sh)
endif()
else()
# Fallback if OS is unknown
set(CPACK_GENERATOR "TGZ")
endif()
# Ship the license notices (top-level LICENSE, the third-party manifest, and the verbatim
# license texts) with every package variant. Installed per component so the files land in
# whichever component package(s) the current build mode produces.
#
# Each component gets a directory of its own, named after the package's principal binary:
# share/doc/jfjoch_broker, jfjoch_writer, jfjoch_viewer, jfjoch_driver_dkms. No two components
# may write the same path, and no component writes the plain share/doc/jfjoch these all used to
# share. Deployment updates the packages one at a time (jfjoch first, then jfjoch-writer), so a
# path claimed by two packages blocks the first step as soon as a release changes its content -
# and leaving the main package on the old shared path would keep blocking it for anyone
# upgrading from a release that still had the collision. These notices were the only files any
# two components had in common.
SET(JFJOCH_NOTICE_FILES
${CMAKE_SOURCE_DIR}/LICENSE
${CMAKE_SOURCE_DIR}/THIRD_PARTY_NOTICES.md)
FOREACH(_component IN LISTS CPACK_COMPONENTS_ALL)
IF (_component STREQUAL "jfjoch")
SET(_suffix broker)
ELSE()
STRING(REPLACE "-" "_" _suffix ${_component})
ENDIF()
INSTALL(FILES ${JFJOCH_NOTICE_FILES}
DESTINATION share/doc/jfjoch_${_suffix} COMPONENT ${_component})
INSTALL(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses/
DESTINATION share/doc/jfjoch_${_suffix}/licenses COMPONENT ${_component}
PATTERN "COLLECT.sh" EXCLUDE)
ENDFOREACH()
INCLUDE(CPack)