diff --git a/.gitea/workflows/build_and_test.yml b/.gitea/workflows/build_and_test.yml index 233acf43d..daa758725 100644 --- a/.gitea/workflows/build_and_test.yml +++ b/.gitea/workflows/build_and_test.yml @@ -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 }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f0dc8265..2f109d122 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/broker/CMakeLists.txt b/broker/CMakeLists.txt index 6921a7ced..afd91bd2b 100644 --- a/broker/CMakeLists.txt +++ b/broker/CMakeLists.txt @@ -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 diff --git a/docker/build_images.sh b/docker/build_images.sh index a464ef509..b4983d5b2 100755 --- a/docker/build_images.sh +++ b/docker/build_images.sh @@ -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: diff --git a/docker/ubuntu2404/Dockerfile b/docker/ubuntu2404/Dockerfile index ca9678c73..1f8f00e75 100644 --- a/docker/ubuntu2404/Dockerfile +++ b/docker/ubuntu2404/Dockerfile @@ -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 diff --git a/docker/ubuntu2404/aarch64-sbsa.cmake b/docker/ubuntu2404/aarch64-sbsa.cmake new file mode 100644 index 000000000..13ae8b14d --- /dev/null +++ b/docker/ubuntu2404/aarch64-sbsa.cmake @@ -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/ 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) diff --git a/image_analysis/indexing/CMakeLists.txt b/image_analysis/indexing/CMakeLists.txt index 9b26e87ed..227b58aef 100644 --- a/image_analysis/indexing/CMakeLists.txt +++ b/image_analysis/indexing/CMakeLists.txt @@ -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." ) diff --git a/rugnux/CMakeLists.txt b/rugnux/CMakeLists.txt index 6b22bb740..9d859bfb7 100644 --- a/rugnux/CMakeLists.txt +++ b/rugnux/CMakeLists.txt @@ -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) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 6fd80f14c..310097f76 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -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) diff --git a/viewer/CMakeLists.txt b/viewer/CMakeLists.txt index a1ac5c72c..7035fb8e6 100644 --- a/viewer/CMakeLists.txt +++ b/viewer/CMakeLists.txt @@ -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. -> real - # file) so the libcufft.so. 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() diff --git a/writer/CMakeLists.txt b/writer/CMakeLists.txt index 766d3b8dc..a33b967ce 100644 --- a/writer/CMakeLists.txt +++ b/writer/CMakeLists.txt @@ -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 (, 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)