Ship jfjoch_viewer and rugnux as two packages, and cross-build rugnux for arm64
Build Packages / build:rugnux:windows (push) Failing after 2s
Build Packages / build:rugnux-tgz (x86_64) (push) Failing after 14s
Build Packages / build:rugnux:aarch64 (cross) (push) Failing after 30s
Build Packages / Unit tests (push) Canceled after 2m33s
Build Packages / build:rpm (rocky8) (push) Canceled after 0s
Build Packages / build:rpm (rocky9_sls9) (push) Canceled after 0s
Build Packages / build:rpm (rocky9) (push) Canceled after 0s
Build Packages / build:rpm (ubuntu2204) (push) Canceled after 0s
Build Packages / build:rpm (ubuntu2404) (push) Canceled after 0s
Build Packages / DIALS test (push) Canceled after 0s
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:viewer-tgz:cuda (push) Canceled after 2m29s
Build Packages / Build documentation (push) Canceled after 0s
Build Packages / Create release (push) Canceled after 0s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Canceled after 2m19s
Build Packages / build:viewer-tgz:cpu (push) Canceled after 2m31s
Build Packages / build:rpm (rocky8_sls9) (push) Canceled after 1m54s
Build Packages / build:rpm (rocky9_nocuda) (push) Canceled after 2m21s
Build Packages / build:rpm (rocky8_nocuda) (push) Canceled after 2m23s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Canceled after 2m13s
Build Packages / build:windows:cuda (push) Canceled after 2m42s
Build Packages / build:windows:nocuda (push) Canceled after 2m45s

The viewer tarball also carried rugnux, jfjoch_extract_hkl and jfjoch_recompress.
A GUI download that contains a batch processor is confusing on its own, and it
forced anyone who only wanted rugnux to take Qt with it. JFJOCH_RUGNUX_ONLY is
the viewer-only subtree minus viewer/ and tools/, so it needs no Qt at all, and
each of the two options now yields one artifact holding one program:

  JFJOCH_VIEWER_ONLY   jfjoch_viewer   .tar.gz / installer / .dmg
  JFJOCH_RUGNUX_ONLY   rugnux          .tar.gz / .zip

The two CLI tools are still built and no longer installed anywhere. rugnux gets
a CPack component of its own, so its notices land in share/doc/jfjoch_rugnux.

