diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b63f0e..ef2468b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,3 +134,96 @@ jobs: flags: windows fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} + + zlib-only-host-build: + name: zlib-only host (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + + - name: Cache vcpkg (Windows) + if: runner.os == 'Windows' + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}\vcpkg_cache + key: zlib-only-vcpkg-${{ runner.os }}-${{ hashFiles('.github/workflows/ci.yml') }} + + - name: Install dependencies (Ubuntu) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y \ + cmake \ + g++ \ + ninja-build \ + zlib1g-dev \ + libgtest-dev + + - name: Install dependencies (macOS) + if: runner.os == 'macOS' + run: | + brew update + brew install ninja zlib googletest + + - name: Install dependencies (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + choco install ninja -y + New-Item -ItemType Directory -Force "$env:GITHUB_WORKSPACE\vcpkg_cache" | Out-Null + git clone https://github.com/microsoft/vcpkg "$env:GITHUB_WORKSPACE\vcpkg" + & "$env:GITHUB_WORKSPACE\vcpkg\bootstrap-vcpkg.bat" + $env:VCPKG_BINARY_SOURCES="clear;files,$env:GITHUB_WORKSPACE\vcpkg_cache,readwrite" + & "$env:GITHUB_WORKSPACE\vcpkg\vcpkg.exe" install ` + zlib ` + gtest ` + --triplet x64-windows + + - name: Setup MSVC environment (Windows) + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + + - name: Configure (Ubuntu) + if: runner.os == 'Linux' + run: | + cmake -S . -B build-zlib-only \ + -G Ninja \ + -DTRX_USE_CONAN=OFF \ + -DTRX_BUILD_TESTS=ON \ + -DTRX_BUILD_EXAMPLES=ON \ + -DTRX_ENABLE_NIFTI=ON \ + -DCMAKE_BUILD_TYPE=Release + + - name: Configure (macOS) + if: runner.os == 'macOS' + run: | + BREW_PREFIX="$(brew --prefix)" + cmake -S . -B build-zlib-only \ + -G Ninja \ + -DTRX_USE_CONAN=OFF \ + -DTRX_BUILD_TESTS=ON \ + -DTRX_BUILD_EXAMPLES=ON \ + -DTRX_ENABLE_NIFTI=ON \ + -DCMAKE_PREFIX_PATH="${BREW_PREFIX}" \ + -DCMAKE_BUILD_TYPE=Release + + - name: Configure (Windows) + if: runner.os == 'Windows' + run: > + cmake -S . -B build-zlib-only + -G Ninja + -DTRX_USE_CONAN=OFF + -DTRX_BUILD_TESTS=ON + -DTRX_ENABLE_NIFTI=ON + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake + + - name: Build + run: cmake --build build-zlib-only --config Release + + - name: Test + run: ctest --test-dir build-zlib-only --output-on-failure -C Release diff --git a/CMakeLists.txt b/CMakeLists.txt index f89b1dc..5fdf61e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ option(TRX_ENABLE_CLANG_TIDY "Run clang-tidy during builds" OFF) option(TRX_ENABLE_INSTALL "Install trx-cpp targets" ${TRX_IS_TOP_LEVEL}) option(TRX_BUILD_DOCS "Build API documentation with Doxygen/Sphinx" OFF) option(TRX_ENABLE_NIFTI "Enable optional NIfTI header utilities" ${TRX_BUILD_EXAMPLES}) +option(TRX_FETCH_EIGEN "Fetch Eigen3 via FetchContent if not found" ON) if(TRX_ENABLE_CLANG_TIDY) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -127,8 +128,27 @@ else() list(PREPEND CMAKE_PREFIX_PATH "${Eigen3_ROOT}") endif() find_package(Eigen3 3.4 CONFIG QUIET) - if(NOT Eigen3_FOUND) - find_package(Eigen3 REQUIRED) # try module mode + if(NOT TARGET Eigen3::Eigen) + # Try module mode for distributions that ship FindEigen3.cmake. + find_package(Eigen3 3.4 QUIET MODULE) + endif() + if(NOT TARGET Eigen3::Eigen AND TRX_FETCH_EIGEN) + message(STATUS "Eigen3 not found; fetching v3.4.0") + # Eigen 3.4.0's CMakeLists.txt unconditionally enters blas/ and lapack/ + # which call enable_language(Fortran). Use SOURCE_SUBDIR to skip running + # Eigen's CMake entirely — Eigen is header-only so we just need the + # include path. + FetchContent_Declare( + eigen + URL https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_SUBDIR _nonexistent + ) + FetchContent_MakeAvailable(eigen) + add_library(Eigen3::Eigen INTERFACE IMPORTED) + set_target_properties(Eigen3::Eigen PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${eigen_SOURCE_DIR}" + ) endif() # Create an imported target if the package did not provide one (module mode) if(NOT TARGET Eigen3::Eigen AND EXISTS "${EIGEN3_INCLUDE_DIR}") @@ -137,6 +157,9 @@ else() INTERFACE_INCLUDE_DIRECTORIES "${EIGEN3_INCLUDE_DIR}" ) endif() + if(NOT TARGET Eigen3::Eigen) + message(FATAL_ERROR "Eigen3 target not found. Install Eigen3 or set TRX_FETCH_EIGEN=ON.") + endif() set(TRX_EIGEN3_TARGET Eigen3::Eigen) endif() @@ -277,6 +300,22 @@ if(TRX_BUILD_DOCS) endif() # ── Installation and package config ───────────────────────────────────────── +# Installation requires that link dependencies are IMPORTED targets (so they +# don't need to be in the export set). This is the case when libzip and Eigen +# were found via find_package (libzip::zip, Eigen3::Eigen). When they were +# built via FetchContent (bare "zip" target), installation is not supported — +# the FetchContent path is a developer/CI convenience, not an installable +# configuration. +if(TRX_ENABLE_INSTALL AND TARGET zip) + get_target_property(_zip_imported zip IMPORTED) + if(NOT _zip_imported) + message(STATUS "trx-cpp: skipping install — libzip was built via FetchContent. " + "Install libzip separately and reconfigure to enable installation.") + set(TRX_ENABLE_INSTALL OFF) + endif() + unset(_zip_imported) +endif() + if(TRX_ENABLE_INSTALL) set(TRX_INSTALL_CONFIGDIR "${CMAKE_INSTALL_LIBDIR}/cmake/trx-cpp") diff --git a/README.md b/README.md index 9766891..be71291 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A C++17 library for reading, writing, and memory-mapping the [TRX tractography f ## Quick start -**Dependencies:** a C++17 compiler, [libzip](https://libzip.org/), [Eigen 3.4+](https://eigen.tuxfamily.org/) +**Dependencies:** a C++17 compiler and zlib (`zlib1g-dev` / `zlib-devel` / Homebrew `zlib`). `libzip` and Eigen 3.4+ are found locally when available, otherwise fetched automatically by CMake. ```cmake # CMakeLists.txt diff --git a/codecov.yml b/codecov.yml index 02ccda9..a4c1c12 100644 --- a/codecov.yml +++ b/codecov.yml @@ -6,6 +6,7 @@ coverage: status: project: default: + informational: true paths: - "include/trx" - "src" diff --git a/docs/building.rst b/docs/building.rst index 03ec2d0..820ea05 100644 --- a/docs/building.rst +++ b/docs/building.rst @@ -7,16 +7,15 @@ Dependencies Required: - C++17 compiler -- libzip -- Eigen3 +- zlib (required by libzip) Installing dependencies ------------------------ The examples below include GoogleTest, which is only required when building -the tests. Ninja is optional but recommended. zlib is only required for the -NIfTI I/O features. +the tests. Ninja is optional but recommended. libzip and Eigen are resolved by +CMake: it uses local installs when present and otherwise fetches them. On Debian-based systems the zip tools have been split into separate packages on recent Ubuntu versions. @@ -25,8 +24,6 @@ on recent Ubuntu versions. sudo apt-get install \ zlib1g-dev \ - libeigen3-dev \ - libzip-dev \ zipcmp \ zipmerge \ ziptool \ @@ -37,7 +34,7 @@ On Mac OS, you can install the dependencies with brew: .. code-block:: bash - brew install libzip eigen googletest ninja zlib + brew install googletest ninja zlib On Windows, you can install the dependencies through vcpkg and chocolatey: @@ -45,7 +42,7 @@ On Windows, you can install the dependencies through vcpkg and chocolatey: .. code-block:: powershell choco install ninja -y - vcpkg install libzip eigen3 gtest zlib + vcpkg install gtest zlib Building to use in other projects @@ -70,6 +67,7 @@ Key CMake options: - ``TRX_BUILD_DOCS``: Build docs with Doxygen/Sphinx (default OFF) - ``TRX_ENABLE_CLANG_TIDY``: Run clang-tidy during builds (default OFF) - ``TRX_USE_CONAN``: Use Conan setup in ``cmake/ConanSetup.cmake`` (default OFF) +- ``TRX_FETCH_EIGEN``: Fetch Eigen3 with FetchContent when not found locally (default ON) To use trx-cpp from another CMake project after installation: @@ -103,7 +101,7 @@ Building for testing Tests require GTest to be discoverable by CMake (e.g., via a system package or ``GTest_DIR``). If GTest is not found, tests will be skipped. -NIfTI I/O tests additionally require zlib to be discoverable (``ZLIB::ZLIB``). +zlib must be discoverable by CMake (``ZLIB::ZLIB``), including for NIfTI I/O. Building documentation: diff --git a/docs/quick_start.rst b/docs/quick_start.rst index 6dd3eee..5afedf8 100644 --- a/docs/quick_start.rst +++ b/docs/quick_start.rst @@ -9,8 +9,10 @@ Prerequisites - A C++17 compiler (GCC ≥ 7, Clang ≥ 5, MSVC 2019+) - CMake ≥ 3.14 -- `libzip `_ -- `Eigen 3.4+ `_ +- zlib development headers/libraries + +``libzip`` and ``Eigen 3.4+`` are discovered from the local system when +available and fetched automatically by CMake otherwise. See :doc:`building` for platform-specific installation commands.