# Eigen has to be resolved BEFORE Ceres is added below. Whichever find_package(Eigen3) runs first # creates the Eigen3::Eigen imported target, and every later one leaves an existing target alone -- # so the first one wins for the whole build, Ceres included. See the guard after Ceres for why that # matters here. FIND_PACKAGE(Eigen3 3.4 REQUIRED NO_MODULE) # provides Eigen3::Eigen SET(PROVIDE_UNINSTALL_TARGET OFF) # Force Ceres to build WITHOUT its own CUDA support: Jungfraujoch never uses Ceres' GPU solvers, so # this just drops unused code and build time (Ceres would otherwise enable CUDA whenever a toolkit is # found). See the Eigen note in the top-level CMakeLists for the related Windows/cmake history. SET(USE_CUDA OFF CACHE BOOL "Force Ceres to build without CUDA (Jungfraujoch does not use Ceres' GPU solvers)" FORCE) # Ceres' sparse and LAPACK back-ends are dead weight here: every Ceres solve in this project picks a # DENSE solver explicitly (DENSE_QR / DENSE_NORMAL_CHOLESKY / DENSE_SCHUR) and none sets # dense_linear_algebra_library_type, which defaults to EIGEN -- so no sparse factorization and no # LAPACK call is ever reachable. Left at their defaults they cost rugnux six shared libraries # (libopenblas, libmetis, and libgfortran/libquadmath/libgomp/libz behind them), and SuiteSparse # turns itself on or off depending on what happens to sit on the build host, so two machines build # different Ceres from the same source. Pinning all three off leaves the results bit-identical. # EIGENSPARSE stays ON: it keeps a sparse implementation available without an external library. SET(SUITESPARSE OFF CACHE BOOL "Ceres: no sparse solver is ever selected" FORCE) SET(LAPACK OFF CACHE BOOL "Ceres: dense solves go through Eigen, not LAPACK" FORCE) SET(EIGENMETIS OFF CACHE BOOL "Ceres: METIS ordering only applies to sparse factorization" FORCE) # Prevent MKL from being found (guarantees no MKL) SET(CMAKE_DISABLE_FIND_PACKAGE_MKL TRUE) SET(CERES_THREADING_MODEL "CXX_THREADS") # Ceres is the only dependency that needs more than a plain tarball: it carries abseil as a git # SUBMODULE, so a source archive leaves third_party/abseil-cpp empty. Ceres tests exactly that # directory and, finding it empty, demands find_package(absl REQUIRED 20240116) instead -- which no # build image can satisfy (Ubuntu 24.04 still ships 20230802). So populate the two archives # separately and hand Ceres the tree a submodule checkout would have produced: abseil unpacked into # third_party/abseil-cpp, and only then the subdirectory added. # # Populate-then-add rather than FetchContent_MakeAvailable(ceres), because MakeAvailable populates # and add_subdirectory()s in one step, leaving no window to fill the submodule -- and the URL # population clears its source directory, so abseil cannot be put there beforehand. # # FetchContent_Populate() is deprecated from CMake 3.30 (CMP0169) in favour of the # MakeAvailable that cannot be used here, so ask for the old behaviour rather than let every build # on a newer CMake print the deprecation. IF (POLICY CMP0169) CMAKE_POLICY(PUSH) CMAKE_POLICY(SET CMP0169 OLD) ENDIF() FetchContent_Declare( ceres URL https://github.com/ceres-solver/ceres-solver/archive/0c70ed3a1a2d6ba47c06c7e8b3b040880bc474db.tar.gz URL_HASH SHA256=bf0bcc78fae884e59dd893693e52510da7cc6866a7444fdae9ba8f19eae266cd ) FetchContent_Populate(ceres) # The exact commit Ceres' submodule points at (abseil lts_2025_01_27), so the tree matches what a # recursive clone of the pinned Ceres would have checked out. FetchContent_Declare( abseil URL https://github.com/abseil/abseil-cpp/archive/9ac7062b1860d895fb5a8cbf58c3e9ef8f674b5f.tar.gz URL_HASH SHA256=fd5062972ac6c5575bc8fb6fb1cd6e7c563730be96be4c17b8dcca74a9c5892d SOURCE_DIR "${ceres_SOURCE_DIR}/third_party/abseil-cpp" ) FetchContent_Populate(abseil) IF (POLICY CMP0169) CMAKE_POLICY(POP) ENDIF() ADD_SUBDIRECTORY("${ceres_SOURCE_DIR}" "${ceres_BINARY_DIR}" EXCLUDE_FROM_ALL) # Ceres asks for Eigen with a version RANGE -- find_package(Eigen3 3.3.4...5 NO_MODULE) -- and # Eigen's own Eigen3ConfigVersion.cmake rejects any range whose two endpoints differ in major # version. A 3.4 Eigen therefore reports itself INCOMPATIBLE with Ceres' query and the search falls # through to whatever older Eigen comes next on the prefix path (a distro eigen3-devel, say). Having # resolved Eigen above, Ceres inherits our Eigen3::Eigen instead, because a generated targets file # returns early when its targets already exist. (The config package the top-level CMakeLists writes # for the bundled Eigen answers the range as well, so with it Ceres' own query simply succeeds -- # but an externally provided Eigen still behaves as described.) If that ever stops holding, Ceres compiles against # one Eigen and the rest of the project against another; the two versions' identically mangled # template instantiations are then merged at link time -- an ODR violation whose symptom is a crash # inside Eigen. Refuse to build it. GET_PROPERTY(CERES_IMPORTED_TARGETS DIRECTORY ${ceres_SOURCE_DIR} PROPERTY IMPORTED_TARGETS) IF ("Eigen3::Eigen" IN_LIST CERES_IMPORTED_TARGETS) MESSAGE(FATAL_ERROR "Ceres created an Eigen3::Eigen of its own instead of using the Eigen this " "project resolved. Two Eigen versions in one binary is undefined behaviour. Leave a " "single Eigen on the find_package path, or point CMAKE_PREFIX_PATH / Eigen3_DIR at the " "one to use.") ENDIF() ADD_LIBRARY(JFJochImageAnalysis STATIC MXAnalysisWithoutFPGA.cpp MXAnalysisWithoutFPGA.h MXAnalysisAfterFPGA.h MXAnalysisAfterFPGA.cpp IndexAndRefine.cpp IndexAndRefine.h dark_mask_analysis/DarkMaskAnalysis.cpp dark_mask_analysis/DarkMaskAnalysis.h beam_stop/ShadowFinder.cpp beam_stop/ShadowFinder.h $<$:beam_stop/ShadowAccumulatorGPU.cu> $<$:beam_stop/ShadowMaskGPU.cu> beam_stop/ShadowAccumulatorGPU.h beam_stop/ShadowFinderInternal.h beam_stop/ShadowMaskGPU.h hot_pixels/HotPixels.cpp hot_pixels/HotPixels.h hot_pixels/HotPixelsGPU.h $<$:hot_pixels/HotPixelsGPU.cu> rotation_indexer/RotationIndexer.cpp rotation_indexer/RotationIndexer.h WriteReflections.cpp WriteReflections.h LoadFCalcFromMtz.cpp LoadFCalcFromMtz.h UpdateReflectionResolution.cpp UpdateReflectionResolution.h IntegrationOutcome.h rotation_indexer/RotationIndexerCounter.cpp rotation_indexer/RotationIndexerCounter.h) ADD_SUBDIRECTORY(spot_finding) ADD_SUBDIRECTORY(bragg_integration) ADD_SUBDIRECTORY(bragg_prediction) ADD_SUBDIRECTORY(indexing) ADD_SUBDIRECTORY(geom_refinement) ADD_SUBDIRECTORY(lattice_search) ADD_SUBDIRECTORY(scale_merge) ADD_SUBDIRECTORY(image_preprocessing) ADD_SUBDIRECTORY(azint) ADD_SUBDIRECTORY(roi) ADD_SUBDIRECTORY(structure_refinement) TARGET_LINK_LIBRARIES(JFJochImageAnalysis JFJochAzIntEngine JFJochROIIntegration JFJochImagePreprocessing JFJochBraggPrediction JFJochBraggIntegration JFJochLatticeSearch JFJochIndexing JFJochSpotFinding JFJochCommon JFJochGeomRefinement JFJochScaleMerge JFJochStructureRefinement JFJochRugnuxFiles gemmi)