What makes the split possible is linking cuFFT statically in both products. It
was the only CUDA component still dynamic - cudart and the fast-feedback indexer
were already static - and shipping it as a .so meant each self-contained archive
had to carry the library beside its executables and find it again through an
$ORIGIN rpath. That rpath was set on jfjoch_viewer alone, while the same tarball
installed three more executables, so those shipped with no runpath at all (cmake
strips the build-tree one, which points at the build host's CUDA directory) and
could not load the libcufft lying next to them. Static, an artifact is one file
that runs: the x86_64 rugnux tarball needs only libc, libstdc++, libgcc_s, libm,
libpthread, librt and libdl. The bundling code and the rpath both go away.

libcufft_static.a carries a relocatable-device-code object, so a consuming
executable needs a CUDA device link; CUDA 13 no longer ships the
libcufft_static_nocallback.a that used to avoid this. CUDA_RESOLVE_DEVICE_SYMBOLS
emits that step while leaving the host link driver alone, so the -march and
-flto flags CI passes still apply. The server build keeps the shared library:
its .deb/.rpm take CUDA from the distro, and every executable there would
otherwise need the same device link.

The guards that exclude server-side targets keyed on NOT JFJOCH_VIEWER_ONLY,
which a rugnux-only build does not satisfy - it failed on jfjoch_writer pulling
in JFJochImagePuller. They now key on JFJOCH_PORTABLE_ONLY, true for either
product, rather than on one product's option in five places.

docker/ubuntu2404 additionally cross-compiles rugnux for arm64 SBSA, which is
both DGX Spark (GB10, sm_121) and Grace Hopper (GH200, sm_90); one binary with
both fatbins runs on either. It needed three things beyond a cross gcc. arm64 is
published on ports.ubuntu.com and Noble's deb822 sources carry no Architectures:
field, so adding the architecture without splitting the entries 404s on every
index. The CUDA cross packages are in neither the x86_64 repo nor the sbsa one
but in cross-linux-sbsa, whose fatbinary_section.h ships only in the x86_64
target tree even though it is architecture-neutral. And HDF5 has two try_run
probes, so qemu-user-static is needed as CMAKE_CROSSCOMPILING_EMULATOR; every
other fetched dependency either guards its run-checks or has none.

The cross-built tarball is one ELF aarch64 binary carrying all 17 GPU modules
for both sm_90 and sm_121, needing no cuFFT and no CUDA runtime at run time.
It is untested on hardware: XDS is x86-64 only, so CI can show it links but only
a Spark or a GH200 can show it works.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQjneRUssfhi1k9rq8Ts3h
This commit is contained in:
2026-08-25 19:23:59 +02:00
co-authored by Claude Opus 5
parent 46ed703eca
commit 8d32024dd1
11 changed files with 349 additions and 58 deletions
+110 -3
View File
@@ -163,9 +163,9 @@ jobs:
shell: bash
run: |
cd build
# Build the whole viewer-only tree, not just the GUI: the "viewer" CPack component also
# contains the portable CLI tools (rugnux, jfjoch_extract_hkl, jfjoch_recompress), which
# must exist on disk before cpack installs the component.
# The "viewer" component is now jfjoch_viewer alone -- rugnux ships as its own artifact
# (build:rugnux-tgz) and the two CLI tools are no longer packaged at all. The full tree is
# still built because the viewer links the Rugnux library for its in-process jobs.
ninja -j16
cpack
- name: Upload viewer tgz to release
@@ -188,6 +188,113 @@ jobs:
mv "$file" "$tgz"
python3 gitea_upload_file.py "$tgz"
done
build-rugnux-tgz:
name: build:rugnux-tgz (x86_64)
runs-on: jfjoch_rocky8
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: Clean previous build
shell: bash
run: rm -rf build-rugnux # self-hosted runners reuse the workspace volume
- name: Configure rugnux build
shell: bash
run: |
cmake -G Ninja -S . -B build-rugnux -DJFJOCH_RUGNUX_ONLY=ON -DJFJOCH_USE_CUDA=ON \
${{ env.LINUX_CMAKE_FLAGS }} -DCMAKE_BUILD_TYPE=Release
- name: Build rugnux tgz
shell: bash
run: cmake --build build-rugnux --target package -j16
- name: Verify the artifact is self-contained
shell: bash
run: |
set -euo pipefail
rm -rf /tmp/rgx && mkdir -p /tmp/rgx
tar xf build-rugnux/rugnux-*.tar.gz -C /tmp/rgx
bin=$(find /tmp/rgx -name rugnux -type f)
# cuFFT is static now: no libcufft beside the binary and none needed at run time.
! ldd "$bin" | grep -q cufft
! find /tmp/rgx -name 'libcufft*'
- name: Upload rugnux tgz to release
if: github.ref_type == 'tag'
uses: akkuman/gitea-release-action@v1
with:
files: build-rugnux/rugnux-*.tar.gz
build-rugnux-windows:
name: build:rugnux:windows
runs-on: windows-11-cuda-qt
steps:
- uses: actions/checkout@v4
- name: Configure rugnux build
shell: bash
# No Qt needed: JFJOCH_RUGNUX_ONLY is the portable subtree minus viewer/.
run: |
cmake -S . -B build-rugnux -DJFJOCH_RUGNUX_ONLY=ON -DJFJOCH_USE_CUDA=ON \
${{ env.MSVC_ARCH_CMAKE_FLAGS }} -DCMAKE_BUILD_TYPE=Release
- name: Build rugnux zip
shell: bash
run: cmake --build build-rugnux --config Release --target PACKAGE -j
- name: Upload rugnux zip to release
if: github.ref_type == 'tag'
uses: akkuman/gitea-release-action@v1
with:
files: build-rugnux/rugnux-*.zip
build-rugnux-arm:
name: build:rugnux:aarch64 (cross)
# Cross-compiled on an x86_64 runner: nvcc, gcc and cmake all run native and only emit aarch64,
# so this costs about what a normal build costs. Uses the ordinary ubuntu2404 image, which now
# also carries the aarch64 toolchain and the CUDA sbsa cross tree.
runs-on: jfjoch_ubuntu2404
container:
image: gitea.psi.ch/leonarski_f/jfjoch_ubuntu2404:2508
steps:
- uses: actions/checkout@v4
- name: Clean previous build
shell: bash
run: rm -rf build-aarch64 # self-hosted runners reuse the workspace volume
- name: Configure (cross to aarch64 SBSA)
shell: bash
# NOT ${{ env.LINUX_CMAKE_FLAGS }}: that pins -march=x86-64-v3, which an aarch64 compiler
# rejects. The aarch64 baseline already has NEON and a vectorised round, so the Eigen gap
# that flag closes on x86 does not exist here.
# sm_90 = GH200 (Grace Hopper, e.g. ALPS), sm_121 = GB10 (DGX Spark). One tarball, both.
run: |
cmake -G Ninja -S . -B build-aarch64 \
-DCMAKE_TOOLCHAIN_FILE=/opt/cross/aarch64-sbsa.cmake \
-DCMAKE_BUILD_TYPE=Release \
-DJFJOCH_RUGNUX_ONLY=ON \
-DJFJOCH_USE_CUDA=ON \
-DJFJOCH_CUDA_ARCHITECTURES="90;121"
- name: Build and package
shell: bash
run: cmake --build build-aarch64 --target package -j16
# A cross build that silently produced x86_64 still packages cleanly, so assert the artifact.
- name: Verify the tarball is really aarch64
shell: bash
run: |
set -euo pipefail
tgz=$(ls build-aarch64/*.tar.gz | head -1)
echo "checking $tgz"
rm -rf /tmp/tgzcheck && mkdir -p /tmp/tgzcheck
tar xf "$tgz" -C /tmp/tgzcheck
bin=$(find /tmp/tgzcheck -name rugnux -type f | head -1)
file "$bin"
file "$bin" | grep -q "ARM aarch64"
# nothing x86 may have leaked in (libjpeg-turbo's ExternalProject is the classic culprit)
! find /tmp/tgzcheck -type f -exec file {} + | grep -q "x86-64"
# cuFFT must be linked statically: no libcufft beside the binary, none needed at run time
! ldd "$bin" 2>/dev/null | grep -q cufft
# both GPU targets present in the fatbin
cuobjdump --list-elf "$bin" | grep -q sm_90
cuobjdump --list-elf "$bin" | grep -q sm_121
- name: Upload tarball to release
if: github.ref_type == 'tag'
uses: akkuman/gitea-release-action@v1
with:
files: build-aarch64/*.tar.gz
build-rpm:
name: build:rpm (${{ matrix.distro }})
runs-on: ${{ matrix.runner }}
+94 -16
View File
@@ -19,15 +19,46 @@ SET(JFJOCH_INSTALL_DRIVER_SOURCE OFF CACHE BOOL "Install kernel driver source (i
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.
IF (WIN32 OR APPLE)
# 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)
@@ -72,7 +103,11 @@ CHECK_LANGUAGE(CUDA)
# 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.
SET(CMAKE_CUDA_ARCHITECTURES 75 80 86 89 90 100 120)
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")
@@ -90,7 +125,7 @@ IF (CMAKE_CUDA_COMPILER)
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")
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
@@ -99,7 +134,7 @@ IF (CMAKE_CUDA_COMPILER)
# 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")
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()
@@ -212,8 +247,8 @@ 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_VIEWER_ONLY)
# A viewer-only build still needs zstd/hdf5/spdlog/httplib (JFJochReader uses httplib).
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)
@@ -326,7 +361,22 @@ IF (JFJOCH_VIEWER_BUILD OR JFJOCH_VIEWER_ONLY)
ENDIF()
ENDIF()
IF (JFJOCH_VIEWER_ONLY)
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)
@@ -374,7 +424,7 @@ ELSE()
ENDIF()
ENDIF()
IF (NOT JFJOCH_WRITER_ONLY AND NOT JFJOCH_VIEWER_ONLY)
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
@@ -411,7 +461,9 @@ ENDIF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
SET(CPACK_PACKAGE_NAME "jfjoch")
# Select the components to package based on build mode
if (JFJOCH_VIEWER_ONLY)
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)
@@ -430,10 +482,10 @@ 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)
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)
elseif (WIN32 AND NOT JFJOCH_RUGNUX_ONLY)
# NSIS installer .exe (Qt runtime deployed next to the binary by windeployqt).
set(CPACK_GENERATOR "NSIS")
@@ -468,7 +520,20 @@ elseif (WIN32)
# 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 (JFJOCH_VIEWER_ONLY)
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
@@ -485,10 +550,23 @@ elseif (JFJOCH_VIEWER_ONLY)
# 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)
if (JFJOCH_CUDA_AVAILABLE)
set(CPACK_PACKAGE_FILE_NAME "jfjoch_viewer-${JFJOCH_VERSION}-linux-cuda${CUDAToolkit_VERSION_MAJOR}")
if (JFJOCH_RUGNUX_ONLY)
set(_jfjoch_tgz_name "rugnux")
else()
set(CPACK_PACKAGE_FILE_NAME "jfjoch_viewer-${JFJOCH_VERSION}-linux-cpu")
set(_jfjoch_tgz_name "jfjoch_viewer")
endif()
# A cross-built archive is named for the TARGET arch, never the build host's: CMAKE_SYSTEM_PROCESSOR
# is what the toolchain file declares, so an aarch64 tarball cannot be mistaken for an x86 one.
# Left off for a native build, so the existing x86 artifact names do not change.
if (CMAKE_CROSSCOMPILING)
set(_jfjoch_tgz_arch "-${CMAKE_SYSTEM_PROCESSOR}")
else()
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")
+2 -2
View File
@@ -6,9 +6,9 @@ AUX_SOURCE_DIRECTORY(gen/model MODEL_SOURCES)
ADD_LIBRARY(JFJochAPI STATIC ${MODEL_SOURCES})
TARGET_INCLUDE_DIRECTORIES(JFJochAPI PUBLIC gen/model)
# The broker service pulls in the receiver/detector stack. A viewer-only build
# The broker service pulls in the receiver/detector stack. A portable build (viewer or rugnux)
# needs JFJochAPI (the shared OpenAPI model, above) but none of this machinery.
IF (NOT JFJOCH_VIEWER_ONLY)
IF (NOT JFJOCH_PORTABLE_ONLY)
# Jungfraujoch Broker (abstract from HTTP service)
ADD_LIBRARY(JFJochBroker STATIC
JFJochStateMachine.cpp JFJochStateMachine.h
+5
View File
@@ -21,6 +21,11 @@
# JOBS=4 docker/build_images.sh # all four at once
# JOBS=1 docker/build_images.sh # serial
# TAG=2607b docker/build_images.sh # override the tag (default 2607b)
#
# The tag has to match what .gitea/workflows/build_and_test.yml asks the runner for, or CI pulls a
# different image than the one just built. Currently jfjoch_rocky8:2511 and jfjoch_ubuntu2404:2508 --
# ubuntu2404 being the image that also carries the aarch64 cross toolchain, so:
# TAG=2508 docker/build_images.sh ubuntu2404
# PUSH=1 docker/build_images.sh # build (parallel) then push (serial; needs docker login)
#
# Parallel stdout would be an unreadable interleave, so each build streams to its own log file:
+53
View File
@@ -16,6 +16,18 @@ ARG EIGEN_VERSION=3.4.0
# Update base and install toolchain + static-Qt build dependencies
# (XCB/X11/OpenGL/EGL/DBus/fontconfig/freetype/zlib headers needed to build static Qt).
#
# arm64 is enabled as a second dpkg architecture here so this one image can also CROSS-COMPILE the
# rugnux CLI for aarch64 (DGX Spark GB10 / Grace Hopper GH200 -- both arm64 SBSA). Noble's deb822
# ubuntu.sources carries no Architectures: field, so a bare `dpkg --add-architecture arm64` sends apt
# to archive.ubuntu.com for arm64 indexes and 404s on every one; arm64 is published on
# ports.ubuntu.com instead. Pin the stock entries to amd64 and add a ports entry for arm64.
RUN set -eux; \
dpkg --add-architecture arm64; \
sed -i 's/^Components: main/Architectures: amd64\nComponents: main/' /etc/apt/sources.list.d/ubuntu.sources; \
printf '\nTypes: deb\nURIs: http://ports.ubuntu.com/ubuntu-ports\nSuites: noble noble-updates noble-security\nComponents: main restricted universe multiverse\nArchitectures: arm64\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\n' \
>> /etc/apt/sources.list.d/ubuntu.sources
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
@@ -188,6 +200,47 @@ RUN set -eux; \
# through Qt's exported config, so DBus1Config.cmake must be on CMAKE_PREFIX_PATH for the viewer build.
ENV CMAKE_PREFIX_PATH=/opt/qt-${QT_VERSION}-static:/opt/dbus-${DBUS_VERSION}-static:/opt/eigen-3.4
# ---------------------------------------------------------------------------------------------
# aarch64 cross-compilation support (rugnux only -- the viewer is not cross-built).
#
# crossbuild-essential-arm64 the aarch64 gcc/g++.
# qemu-user-static HDF5 runs two try_run() probes (config/ConfigureChecks.cmake) that
# need CMAKE_CROSSCOMPILING_EMULATOR. libzmq guards its own run-checks
# with NOT CMAKE_CROSSCOMPILING; Ceres, libtiff, zstd, Catch2, spdlog,
# cpp-httplib, FFTW and ffbidx have no run-checks at all.
# binutils-aarch64-linux-gnu reading aarch64 ELF on the build host.
# :arm64 libraries ZLIB is one of the project's two external dependencies (Eigen, the
# other, is header-only and installed above); OpenSSL is needed by the
# libcurl the build fetches.
# ---------------------------------------------------------------------------------------------
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
crossbuild-essential-arm64 binutils-aarch64-linux-gnu qemu-user-static \
zlib1g-dev:arm64 libssl-dev:arm64; \
apt-get clean; \
rm -rf /var/lib/apt/lists/*
# CUDA cross target tree -> /usr/local/cuda/targets/sbsa-linux, beside the image's x86_64 tree.
# These packages are NOT in the x86_64 CUDA repo (it publishes none) and NOT in the sbsa repo (that
# is the NATIVE arm64 one a Spark or GH200 installs locally). They have their own repo and keyring:
# repos/ubuntu2404/cross-linux-sbsa/
# repos/ubuntu2404/cross-linux-aarch64/ also exists -- that is the Tegra/Jetson tree, not this one.
RUN set -eux; \
cd /tmp; \
wget -q https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/cross-linux-sbsa/cuda-keyring_1.1-1_all.deb; \
dpkg -i cuda-keyring_1.1-1_all.deb; rm cuda-keyring_1.1-1_all.deb; \
apt-get update; \
V=$(nvcc --version | sed -nE 's/.*release ([0-9]+)\.([0-9]+).*/\1-\2/p'); \
apt-get install -y --no-install-recommends \
cuda-cudart-cross-sbsa-$V cuda-crt-cross-sbsa-$V \
cuda-culibos-cross-sbsa-$V libcufft-cross-sbsa-$V; \
test -f /usr/local/cuda/targets/sbsa-linux/lib/libcufft_static.a; \
apt-get clean; \
rm -rf /var/lib/apt/lists/*
COPY aarch64-sbsa.cmake /opt/cross/aarch64-sbsa.cmake
# Set workdir for your project
WORKDIR /workspace
+39
View File
@@ -0,0 +1,39 @@
# Cross toolchain: x86_64 Ubuntu 24.04 build host -> aarch64 arm64 SBSA.
# Covers DGX Spark (GB10, sm_121) and Grace Hopper (GH200, sm_90) alike -- same host architecture,
# only the CUDA fatbin differs, and that is chosen with -DJFJOCH_CUDA_ARCHITECTURES.
#
# Relies on Debian multiarch rather than a separate sysroot: arm64 libraries live in
# /usr/lib/aarch64-linux-gnu and headers are shared in /usr/include, so CMAKE_LIBRARY_ARCHITECTURE
# is what steers find_library() rather than CMAKE_SYSROOT.
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64) # also selects FFTW's NEON codelets in CMakeLists.txt
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
set(CMAKE_LIBRARY_ARCHITECTURE aarch64-linux-gnu)
# Programs must come from the build host (nvcc, ninja); libraries and headers from the target.
set(CMAKE_FIND_ROOT_PATH /usr/lib/aarch64-linux-gnu /usr)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# HDF5 runs two try_run() probes (config/ConfigureChecks.cmake); without an emulator the configure
# aborts. libzmq guards its own run-checks with NOT CMAKE_CROSSCOMPILING and needs nothing here.
set(CMAKE_CROSSCOMPILING_EMULATOR /usr/bin/qemu-aarch64-static)
# --- CUDA -------------------------------------------------------------------------------------
# nvcc is an x86_64 binary that emits aarch64 host code: -ccbin picks the cross host compiler and
# -target-dir picks which targets/<dir> tree supplies CUDA's headers and libraries.
set(CMAKE_CUDA_COMPILER /usr/local/cuda/bin/nvcc)
set(CMAKE_CUDA_HOST_COMPILER aarch64-linux-gnu-g++)
# fatbinary_section.h ships ONLY in targets/x86_64-linux/include -- it is a host-side codegen header,
# not a target one -- so -target-dir alone leaves the generated .stub.c unable to find it and EVERY
# .cu fails with "fatbinary_section.h: No such file or directory". The header is architecture-neutral,
# so adding the host include dir back is correct rather than a workaround.
set(CMAKE_CUDA_FLAGS_INIT "-target-dir sbsa-linux -I/usr/local/cuda/include")
# Without this, find_package(CUDAToolkit) resolves against the x86_64 tree and hands back x86
# libraries. NVIDIA's cross-linux-sbsa packages ship libcufft_static.a and no libcufft.so, which is
# why a cross build links cuFFT statically; a native build keeps the .so and an $ORIGIN rpath.
set(CUDAToolkit_ROOT /usr/local/cuda/targets/sbsa-linux)
+17 -3
View File
@@ -34,9 +34,23 @@ IF (JFJOCH_CUDA_AVAILABLE)
CUDAMemHelpers.h
FFTIndexerGPU.cu FFTIndexerGPU.h
FFBIDXIndexer.cpp FFBIDXIndexer.h)
TARGET_LINK_LIBRARIES(JFJochIndexing fast_indexer_static
CUDA::cufft
)
# The two shipped products (jfjoch_viewer, rugnux) link cuFFT STATICALLY on every platform.
# It is the only CUDA component that was ever dynamic -- cudart and the fast-feedback indexer are
# already static -- and shipping it as a .so meant each self-contained artifact had to carry the
# library beside its executables and find it again through an $ORIGIN rpath, machinery that
# silently failed for any executable that was not the one the rpath had been set on. Static, an
# artifact is one file that runs; it is also what lets rugnux ship as a bare rugnux.exe on
# Windows. A cross build has no alternative anyway: NVIDIA's cross-linux-sbsa packages carry
# libcufft_static.a and no libcufft.so at all.
#
# The server stack keeps the shared library: its .deb/.rpm take CUDA from the distro, so there is
# nothing to bundle, and every executable in that build (broker, tests, tools) would otherwise
# need the device-link step below.
IF (JFJOCH_PORTABLE_ONLY)
TARGET_LINK_LIBRARIES(JFJochIndexing fast_indexer_static CUDA::cufft_static)
ELSE()
TARGET_LINK_LIBRARIES(JFJochIndexing fast_indexer_static CUDA::cufft)
ENDIF()
ELSE()
MESSAGE(WARNING "CUDA is strongly recommended for image analysis." )
+11 -1
View File
@@ -22,7 +22,17 @@ TARGET_LINK_LIBRARIES(Rugnux JFJochReader JFJochImageAnalysis JFJochWriter gemmi
# default), azint, scale (re-scale/merge stored reflections) or calibration (powder-ring geometry).
ADD_EXECUTABLE(rugnux rugnux_cli.cpp)
TARGET_LINK_LIBRARIES(rugnux Rugnux JFJochReader JFJochImageAnalysis JFJochWriter)
INSTALL(TARGETS rugnux RUNTIME COMPONENT viewer)
INSTALL(TARGETS rugnux RUNTIME COMPONENT rugnux)
# libcufft_static.a carries a relocatable-device-code object (separate_callback.o), so an executable
# linking it needs a CUDA device-link step -- without it the host link fails on an undefined
# __cudaRegisterLinkedBinary_* symbol. CUDA 13 no longer ships libcufft_static_nocallback.a, which
# used to be the way around it. CUDA_RESOLVE_DEVICE_SYMBOLS makes CMake emit that step while leaving
# the host link driver alone, so the -march/-flto flags CI passes still apply.
IF (JFJOCH_PORTABLE_ONLY AND JFJOCH_CUDA_AVAILABLE)
SET_TARGET_PROPERTIES(rugnux PROPERTIES CUDA_RESOLVE_DEVICE_SYMBOLS ON)
ENDIF()
# On Windows this CLI gets getopt/getopt_long from the vendored wingetopt shim (libc has none).
IF (WIN32)
+4 -3
View File
@@ -2,18 +2,19 @@
# Offline analysis CLI tools. These link only the portable libraries (reader,
# image_analysis, writer, common), so they build in viewer-only / Windows packages too
# and install into the "viewer" component.
# jfjoch_extract_hkl and jfjoch_recompress are developer utilities: still built, deliberately not
# installed into any package. They used to ride inside the viewer tarball, which is part of what
# made that artifact confusing.
ADD_EXECUTABLE(jfjoch_extract_hkl jfjoch_extract_hkl.cpp
XdsIntegrateParser.cpp
XdsIntegrateParser.h)
TARGET_LINK_LIBRARIES(jfjoch_extract_hkl JFJochReader)
INSTALL(TARGETS jfjoch_extract_hkl RUNTIME COMPONENT viewer)
# rugnux, the single offline analysis CLI, lives in rugnux/ next to its library.
# In-place re-compress /entry/data/data of a _data file from bitshuffle/LZ4 to bitshuffle/zstd.
ADD_EXECUTABLE(jfjoch_recompress jfjoch_recompress.cpp)
TARGET_LINK_LIBRARIES(jfjoch_recompress JFJochHDF5Wrappers)
INSTALL(TARGETS jfjoch_recompress RUNTIME COMPONENT viewer)
# On Windows these CLIs get getopt/getopt_long from the vendored wingetopt shim (libc has none).
IF (WIN32)
@@ -22,7 +23,7 @@ ENDIF()
# Online / hardware tools (broker, FPGA, receiver, detector). They link the non-portable
# libraries and are Linux-only, so they are skipped in a viewer-only build.
IF(NOT JFJOCH_VIEWER_ONLY)
IF(NOT JFJOCH_PORTABLE_ONLY)
ADD_EXECUTABLE(jfjoch_udp_simulator jfjoch_udp_simulator.cpp UDPSimulator.cpp UDPSimulator.h)
TARGET_LINK_LIBRARIES(jfjoch_udp_simulator JFJochCommon)
INSTALL(TARGETS jfjoch_udp_simulator RUNTIME COMPONENT jfjoch)
+12 -28
View File
@@ -141,6 +141,16 @@ INSTALL(TARGETS jfjoch_viewer
BUNDLE DESTINATION . COMPONENT viewer # macOS .app bundle
RUNTIME DESTINATION bin COMPONENT viewer) # Windows .exe / Linux binary
# libcufft_static.a carries a relocatable-device-code object (separate_callback.o), so an executable
# linking it needs a CUDA device-link step -- without it the host link fails on an undefined
# __cudaRegisterLinkedBinary_* symbol. CUDA 13 no longer ships libcufft_static_nocallback.a, which
# used to be the way around it. CUDA_RESOLVE_DEVICE_SYMBOLS makes CMake emit that step while leaving
# the host link driver alone, so the -march/-flto flags CI passes still apply.
IF (JFJOCH_PORTABLE_ONLY AND JFJOCH_CUDA_AVAILABLE)
SET_TARGET_PROPERTIES(jfjoch_viewer PROPERTIES CUDA_RESOLVE_DEVICE_SYMBOLS ON)
ENDIF()
IF(JFJOCH_VIEWER_DBUS)
TARGET_SOURCES(jfjoch_viewer PRIVATE dbus/JFJochViewerAdaptor.cpp dbus/JFJochViewerAdaptor.h)
TARGET_LINK_LIBRARIES(jfjoch_viewer Qt6::DBus)
@@ -192,32 +202,6 @@ IF(Qt6_VERSION VERSION_GREATER_EQUAL "6.5")
INSTALL(SCRIPT ${jfjoch_viewer_deploy_script} COMPONENT viewer)
ENDIF()
# Bundle the cuFFT runtime next to the viewer so the installed app runs on hosts that have an NVIDIA
# driver but no CUDA toolkit. cuFFT is the ONLY CUDA component we link dynamically (cudart and the
# fast-feedback indexer are static), so it is the one runtime file the toolkit would otherwise have to
# provide; it is self-contained (Windows: depends only on KERNEL32; Linux: only libc/libstdc++ and the
# always-present driver libcuda). GPU build only. macOS has no CUDA, so it is excluded.
IF(JFJOCH_CUDA_AVAILABLE)
IF(WIN32)
# CUDA 13 keeps redistributable DLLs in bin/x64, earlier toolkits in bin -- glob both.
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 viewer)
ELSEIF(UNIX AND NOT APPLE AND JFJOCH_VIEWER_ONLY)
# Only the self-contained Linux .tar.gz (JFJOCH_VIEWER_ONLY) bundles cuFFT: it has no package
# manager, so it must carry its runtime deps, and we want that set really minimal. The .deb/.rpm
# builds deliberately do NOT bundle it - CUDA there is centrally managed by the distro's packages.
# Ship libcufft.so beside the binary and add an $ORIGIN rpath so the loader finds the bundled copy
# (the same self-contained-app idea as the Windows DLL). CUDA::cufft is an UNKNOWN imported target,
# so install its file directly, resolving the symlink chain (libcufft.so -> .so.<major> -> real
# file) so the libcufft.so.<major> the binary is linked against lands in bin.
INSTALL(CODE
"file(INSTALL DESTINATION \"\${CMAKE_INSTALL_PREFIX}/bin\" TYPE SHARED_LIBRARY FOLLOW_SYMLINK_CHAIN FILES \"${CUDA_cufft_LIBRARY}\")"
COMPONENT viewer)
SET_TARGET_PROPERTIES(jfjoch_viewer PROPERTIES INSTALL_RPATH "$ORIGIN")
ENDIF()
# cuFFT is linked statically (see image_analysis/indexing/CMakeLists.txt), so there is no CUDA
# runtime library to ship beside jfjoch_viewer on any platform.
ENDIF()
+2 -2
View File
@@ -37,8 +37,8 @@ TARGET_LINK_LIBRARIES(JFJochWriter JFJochPreview JFJochLogger JFJochHDF5Wrappers
# portable viewer subset: JFJochStreamWriter pulls in JFJochImagePuller (not built in a viewer-only
# tree) and jfjoch_writer uses a fork()/waitpid() multi-process design (<sys/wait.h>, no Windows
# equivalent). The portable JFJochWriter library above is what the CLI tools and viewer need; these
# two targets are not. (Windows/macOS force JFJOCH_VIEWER_ONLY, so this also excludes them there.)
IF (NOT JFJOCH_VIEWER_ONLY)
# two targets are not. (Windows/macOS force a portable build, so this also excludes them there.)
IF (NOT JFJOCH_PORTABLE_ONLY)
ADD_LIBRARY(JFJochStreamWriter StreamWriter.cpp StreamWriter.h)
TARGET_LINK_LIBRARIES(JFJochStreamWriter JFJochWriter JFJochImagePuller)