From 48934512f51bfa0a6cb3ee2ca42fecbe094192ac Mon Sep 17 00:00:00 2001 From: VPRamon Date: Sat, 13 Jun 2026 20:15:42 +0200 Subject: [PATCH 1/5] Update subproject commits for siderust and tempoch-cpp --- siderust | 2 +- tempoch-cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/siderust b/siderust index d5e2c50..0fe54bb 160000 --- a/siderust +++ b/siderust @@ -1 +1 @@ -Subproject commit d5e2c5088c4d278b4d2927edea8520559de059a5 +Subproject commit 0fe54bbaa9233d531ed043f5202b6dfe21ff0ce8 diff --git a/tempoch-cpp b/tempoch-cpp index 63f1f8d..40a7779 160000 --- a/tempoch-cpp +++ b/tempoch-cpp @@ -1 +1 @@ -Subproject commit 63f1f8d6c4c4a09ccd8a7d54c33792d238003d20 +Subproject commit 40a7779d80a49521191dea202f58c94b17555480 From 2f48f336d5c14229f48d130cf58fb4985b5819f8 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Sun, 14 Jun 2026 22:46:02 +0200 Subject: [PATCH 2/5] Refactor CI workflows and remove tempoch-cpp submodule - Updated CI workflows to checkout only the siderust submodule. - Removed references to tempoch-cpp and its related checks from CI scripts. - Added a new script to install official qtty-cpp and tempoch-cpp packages. - Updated CMake configuration to find qtty-cpp and tempoch-cpp as installed packages instead of submodules. - Modified Dockerfiles to reflect the removal of tempoch-cpp and adjusted build steps accordingly. - Updated README and documentation to guide users on installing dependencies. - Cleaned up build scripts to focus on the siderust library and its dependencies. --- .github/workflows/ci-build-test-docs.yml | 21 ++-- .github/workflows/ci-coverage.yml | 9 +- .github/workflows/ci-installed-consumer.yml | 37 ++----- .github/workflows/ci-lint.yml | 7 +- .github/workflows/ci-package.yml | 21 ++-- .gitmodules | 3 - CMakeLists.txt | 114 ++++++++++++++++---- Dockerfile | 8 +- Dockerfile.prod | 39 ++----- README.md | 35 +++--- cmake/siderust_cppConfig.cmake.in | 33 ++++-- docs/Doxyfile.in | 3 +- scripts/build.sh | 4 +- scripts/ci.sh | 6 +- scripts/install-official-cpp-deps.sh | 53 +++++++++ scripts/lint.sh | 2 +- scripts/test.sh | 2 +- scripts/validate-package-metadata.sh | 4 +- siderust | 2 +- tempoch-cpp | 1 - 20 files changed, 244 insertions(+), 160 deletions(-) create mode 100755 scripts/install-official-cpp-deps.sh delete mode 160000 tempoch-cpp diff --git a/.github/workflows/ci-build-test-docs.yml b/.github/workflows/ci-build-test-docs.yml index 3fdad52..6a4ad0a 100644 --- a/.github/workflows/ci-build-test-docs.yml +++ b/.github/workflows/ci-build-test-docs.yml @@ -11,7 +11,7 @@ jobs: CARGO_TERM_COLOR: always CMAKE_BUILD_PARALLEL_LEVEL: 2 steps: - - name: Checkout (with submodules) + - name: Checkout (with siderust submodule) uses: actions/checkout@v4 with: submodules: recursive @@ -25,10 +25,6 @@ jobs: echo echo "siderust: $(git -C siderust rev-parse HEAD) ($(git -C siderust describe --tags --always 2>/dev/null || true))" echo "siderust-ffi: $(git -C siderust/siderust-ffi rev-parse HEAD) ($(git -C siderust/siderust-ffi describe --tags --always 2>/dev/null || true))" - echo "tempoch: $(git -C tempoch-cpp/tempoch rev-parse HEAD) ($(git -C tempoch-cpp/tempoch describe --tags --always 2>/dev/null || true))" - echo "tempoch-ffi: $(git -C tempoch-cpp/tempoch/tempoch-ffi rev-parse HEAD) ($(git -C tempoch-cpp/tempoch/tempoch-ffi describe --tags --always 2>/dev/null || true))" - echo "qtty: $(git -C tempoch-cpp/qtty-cpp/qtty rev-parse HEAD) ($(git -C tempoch-cpp/qtty-cpp/qtty describe --tags --always 2>/dev/null || true))" - echo "qtty-ffi: $(git -C tempoch-cpp/qtty-cpp/qtty/qtty-ffi rev-parse HEAD) ($(git -C tempoch-cpp/qtty-cpp/qtty/qtty-ffi describe --tags --always 2>/dev/null || true))" - name: Install system dependencies shell: bash @@ -41,8 +37,13 @@ jobs: ninja-build \ pkg-config \ libssl-dev \ + curl \ graphviz + - name: Install official qtty-cpp and tempoch-cpp packages + shell: bash + run: scripts/install-official-cpp-deps.sh + - name: Install Doxygen 1.16.1 shell: bash run: | @@ -67,24 +68,16 @@ jobs: ~/.cargo/git siderust/target siderust/siderust-ffi/target - tempoch-cpp/tempoch/target - tempoch-cpp/tempoch/tempoch-ffi/target - tempoch-cpp/qtty-cpp/qtty/target - tempoch-cpp/qtty-cpp/qtty/qtty-ffi/target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo- - - name: Validate required submodules exist + - name: Validate required submodule exists shell: bash run: | set -euo pipefail test -f siderust/Cargo.toml test -f siderust/siderust-ffi/Cargo.toml - test -f tempoch-cpp/tempoch/Cargo.toml - test -f tempoch-cpp/tempoch/tempoch-ffi/Cargo.toml - test -f tempoch-cpp/qtty-cpp/qtty/Cargo.toml - test -f tempoch-cpp/qtty-cpp/qtty/qtty-ffi/Cargo.toml - name: Configure (CMake) shell: bash diff --git a/.github/workflows/ci-coverage.yml b/.github/workflows/ci-coverage.yml index 60136a2..6a9f935 100644 --- a/.github/workflows/ci-coverage.yml +++ b/.github/workflows/ci-coverage.yml @@ -10,7 +10,7 @@ jobs: env: CARGO_TERM_COLOR: always steps: - - name: Checkout (with submodules) + - name: Checkout (with siderust submodule) uses: actions/checkout@v4 with: submodules: recursive @@ -27,8 +27,13 @@ jobs: ninja-build \ pkg-config \ libssl-dev \ + curl \ gcovr + - name: Install official qtty-cpp and tempoch-cpp packages + shell: bash + run: scripts/install-official-cpp-deps.sh + - name: Set up Rust (stable) uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -64,7 +69,6 @@ jobs: --root . \ --exclude 'build-coverage/.*' \ --exclude 'siderust/.*' \ - --exclude 'tempoch-cpp/.*' \ --exclude 'tests/.*' \ --exclude 'examples/.*' \ --xml \ @@ -79,7 +83,6 @@ jobs: --root . \ --exclude 'build-coverage/.*' \ --exclude 'siderust/.*' \ - --exclude 'tempoch-cpp/.*' \ --exclude 'tests/.*' \ --exclude 'examples/.*' \ --html-details \ diff --git a/.github/workflows/ci-installed-consumer.yml b/.github/workflows/ci-installed-consumer.yml index 8f4abdb..7ecaadc 100644 --- a/.github/workflows/ci-installed-consumer.yml +++ b/.github/workflows/ci-installed-consumer.yml @@ -22,7 +22,7 @@ jobs: CARGO_TERM_COLOR: always CMAKE_BUILD_PARALLEL_LEVEL: 2 steps: - - name: Checkout (with submodules) + - name: Checkout (with siderust submodule) uses: actions/checkout@v4 with: submodules: recursive @@ -38,8 +38,13 @@ jobs: cmake \ ninja-build \ pkg-config \ + curl \ libssl-dev + - name: Install official qtty-cpp and tempoch-cpp packages + shell: bash + run: scripts/install-official-cpp-deps.sh + - name: Set up Rust (stable) uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -53,36 +58,11 @@ jobs: ~/.cargo/git siderust/target siderust/siderust-ffi/target - tempoch-cpp/tempoch/target - tempoch-cpp/tempoch/tempoch-ffi/target - tempoch-cpp/qtty-cpp/qtty/target - tempoch-cpp/qtty-cpp/qtty/qtty-ffi/target key: ${{ runner.os }}-cargo-installed-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo- - - name: Configure & install qtty-cpp (standalone) - shell: bash - run: | - set -euo pipefail - cmake -S tempoch-cpp/qtty-cpp -B build-qtty -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DQTTY_BUILD_DOCS=OFF - cmake --build build-qtty -j - cmake --install build-qtty --prefix "$GITHUB_WORKSPACE/stage" - - - name: Configure & install tempoch-cpp (standalone, uses staged qtty-cpp) - shell: bash - run: | - set -euo pipefail - cmake -S tempoch-cpp -B build-tempoch -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DTEMPOCH_BUILD_DOCS=OFF \ - -DCMAKE_PREFIX_PATH="$GITHUB_WORKSPACE/stage" - cmake --build build-tempoch -j - cmake --install build-tempoch --prefix "$GITHUB_WORKSPACE/stage" - - - name: Configure siderust-cpp against staged qtty/tempoch + - name: Configure siderust-cpp against official qtty/tempoch packages shell: bash run: | set -euo pipefail @@ -90,8 +70,7 @@ jobs: -DCMAKE_BUILD_TYPE=Release \ -DSIDERUST_BUILD_DOCS=OFF \ -DSIDERUST_CPP_BUILD_TESTS=OFF \ - -DSIDERUST_CPP_BUILD_EXAMPLES=OFF \ - -DCMAKE_PREFIX_PATH="$GITHUB_WORKSPACE/stage" + -DSIDERUST_CPP_BUILD_EXAMPLES=OFF - name: Build siderust_ffi (cdylib) shell: bash diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index facb81c..fea1083 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -10,7 +10,7 @@ jobs: env: CARGO_TERM_COLOR: always steps: - - name: Checkout (with submodules) + - name: Checkout (with siderust submodule) uses: actions/checkout@v4 with: submodules: recursive @@ -24,8 +24,13 @@ jobs: sudo apt-get install -y --no-install-recommends \ cmake \ ninja-build \ + curl \ clang-tidy + - name: Install official qtty-cpp and tempoch-cpp packages + shell: bash + run: scripts/install-official-cpp-deps.sh + - name: Install clang-format 18 shell: bash run: | diff --git a/.github/workflows/ci-package.yml b/.github/workflows/ci-package.yml index 3de3a2e..fa89840 100644 --- a/.github/workflows/ci-package.yml +++ b/.github/workflows/ci-package.yml @@ -11,7 +11,7 @@ jobs: CARGO_TERM_COLOR: always CMAKE_BUILD_PARALLEL_LEVEL: 2 steps: - - name: Checkout (with submodules) + - name: Checkout (with siderust submodule) uses: actions/checkout@v4 with: submodules: recursive @@ -28,8 +28,13 @@ jobs: ninja-build \ pkg-config \ libssl-dev \ + curl \ rpm + - name: Install official qtty-cpp and tempoch-cpp packages + shell: bash + run: scripts/install-official-cpp-deps.sh + - name: Set up Rust (stable) uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -43,10 +48,6 @@ jobs: ~/.cargo/git siderust/target siderust/siderust-ffi/target - tempoch-cpp/tempoch/target - tempoch-cpp/tempoch/tempoch-ffi/target - tempoch-cpp/qtty-cpp/qtty/target - tempoch-cpp/qtty-cpp/qtty/qtty-ffi/target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo- @@ -67,16 +68,6 @@ jobs: shell: bash run: cmake --install build --prefix build/staging - - name: Copy shared libraries to staging - shell: bash - run: | - set -euo pipefail - mkdir -p build/staging/lib - cp siderust/target/release/libsiderust_ffi.so \ - tempoch-cpp/tempoch/tempoch-ffi/target/release/libtempoch_ffi.so \ - tempoch-cpp/qtty-cpp/qtty/target/release/libqtty_ffi.so \ - build/staging/lib/ - - name: Generate packages (CPack) shell: bash run: | diff --git a/.gitmodules b/.gitmodules index 6e313be..a30ff87 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "tempoch-cpp"] - path = tempoch-cpp - url = https://github.com/Siderust/tempoch-cpp.git [submodule "siderust"] path = siderust url = https://github.com/Siderust/siderust.git diff --git a/CMakeLists.txt b/CMakeLists.txt index a386347..98c3b96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,7 +87,6 @@ add_custom_target( COMMAND ${CARGO_BIN} rustc --release --crate-type cdylib ${_SIDERUST_FEATURES_ARGS} WORKING_DIRECTORY ${SIDERUST_FFI_DIR} BYPRODUCTS ${SIDERUST_LIBRARY_PATH} - DEPENDS build_tempoch_ffi COMMENT "Building siderust-ffi via Cargo (cdylib override)" VERBATIM ) @@ -103,21 +102,78 @@ add_dependencies(siderust_ffi build_siderust_ffi) # --------------------------------------------------------------------------- # Header-only C++ wrapper library # --------------------------------------------------------------------------- -# Pull in qtty-cpp (unit-safe quantities) via the nested tempoch-cpp checkout -set(QTTY_FFI_FEATURES "" CACHE STRING "Cargo features for qtty-ffi" FORCE) -add_subdirectory(tempoch-cpp/qtty-cpp) +find_package(qtty_cpp 0.4.5 REQUIRED) +find_package(tempoch_cpp 0.5.4 REQUIRED) -# Pull in tempoch-cpp (time types) as a subdirectory -set(TEMPOCH_FFI_FEATURES "" CACHE STRING "Cargo features for tempoch-ffi" FORCE) -add_subdirectory(tempoch-cpp) +function(siderust_import_packaged_ffi_target target_name library_stem package_dir) + if(TARGET ${target_name}) + return() + endif() + + get_filename_component(_siderust_package_prefix "${package_dir}/../../.." ABSOLUTE) + set(_siderust_install_libdir lib) + set(_siderust_install_bindir bin) + if(DEFINED CMAKE_INSTALL_LIBDIR) + set(_siderust_install_libdir "${CMAKE_INSTALL_LIBDIR}") + endif() + if(DEFINED CMAKE_INSTALL_BINDIR) + set(_siderust_install_bindir "${CMAKE_INSTALL_BINDIR}") + endif() + set(_siderust_ffi_search_paths + "${_siderust_package_prefix}/${_siderust_install_libdir}" + "${_siderust_package_prefix}/${_siderust_install_bindir}" + "${_siderust_package_prefix}/lib" + "${_siderust_package_prefix}/lib64" + "${_siderust_package_prefix}/bin") + if(CMAKE_PREFIX_PATH) + foreach(_siderust_prefix IN LISTS CMAKE_PREFIX_PATH) + list(APPEND _siderust_ffi_search_paths + "${_siderust_prefix}/${_siderust_install_libdir}" + "${_siderust_prefix}/${_siderust_install_bindir}" + "${_siderust_prefix}/lib" + "${_siderust_prefix}/lib64" + "${_siderust_prefix}/bin") + endforeach() + endif() -# Paths to qtty-ffi shared library (for RPATH) -set(QTTY_SUBMODULE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tempoch-cpp/qtty-cpp/qtty) -set(QTTY_ARTIFACT_DIR ${QTTY_SUBMODULE_DIR}/target/release) + if(WIN32) + unset(_siderust_ffi_import_library CACHE) + unset(_siderust_ffi_runtime_library CACHE) + find_library(_siderust_ffi_import_library + NAMES ${library_stem} + PATHS ${_siderust_ffi_search_paths} + NO_DEFAULT_PATH) + find_file(_siderust_ffi_runtime_library + NAMES ${library_stem}.dll + PATHS ${_siderust_ffi_search_paths} + NO_DEFAULT_PATH) + if(NOT _siderust_ffi_import_library OR NOT _siderust_ffi_runtime_library) + message(FATAL_ERROR + "Could not locate ${library_stem} import/runtime libraries under " + "${_siderust_ffi_search_paths}. Install the official package or set CMAKE_PREFIX_PATH.") + endif() + add_library(${target_name} SHARED IMPORTED GLOBAL) + set_target_properties(${target_name} PROPERTIES + IMPORTED_LOCATION "${_siderust_ffi_runtime_library}" + IMPORTED_IMPLIB "${_siderust_ffi_import_library}") + else() + unset(_siderust_ffi_library CACHE) + find_library(_siderust_ffi_library + NAMES ${library_stem} lib${library_stem} + PATHS ${_siderust_ffi_search_paths} + NO_DEFAULT_PATH) + if(NOT _siderust_ffi_library) + message(FATAL_ERROR + "Could not locate ${library_stem} under ${_siderust_ffi_search_paths}. " + "Install the official package or set CMAKE_PREFIX_PATH.") + endif() + add_library(${target_name} SHARED IMPORTED GLOBAL) + set_target_properties(${target_name} PROPERTIES IMPORTED_LOCATION "${_siderust_ffi_library}") + endif() +endfunction() -# Paths to tempoch-ffi shared library (for RPATH) -set(TEMPOCH_SUBMODULE_DIR_PARENT ${CMAKE_CURRENT_SOURCE_DIR}/tempoch-cpp/tempoch) -set(TEMPOCH_ARTIFACT_DIR ${TEMPOCH_SUBMODULE_DIR_PARENT}/tempoch-ffi/target/release) +siderust_import_packaged_ffi_target(qtty_ffi qtty_ffi "${qtty_cpp_DIR}") +siderust_import_packaged_ffi_target(tempoch_ffi tempoch_ffi "${tempoch_cpp_DIR}") add_library(siderust_cpp INTERFACE) add_library(siderust::siderust_cpp ALIAS siderust_cpp) @@ -126,13 +182,13 @@ target_include_directories(siderust_cpp INTERFACE $ $ ) -# Build-tree linking uses the in-tree imported targets. Install-tree linking -# uses the imported targets defined in siderust_cppConfig.cmake (which point -# at the installed shared library) plus the qtty / tempoch package configs. +# Build-tree and install-tree linking use official qtty-cpp / tempoch-cpp +# package targets. Local dependency development is supported by staging those +# packages and passing their prefix through CMAKE_PREFIX_PATH. target_link_libraries(siderust_cpp INTERFACE $ - $ - $ + $ + $ $ $ $ @@ -147,6 +203,20 @@ if(SIDERUST_BUILD_DOCS) if(DOXYGEN_FOUND) set(SIDERUST_DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in) set(SIDERUST_DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.siderust_cpp) + set(_siderust_doxygen_dependency_include_paths "") + foreach(_siderust_dependency_target qtty::qtty_cpp tempoch::tempoch_cpp) + get_target_property(_siderust_dependency_includes + ${_siderust_dependency_target} INTERFACE_INCLUDE_DIRECTORIES) + if(_siderust_dependency_includes) + foreach(_siderust_dependency_include IN LISTS _siderust_dependency_includes) + list(APPEND _siderust_doxygen_dependency_include_paths + "\"${_siderust_dependency_include}\"") + endforeach() + endif() + endforeach() + list(REMOVE_DUPLICATES _siderust_doxygen_dependency_include_paths) + list(JOIN _siderust_doxygen_dependency_include_paths " \\\n " + SIDERUST_DOXYGEN_DEPENDENCY_INCLUDE_PATHS) configure_file(${SIDERUST_DOXYFILE_IN} ${SIDERUST_DOXYFILE_OUT} @ONLY) if(NOT TARGET docs) @@ -167,9 +237,9 @@ endif() # RPATH for shared library lookup at runtime (build tree). if(APPLE) - set(_siderust_rpath "${SIDERUST_ARTIFACT_DIR};@loader_path/../tempoch-cpp/tempoch/tempoch-ffi/target/release;@loader_path/../tempoch-cpp/qtty-cpp/qtty/target/release") + set(_siderust_rpath "${SIDERUST_ARTIFACT_DIR}") elseif(UNIX) - set(_siderust_rpath "${SIDERUST_ARTIFACT_DIR}:$ORIGIN/../tempoch-cpp/tempoch/tempoch-ffi/target/release:$ORIGIN/../tempoch-cpp/qtty-cpp/qtty/target/release") + set(_siderust_rpath "${SIDERUST_ARTIFACT_DIR}") endif() # --------------------------------------------------------------------------- @@ -431,13 +501,13 @@ if(SIDERUST_CPP_ENABLE_PACKAGING AND SIDERUST_CPP_INSTALL) # copied into Debian/RPM metadata. set(CPACK_DEBIAN_PACKAGE_MAINTAINER "VPRamon ") set(CPACK_DEBIAN_PACKAGE_SECTION "libs") - set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.17), libstdc++6 (>= 9), qtty-cpp (>= 0.4.5), tempoch-cpp (>= 0.5.2)") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.17), libstdc++6 (>= 9), qtty-cpp (>= 0.4.5), tempoch-cpp (>= 0.5.4)") set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) # -- RPM --------------------------------------------------------------- set(CPACK_RPM_PACKAGE_LICENSE "AGPL-3.0") set(CPACK_RPM_PACKAGE_GROUP "Development/Libraries") - set(CPACK_RPM_PACKAGE_REQUIRES "glibc >= 2.17, libstdc++ >= 9, qtty-cpp >= 0.4.5, tempoch-cpp >= 0.5.2") + set(CPACK_RPM_PACKAGE_REQUIRES "glibc >= 2.17, libstdc++ >= 9, qtty-cpp >= 0.4.5, tempoch-cpp >= 0.5.4") set(CPACK_RPM_FILE_NAME RPM-DEFAULT) set(CPACK_GENERATOR "DEB;RPM") diff --git a/Dockerfile b/Dockerfile index 4af2466..e31d3ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,10 +34,10 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --pr WORKDIR /workspace COPY . /workspace -# Fail early if required submodules are missing from the build context. -RUN test -f siderust/siderust-ffi/Cargo.toml && \ - test -f tempoch-cpp/tempoch/tempoch-ffi/Cargo.toml && \ - test -f tempoch-cpp/qtty-cpp/CMakeLists.txt +# Install official C++ dependency packages and fail early if the required +# siderust submodule is missing from the build context. +RUN scripts/install-official-cpp-deps.sh && \ + test -f siderust/siderust-ffi/Cargo.toml # Validate the container toolchain by configuring, building, testing, and generating docs. RUN rm -rf build && \ diff --git a/Dockerfile.prod b/Dockerfile.prod index f4e5e3d..8f22f34 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -1,7 +1,7 @@ # Dockerfile.prod — Multi-stage production image for siderust-cpp # # Stages: -# 1. rust-builder — builds libsiderust_ffi.so, libtempoch_ffi.so, libqtty_ffi.so +# 1. rust-builder — builds libsiderust_ffi.so # 2. cpp-builder — configures CMake, runs install into /install # 3. runtime — minimal Ubuntu 22.04 with only the installed artefacts @@ -31,26 +31,11 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ WORKDIR /src COPY siderust/ siderust/ -COPY tempoch-cpp/ tempoch-cpp/ -# Build all Rust shared libraries in release mode. -# -# Path rules (must match siderust-cpp's top-level CMakeLists.txt and -# the inner tempoch-cpp / qtty-cpp CMakeLists): -# -# - siderust : workspace target dir → /src/siderust/target/release/ -# - tempoch : CARGO_TARGET_DIR is overridden → /src/tempoch-cpp/tempoch/tempoch-ffi/target/release/ -# - qtty : workspace target dir → /src/tempoch-cpp/qtty-cpp/qtty/target/release/ +# Build the siderust Rust shared library in release mode. RUN cargo rustc --release --crate-type cdylib \ --manifest-path /src/siderust/siderust-ffi/Cargo.toml -RUN CARGO_TARGET_DIR=/src/tempoch-cpp/tempoch/tempoch-ffi/target \ - cargo rustc --release --crate-type cdylib \ - --manifest-path /src/tempoch-cpp/tempoch/tempoch-ffi/Cargo.toml - -RUN cargo build --release -p qtty-ffi \ - --manifest-path /src/tempoch-cpp/qtty-cpp/qtty/Cargo.toml - # --------------------------------------------------------------------------- # Stage 2: C++ build and install # --------------------------------------------------------------------------- @@ -78,20 +63,14 @@ COPY --from=rust-builder /usr/local/cargo /usr/local/cargo WORKDIR /src COPY . /src/ -# Bring pre-built Rust artefacts into the expected target/release locations +RUN scripts/install-official-cpp-deps.sh + +# Bring the pre-built Rust artefact into the expected target/release location # so CMake's custom target finds them and does not rebuild. COPY --from=rust-builder \ /src/siderust/target/release/libsiderust_ffi.so \ /src/siderust/target/release/libsiderust_ffi.so -COPY --from=rust-builder \ - /src/tempoch-cpp/tempoch/tempoch-ffi/target/release/libtempoch_ffi.so \ - /src/tempoch-cpp/tempoch/tempoch-ffi/target/release/libtempoch_ffi.so - -COPY --from=rust-builder \ - /src/tempoch-cpp/qtty-cpp/qtty/target/release/libqtty_ffi.so \ - /src/tempoch-cpp/qtty-cpp/qtty/target/release/libqtty_ffi.so - RUN cmake -S . -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DSIDERUST_BUILD_DOCS=OFF \ @@ -99,11 +78,9 @@ RUN cmake -S . -B build -G Ninja \ && cmake --build build --target test_siderust \ && cmake --install build -# Copy shared libraries into the install prefix. +# Copy the siderust shared library into the install prefix. RUN mkdir -p /install/lib && \ cp /src/siderust/target/release/libsiderust_ffi.so \ - /src/tempoch-cpp/tempoch/tempoch-ffi/target/release/libtempoch_ffi.so \ - /src/tempoch-cpp/qtty-cpp/qtty/target/release/libqtty_ffi.so \ /install/lib/ # --------------------------------------------------------------------------- @@ -118,9 +95,13 @@ LABEL org.opencontainers.image.source="https://github.com/Siderust/siderust-cpp" RUN apt-get update && apt-get install -y --no-install-recommends \ libstdc++6 \ libgcc-s1 \ + curl \ ca-certificates \ && rm -rf /var/lib/apt/lists/* +COPY --from=cpp-builder /src/scripts/install-official-cpp-deps.sh /tmp/install-official-cpp-deps.sh +RUN /tmp/install-official-cpp-deps.sh && rm -f /tmp/install-official-cpp-deps.sh + COPY --from=cpp-builder /install /usr/local # Refresh the dynamic linker cache so the copied .so files are found. diff --git a/README.md b/README.md index 90b217a..5589b46 100644 --- a/README.md +++ b/README.md @@ -81,10 +81,13 @@ you need a raw scalar for computation. ## Building ```bash -# Clone with submodules +# Clone with the siderust submodule git clone --recurse-submodules cd siderust-cpp +# Install official qtty-cpp and tempoch-cpp packages +scripts/install-official-cpp-deps.sh + # Build mkdir build && cd build cmake .. @@ -115,10 +118,13 @@ built with CMake + CPack. ```bash # Debian/Ubuntu -sudo apt-get install cmake ninja-build rpm +sudo apt-get install cmake ninja-build rpm curl # RHEL/Fedora -sudo dnf install cmake ninja-build dpkg +sudo dnf install cmake ninja-build dpkg curl + +# Install official qtty-cpp and tempoch-cpp packages +scripts/install-official-cpp-deps.sh ``` ### Build the packages @@ -133,13 +139,6 @@ cmake --build build --parallel # Install headers + cmake config to a staging prefix cmake --install build --prefix build/staging -# Copy shared libraries into the staging tree -mkdir -p build/staging/lib -cp siderust/target/release/libsiderust_ffi.so \ - tempoch-cpp/tempoch/tempoch-ffi/target/release/libtempoch_ffi.so \ - tempoch-cpp/qtty-cpp/qtty/target/release/libqtty_ffi.so \ - build/staging/lib/ - # Generate .deb and .rpm in build/packages/ cd build cpack --config CPackConfig.cmake -G "DEB;RPM" -B packages @@ -150,14 +149,16 @@ ls packages/ ```bash # Debian/Ubuntu -sudo dpkg -i packages/siderust-cpp-*.deb +sudo apt-get install ./packages/siderust-cpp-*.deb # RHEL/Fedora/openSUSE -sudo rpm -i packages/siderust-cpp-*.rpm +sudo dnf install ./packages/siderust-cpp-*.rpm ``` After installation, headers land in `/usr/local/include/siderust/` and the -shared libraries in `/usr/local/lib/`. CMake consumers can then use: +`siderust_ffi` shared library lands in `/usr/local/lib/`. The `qtty-cpp` and +`tempoch-cpp` shared libraries are provided by their official packages. CMake +consumers can then use: ```cmake find_package(siderust_cpp REQUIRED) @@ -177,7 +178,7 @@ libraries at runtime: `libsiderust_ffi`, `libtempoch_ffi`, and `libqtty_ffi`. |----------------|---------------|----------------| | **System packages** (`.deb` / `.rpm` into `/usr` or `/usr/local`) | The dynamic linker finds libraries in standard `lib/` paths after `ldconfig` or equivalent. No extra environment variables are usually required. | Install `siderust_ffi.dll`, `tempoch_ffi.dll`, and `qtty_ffi.dll` under `bin/` (or ensure that directory is on `PATH`). Link via the `.dll.lib` import libraries in `lib/`. | | **Custom CMake prefix** (`cmake --install … --prefix /opt/foo`) | Either add `/opt/foo/lib` to `LD_LIBRARY_PATH` (Linux) or `DYLD_LIBRARY_PATH` (macOS), **or** link your executable with an RPATH/`@rpath` that points at `$ORIGIN/../lib` relative to your binary. The installed-consumer CI smoke test uses the latter when built with `CMAKE_PREFIX_PATH` set to the staged prefix. | Add the install `bin/` directory to `PATH`, or copy the three DLLs next to your executable. `find_package(siderust_cpp)` sets `IMPORTED_LOCATION` (DLL) and `IMPORTED_IMPLIB` (`.dll.lib`) on `siderust::siderust_ffi`. | -| **In-tree build** (`add_subdirectory` / local `build/`) | CMake sets `BUILD_RPATH` on examples and tests to the Cargo `target/release` directories under the submodules. | Same as custom prefix: DLLs live next to import libraries under each crate’s `target/release/`. | +| **In-tree build** (`add_subdirectory` / local `build/`) | CMake sets `BUILD_RPATH` for the local `siderust/target/release` library. `qtty-cpp` and `tempoch-cpp` come from installed packages or from a staged local prefix passed through `CMAKE_PREFIX_PATH`. | Same as custom prefix: ensure the official or staged dependency DLL directory is on `PATH`. | `find_package(siderust_cpp)` defines imported targets `siderust::siderust_cpp` and `siderust::siderust_ffi` so that **linking** resolves symbols; **loading** still @@ -194,7 +195,7 @@ The repository includes a root `Dockerfile` that installs all build dependencies (CMake, Rust, Doxygen), then runs configure/build/tests/docs during image build. ```bash -# Clone with submodules +# Clone with the siderust submodule git clone --recurse-submodules cd siderust-cpp @@ -245,6 +246,7 @@ Generated HTML entry point: - C++17 compiler (GCC 8+, Clang 7+, MSVC 2019+) - CMake 3.21+ - Rust toolchain (cargo) — Rust FFI libraries are built automatically +- Installed `qtty-cpp` and `tempoch-cpp` packages, or local staged installs passed through `CMAKE_PREFIX_PATH` - Internet connection for Google Test (fetched automatically) ### Project Layout @@ -291,8 +293,7 @@ siderust-cpp/ │ ├── test_bodies.cpp │ ├── test_altitude.cpp │ └── test_ephemeris.cpp -├── siderust-ffi/ ← git submodule (contains `siderust` as nested submodule) -└── tempoch-cpp/qtty-cpp/ ← nested git submodule +└── siderust/ ← git submodule with `siderust-ffi` ``` ## Time API diff --git a/cmake/siderust_cppConfig.cmake.in b/cmake/siderust_cppConfig.cmake.in index d63f818..5c4346e 100644 --- a/cmake/siderust_cppConfig.cmake.in +++ b/cmake/siderust_cppConfig.cmake.in @@ -2,8 +2,8 @@ include(CMakeFindDependencyMacro) -find_dependency(qtty_cpp REQUIRED) -find_dependency(tempoch_cpp REQUIRED) +find_dependency(qtty_cpp 0.4.5 REQUIRED) +find_dependency(tempoch_cpp 0.5.4 REQUIRED) # --------------------------------------------------------------------------- # Reconstruct the absolute install paths from PACKAGE_PREFIX_DIR (set by @@ -37,21 +37,22 @@ if(WIN32 AND NOT EXISTS "${SIDERUST_FFI_IMPLIB}") endif() # --------------------------------------------------------------------------- -# Workaround for upstream qtty-cpp / tempoch-cpp: their installed +# Workaround for official qtty-cpp / tempoch-cpp packages: their installed # *Targets.cmake exports the bare names `qtty_ffi` and `tempoch_ffi` in -# INTERFACE_LINK_LIBRARIES (because they were build-tree SHARED IMPORTED -# targets) but neither *Config.cmake defines a matching imported target in -# the installed tree. Worse, those configs currently bake in a broken +# INTERFACE_LINK_LIBRARIES, but neither *Config.cmake defines a matching +# imported target in the installed tree. Worse, those configs currently bake in a broken # absolute path for QTTY_FFI_LIBRARY / TEMPOCH_FFI_LIBRARY (the @PACKAGE_*@ # substitution did not include CMAKE_INSTALL_PREFIX in PATH_VARS, so the # path comes out as e.g. "/lib/libqtty_ffi.so"). # -# Instead of trusting QTTY_FFI_LIBRARY / TEMPOCH_FFI_LIBRARY, locate the -# shared libraries ourselves under the install prefix. +# Instead of trusting QTTY_FFI_LIBRARY / TEMPOCH_FFI_LIBRARY, locate the shared +# libraries ourselves under the dependency package prefixes and this package's prefix. # --------------------------------------------------------------------------- +set(_siderust_config_libdir "@CMAKE_INSTALL_LIBDIR@") +set(_siderust_config_bindir "@CMAKE_INSTALL_BINDIR@") set(_siderust_ffi_search_paths - "${PACKAGE_PREFIX_DIR}/${CMAKE_INSTALL_LIBDIR}" - "${PACKAGE_PREFIX_DIR}/${CMAKE_INSTALL_BINDIR}" + "${PACKAGE_PREFIX_DIR}/${_siderust_config_libdir}" + "${PACKAGE_PREFIX_DIR}/${_siderust_config_bindir}" "${PACKAGE_PREFIX_DIR}/lib" "${PACKAGE_PREFIX_DIR}/lib64" "${PACKAGE_PREFIX_DIR}/bin" @@ -59,6 +60,18 @@ set(_siderust_ffi_search_paths if(WIN32) list(APPEND _siderust_ffi_search_paths "${SIDERUST_CPP_BINDIR}") endif() +foreach(_siderust_dependency_config_dir IN ITEMS "${qtty_cpp_DIR}" "${tempoch_cpp_DIR}") + if(_siderust_dependency_config_dir) + get_filename_component(_siderust_dependency_prefix + "${_siderust_dependency_config_dir}/../../.." ABSOLUTE) + list(APPEND _siderust_ffi_search_paths + "${_siderust_dependency_prefix}/${_siderust_config_libdir}" + "${_siderust_dependency_prefix}/${_siderust_config_bindir}" + "${_siderust_dependency_prefix}/lib" + "${_siderust_dependency_prefix}/lib64" + "${_siderust_dependency_prefix}/bin") + endif() +endforeach() if(NOT TARGET qtty_ffi) if(WIN32) diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index 4c90c6f..878ce0e 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -48,9 +48,8 @@ MACRO_EXPANSION = NO SKIP_FUNCTION_MACROS = YES INCLUDE_PATH = \ "@CMAKE_CURRENT_SOURCE_DIR@/include" \ - "@CMAKE_CURRENT_SOURCE_DIR@/tempoch-cpp/qtty-cpp/include" \ "@CMAKE_CURRENT_SOURCE_DIR@/siderust/siderust-ffi/include" \ - "@CMAKE_CURRENT_SOURCE_DIR@/tempoch-cpp/tempoch/tempoch-ffi/include" + @SIDERUST_DOXYGEN_DEPENDENCY_INCLUDE_PATHS@ SOURCE_BROWSER = YES INLINE_SOURCES = NO diff --git a/scripts/build.sh b/scripts/build.sh index 6630db8..8c52168 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -7,7 +7,7 @@ source "${SCRIPT_DIR}/lib.sh" BUILD_DIR="build" BUILD_DOCS="ON" -TARGET="test_tempoch" +TARGET="test_siderust" PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL:-2}" usage() { @@ -56,7 +56,7 @@ require_cmd ninja header "Configure: CMake (${BUILD_DIR})" ensure_fresh_cmake_build_dir "${BUILD_DIR}" "${REPO_ROOT}" -cmake -S . -B "${BUILD_DIR}" -G Ninja -DTEMPOCH_BUILD_DOCS="${BUILD_DOCS}" +cmake -S . -B "${BUILD_DIR}" -G Ninja -DSIDERUST_BUILD_DOCS="${BUILD_DOCS}" header "Build: ${TARGET}" CMAKE_BUILD_PARALLEL_LEVEL="${PARALLEL_LEVEL}" cmake --build "${BUILD_DIR}" --target "${TARGET}" diff --git a/scripts/ci.sh b/scripts/ci.sh index e47793d..f49f93e 100644 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -104,11 +104,11 @@ ensure_repo_root header "Submodule check" git submodule status --recursive || true -if [[ ! -f tempoch/Cargo.toml ]]; then - fail "tempoch submodule missing (run: git submodule update --init --recursive)" +if [[ ! -f siderust/siderust-ffi/Cargo.toml ]]; then + fail "siderust submodule missing (run: git submodule update --init --recursive)" exit 1 fi -ok "tempoch submodule present" +ok "siderust submodule present" if [[ "${RUN_FMT}" == "true" ]]; then "${SCRIPT_DIR}/fmt.sh" "--${FMT_MODE}" diff --git a/scripts/install-official-cpp-deps.sh b/scripts/install-official-cpp-deps.sh new file mode 100755 index 0000000..44fd628 --- /dev/null +++ b/scripts/install-official-cpp-deps.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +QTTY_CPP_VERSION="${QTTY_CPP_VERSION:-0.4.5}" +TEMPOCH_CPP_VERSION="${TEMPOCH_CPP_VERSION:-0.5.4}" +SIDERUST_PACKAGE_BASE_URL="${SIDERUST_PACKAGE_BASE_URL:-https://siderust.org}" + +tmp_dir="$(mktemp -d)" +cleanup() { + rm -rf "${tmp_dir}" +} +trap cleanup EXIT + +run_privileged() { + if [[ "$(id -u)" -eq 0 ]]; then + "$@" + elif command -v sudo >/dev/null 2>&1; then + sudo "$@" + else + echo "This installer needs root privileges or sudo to install system packages." >&2 + return 1 + fi +} + +download() { + local url="$1" + local output="$2" + echo "Downloading ${url}" + curl -fsSL "${url}" -o "${output}" +} + +if command -v apt-get >/dev/null 2>&1 && command -v dpkg >/dev/null 2>&1; then + qtty_pkg="${tmp_dir}/qtty-cpp_${QTTY_CPP_VERSION}_amd64.deb" + tempoch_pkg="${tmp_dir}/tempoch-cpp_${TEMPOCH_CPP_VERSION}_amd64.deb" + download "${SIDERUST_PACKAGE_BASE_URL}/apt/qtty-cpp_${QTTY_CPP_VERSION}_amd64.deb" "${qtty_pkg}" + download "${SIDERUST_PACKAGE_BASE_URL}/apt/tempoch-cpp_${TEMPOCH_CPP_VERSION}_amd64.deb" "${tempoch_pkg}" + run_privileged apt-get install -y --no-install-recommends "${qtty_pkg}" "${tempoch_pkg}" +elif command -v rpm >/dev/null 2>&1; then + qtty_pkg="${tmp_dir}/qtty-cpp-${QTTY_CPP_VERSION}-1.x86_64.rpm" + tempoch_pkg="${tmp_dir}/tempoch-cpp-${TEMPOCH_CPP_VERSION}-1.x86_64.rpm" + download "${SIDERUST_PACKAGE_BASE_URL}/rpm/qtty-cpp-${QTTY_CPP_VERSION}-1.x86_64.rpm" "${qtty_pkg}" + download "${SIDERUST_PACKAGE_BASE_URL}/rpm/tempoch-cpp-${TEMPOCH_CPP_VERSION}-1.x86_64.rpm" "${tempoch_pkg}" + if command -v dnf >/dev/null 2>&1; then + run_privileged dnf install -y "${qtty_pkg}" "${tempoch_pkg}" + elif command -v yum >/dev/null 2>&1; then + run_privileged yum install -y "${qtty_pkg}" "${tempoch_pkg}" + else + run_privileged rpm -Uvh "${qtty_pkg}" "${tempoch_pkg}" + fi +else + echo "Unsupported package manager. Install qtty-cpp and tempoch-cpp manually, then pass their prefix through CMAKE_PREFIX_PATH if needed." >&2 + exit 1 +fi diff --git a/scripts/lint.sh b/scripts/lint.sh index f676fd3..7771cb9 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -63,7 +63,7 @@ fi header "Lint: clang-tidy" ensure_fresh_cmake_build_dir "build" "${REPO_ROOT}" -cmake -S . -B build -G Ninja -DTEMPOCH_BUILD_DOCS=OFF -DCMAKE_EXPORT_COMPILE_COMMANDS=ON >/dev/null +cmake -S . -B build -G Ninja -DSIDERUST_BUILD_DOCS=OFF -DCMAKE_EXPORT_COMPILE_COMMANDS=ON >/dev/null mapfile -t cpp_files < <(git ls-files '*.cpp') if [[ ${#cpp_files[@]} -eq 0 ]]; then diff --git a/scripts/test.sh b/scripts/test.sh index 3f85be8..f514c82 100644 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -6,7 +6,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/lib.sh" BUILD_DIR="build" -LABEL="tempoch_cpp" +LABEL="siderust_cpp" usage() { cat <= 0.8)" "tempoch-cpp (>= 0.6)"; do fi done -for required in "qtty-cpp (>= 0.4.5)" "tempoch-cpp (>= 0.5.2)"; do +for required in "qtty-cpp (>= 0.4.5)" "tempoch-cpp (>= 0.5.4)"; do if [[ "${deb_depends}" != *"${required}"* ]]; then fail "DEB metadata is missing required dependency: ${required}" exit 1 @@ -88,7 +88,7 @@ if [[ ${#rpm_packages[@]} -gt 0 ]]; then fi done - for required in "qtty-cpp >= 0.4.5" "tempoch-cpp >= 0.5.2"; do + for required in "qtty-cpp >= 0.4.5" "tempoch-cpp >= 0.5.4"; do if ! grep -Fq "${required}" <<<"${rpm_requires}"; then fail "RPM metadata is missing required dependency: ${required}" exit 1 diff --git a/siderust b/siderust index 0fe54bb..5d0a600 160000 --- a/siderust +++ b/siderust @@ -1 +1 @@ -Subproject commit 0fe54bbaa9233d531ed043f5202b6dfe21ff0ce8 +Subproject commit 5d0a6003b898150b819dea4cdb662e4c18c4ffe4 diff --git a/tempoch-cpp b/tempoch-cpp deleted file mode 160000 index 40a7779..0000000 --- a/tempoch-cpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 40a7779d80a49521191dea202f58c94b17555480 From f9e16a5007302d2b0dff5a7ce13452ed508eeb04 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Sun, 14 Jun 2026 23:25:18 +0200 Subject: [PATCH 3/5] Enhance CI coverage and testing framework - Introduced a CI coverage gate requiring at least 90% line coverage. - Added `scripts/coverage.sh` for local coverage checks. - Updated CMake configuration to disable example and benchmark builds during coverage. - Added new tests for altitude and azimuth calculations across celestial bodies. - Improved linting script to support Docker execution. - Updated changelog for version 0.8.0 release. --- .github/workflows/ci-coverage.yml | 16 ++++- .gitignore | 1 + CHANGELOG.md | 15 ++++- CMakeLists.txt | 2 +- Dockerfile.lint | 34 ++++++++++ scripts/coverage.sh | 93 ++++++++++++++++++++++++++ scripts/lint.sh | 105 +++++++++++++++++++++++++++++- tests/test_altitude.cpp | 61 +++++++++++++++++ tests/test_bodies.cpp | 70 ++++++++++++++++++++ tests/test_context.cpp | 48 ++++++++++++++ tests/test_orbits.cpp | 14 ++++ tests/test_subject.cpp | 58 +++++++++++++++++ 12 files changed, 508 insertions(+), 9 deletions(-) create mode 100644 Dockerfile.lint create mode 100755 scripts/coverage.sh diff --git a/.github/workflows/ci-coverage.yml b/.github/workflows/ci-coverage.yml index 6a9f935..6e7c330 100644 --- a/.github/workflows/ci-coverage.yml +++ b/.github/workflows/ci-coverage.yml @@ -9,6 +9,7 @@ jobs: runs-on: ubuntu-22.04 env: CARGO_TERM_COLOR: always + SIDERUST_CPP_COVERAGE_THRESHOLD: 90 steps: - name: Checkout (with siderust submodule) uses: actions/checkout@v4 @@ -45,6 +46,8 @@ jobs: set -euo pipefail cmake -S . -B build-coverage -G Ninja \ -DSIDERUST_BUILD_DOCS=OFF \ + -DSIDERUST_CPP_BUILD_EXAMPLES=OFF \ + -DSIDERUST_CPP_BUILD_BENCHES=OFF \ -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_CXX_FLAGS="--coverage" \ -DCMAKE_EXE_LINKER_FLAGS="--coverage" @@ -59,28 +62,35 @@ jobs: shell: bash run: | set -euo pipefail + find build-coverage -name '*.gcda' -delete ctest --test-dir build-coverage --output-on-failure -L siderust_cpp + find build-coverage/CMakeFiles -path '*/CompilerId*/*' \( -name '*.gcno' -o -name '*.gcda' \) -delete - name: Coverage (Cobertura XML) shell: bash run: | set -euo pipefail - gcovr \ + gcovr build-coverage \ --root . \ + --gcov-object-directory build-coverage \ + --filter 'include/siderust/.*' \ --exclude 'build-coverage/.*' \ --exclude 'siderust/.*' \ --exclude 'tests/.*' \ --exclude 'examples/.*' \ --xml \ - --output coverage.xml + --output coverage.xml \ + --fail-under-line "$SIDERUST_CPP_COVERAGE_THRESHOLD" - name: Coverage (HTML) shell: bash run: | set -euo pipefail mkdir -p coverage_html - gcovr \ + gcovr build-coverage \ --root . \ + --gcov-object-directory build-coverage \ + --filter 'include/siderust/.*' \ --exclude 'build-coverage/.*' \ --exclude 'siderust/.*' \ --exclude 'tests/.*' \ diff --git a/.gitignore b/.gitignore index f0b796f..06e238d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ cmake-build-*/ out/ build* coverage* +!scripts/coverage.sh # IDE files .vscode/ diff --git a/CHANGELOG.md b/CHANGELOG.md index abb3a64..7f70413 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.8.0-rc] - 2026/06/08 +## [0.8.0] - 2026/06/14 -Release candidate aligned with `siderust v0.10.0` (Option A altitude/event API). +Release aligned with `siderust v0.10.0` (Option A altitude/event API). + +### Added + +- CI coverage gate requiring at least 90% line coverage, plus + `scripts/coverage.sh` for the same local check. +- Targeted tests for FFI error mapping, enum stream formatting, target + forwarding, altitude helpers, and azimuth helpers. ### Changed @@ -18,11 +25,15 @@ Release candidate aligned with `siderust v0.10.0` (Option A altitude/event API). - Removed `CrossingAlgorithm`, `ChebyshevOptions`, scan-step tuning, and all FFI `_v2` dispatch paths. - Benchmarks simplified to the single optimized search engine (no algorithm dimension). - `siderust` submodule updated to the `v0.10.0` / PR #79 release line. +- Production builds now consume official `qtty-cpp` and `tempoch-cpp` CMake + packages via `find_package`; local dependency development uses + `CMAKE_PREFIX_PATH` with a staged package install. ### Removed - Legacy `siderust_altitude_periods` / `siderust_altitude_query_t` FFI usage. - Public algorithm selector and Chebyshev crossing-search controls. +- Vendored `tempoch-cpp` submodule usage from the production build path. ## [0.7.0] - 2026/06/06 diff --git a/CMakeLists.txt b/CMakeLists.txt index 98c3b96..aea69f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.21) project(siderust_cpp VERSION 0.8.0 LANGUAGES CXX) -set(SIDERUST_CPP_PACKAGE_VERSION "0.8.0-rc") +set(SIDERUST_CPP_PACKAGE_VERSION "0.8.0") # --------------------------------------------------------------------------- # CMake 3.21+ provides PROJECT_IS_TOP_LEVEL; emulate for older CMake if needed. diff --git a/Dockerfile.lint b/Dockerfile.lint new file mode 100644 index 0000000..44c74a6 --- /dev/null +++ b/Dockerfile.lint @@ -0,0 +1,34 @@ +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + cargo \ + clang-tidy \ + cmake \ + curl \ + git \ + libssl-dev \ + ninja-build \ + pkg-config \ + rustc \ + wget \ + && rm -rf /var/lib/apt/lists/* + +RUN mkdir -p /etc/apt/keyrings && \ + wget -qO /etc/apt/keyrings/apt.llvm.org.asc https://apt.llvm.org/llvm-snapshot.gpg.key && \ + echo "deb [signed-by=/etc/apt/keyrings/apt.llvm.org.asc] https://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main" \ + > /etc/apt/sources.list.d/llvm-toolchain-jammy-18.list && \ + apt-get update && \ + apt-get install -y --no-install-recommends clang-format-18 && \ + ln -sf /usr/bin/clang-format-18 /usr/local/bin/clang-format && \ + rm -rf /var/lib/apt/lists/* + +COPY scripts/install-official-cpp-deps.sh /tmp/install-official-cpp-deps.sh +RUN /tmp/install-official-cpp-deps.sh && rm -f /tmp/install-official-cpp-deps.sh + +WORKDIR /workspace + +CMD ["/bin/bash"] diff --git a/scripts/coverage.sh b/scripts/coverage.sh new file mode 100755 index 0000000..2ecf56d --- /dev/null +++ b/scripts/coverage.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=scripts/lib.sh +source "${SCRIPT_DIR}/lib.sh" + +BUILD_DIR="build-coverage" +PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL:-2}" +COVERAGE_THRESHOLD="${SIDERUST_CPP_COVERAGE_THRESHOLD:-90}" + +usage() { + cat </dev/null -mapfile -t cpp_files < <(git ls-files '*.cpp') +cpp_output="$(git ls-files '*.cpp')" || { + fail "Unable to list tracked C++ source files" + exit 1 +} +mapfile -t cpp_files <<<"${cpp_output}" +if [[ ${#cpp_files[@]} -eq 1 && -z "${cpp_files[0]}" ]]; then + cpp_files=() +fi + if [[ ${#cpp_files[@]} -eq 0 ]]; then warn "No C++ source files found" exit 0 diff --git a/tests/test_altitude.cpp b/tests/test_altitude.cpp index 72356b6..88d0137 100644 --- a/tests/test_altitude.cpp +++ b/tests/test_altitude.cpp @@ -139,6 +139,18 @@ TEST_F(AltitudeTest, MoonAboveThresholdAcceptsSearchOptions) { } } +TEST_F(AltitudeTest, MoonBelowCrossingsCulminationsAndRanges) { + SearchOptions opts; + opts.with_tolerance(qtty::Day(1e-9)); + + for (const auto &p : moon::below_threshold(obs, window, 0.0_deg, opts)) { + EXPECT_GT(p.duration().value(), 0.0); + } + EXPECT_NO_THROW((void)moon::crossings(obs, window, 0.0_deg, opts)); + EXPECT_NO_THROW((void)moon::culminations(obs, window, opts)); + EXPECT_NO_THROW((void)moon::altitude_ranges(obs, window, -90.0_deg, 90.0_deg, opts)); +} + // ============================================================================ // Star // ============================================================================ @@ -157,6 +169,16 @@ TEST_F(AltitudeTest, StarAboveThreshold) { EXPECT_GT(periods.size(), 0u); } +TEST_F(AltitudeTest, StarBelowCrossingsAndCulminations) { + const auto &vega = VEGA(); + SearchOptions opts; + opts.with_tolerance(qtty::Day(1e-9)); + + EXPECT_GT(star_altitude::below_threshold(vega, obs, window, 30.0_deg, opts).size(), 0u); + EXPECT_GT(star_altitude::crossings(vega, obs, window, 30.0_deg, opts).size(), 0u); + EXPECT_GT(star_altitude::culminations(vega, obs, window, opts).size(), 0u); +} + // ============================================================================ // ICRS direction // ============================================================================ @@ -243,3 +265,42 @@ TEST_F(AltitudeTest, EquatorialMeanJ2000TargetAltitudeAt) { EXPECT_GT(alt.value(), -90.0); EXPECT_LT(alt.value(), 90.0); } + +// ============================================================================ +// Azimuth namespace helpers +// ============================================================================ + +TEST_F(AltitudeTest, SunAzimuthNamespaceHelpers) { + SearchOptions opts; + opts.with_tolerance(qtty::Day(1e-9)); + + EXPECT_TRUE(std::isfinite(sun::azimuth_at(obs, start).value())); + EXPECT_GT(sun::azimuth_crossings(obs, window, 180.0_deg, opts).size(), 0u); + EXPECT_NO_THROW((void)sun::azimuth_extrema(obs, window, opts)); + EXPECT_GT(sun::in_azimuth_range(obs, window, 90.0_deg, 270.0_deg, opts).size(), 0u); + EXPECT_NO_THROW((void)sun::outside_azimuth_range(obs, window, 90.0_deg, 270.0_deg, opts)); +} + +TEST_F(AltitudeTest, MoonAzimuthNamespaceHelpers) { + SearchOptions opts; + opts.with_tolerance(qtty::Day(1e-9)); + + EXPECT_TRUE(std::isfinite(moon::azimuth_at(obs, start).value())); + EXPECT_NO_THROW((void)moon::azimuth_crossings(obs, window, 180.0_deg, opts)); + EXPECT_NO_THROW((void)moon::azimuth_extrema(obs, window, opts)); + EXPECT_NO_THROW((void)moon::in_azimuth_range(obs, window, 90.0_deg, 270.0_deg, opts)); + EXPECT_NO_THROW((void)moon::outside_azimuth_range(obs, window, 90.0_deg, 270.0_deg, opts)); +} + +TEST_F(AltitudeTest, StarAzimuthNamespaceHelpers) { + const auto &vega = VEGA(); + SearchOptions opts; + opts.with_tolerance(qtty::Day(1e-9)); + + EXPECT_TRUE(std::isfinite(star_altitude::azimuth_at(vega, obs, start).value())); + EXPECT_NO_THROW((void)star_altitude::azimuth_crossings(vega, obs, window, 180.0_deg, opts)); + EXPECT_NO_THROW( + (void)star_altitude::in_azimuth_range(vega, obs, window, 90.0_deg, 270.0_deg, opts)); + EXPECT_NO_THROW( + (void)star_altitude::outside_azimuth_range(vega, obs, window, 90.0_deg, 270.0_deg, opts)); +} diff --git a/tests/test_bodies.cpp b/tests/test_bodies.cpp index 3d82478..5c59b84 100644 --- a/tests/test_bodies.cpp +++ b/tests/test_bodies.cpp @@ -118,6 +118,22 @@ TEST(Bodies, BodyTargetAllBodiesAltitude) { } } +TEST(Bodies, BodyTargetNamesCoverAllBodies) { + const auto unknown_body = + static_cast(999); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange) + const std::vector> cases = { + {Body::Sun, "Sun"}, {Body::Moon, "Moon"}, + {Body::Mercury, "Mercury"}, {Body::Venus, "Venus"}, + {Body::Mars, "Mars"}, {Body::Jupiter, "Jupiter"}, + {Body::Saturn, "Saturn"}, {Body::Uranus, "Uranus"}, + {Body::Neptune, "Neptune"}, {unknown_body, "Unknown Body"}, + }; + + for (const auto &[body, expected] : cases) { + EXPECT_EQ(BodyTarget(body).name(), expected); + } +} + TEST(Bodies, BodyTargetAzimuth) { BodyTarget sun(Body::Sun); auto obs = geodetic(2.35, 48.85, 35.0); @@ -146,6 +162,20 @@ TEST(Bodies, BodyTargetAboveThreshold) { EXPECT_GT(periods.size(), 0u); } +TEST(Bodies, BodyTargetForwardsAltitudeAndAzimuthSearches) { + BodyTarget sun(Body::Sun); + auto obs = geodetic(2.35, 48.85, 35.0); + auto window = Period(Time(60000.0), Time(60001.0)); + SearchOptions opts; + opts.with_tolerance(qtty::Day(1e-9)); + + EXPECT_EQ(sun.body(), Body::Sun); + EXPECT_GT(sun.below_threshold(obs, window, qtty::Degree(0.0), opts).size(), 0u); + EXPECT_GT(sun.crossings(obs, window, qtty::Degree(0.0), opts).size(), 0u); + EXPECT_GT(sun.culminations(obs, window, opts).size(), 0u); + EXPECT_GT(sun.azimuth_crossings(obs, window, qtty::Degree(180.0), opts).size(), 0u); +} + TEST(Bodies, BodyTargetPolymorphic) { auto obs = geodetic(2.35, 48.85, 35.0); auto mjd = Time(60000.5); @@ -175,6 +205,28 @@ TEST(Bodies, BodyNamespaceAzimuthAt) { EXPECT_GE(rad.value(), 0.0); } +TEST(Bodies, BodyNamespaceSearchFunctions) { + auto obs = geodetic(2.35, 48.85, 35.0); + auto window = Period(Time(60000.0), Time(60001.0)); + SearchOptions opts; + opts.with_tolerance(qtty::Day(1e-9)); + + EXPECT_GT(body::above_threshold(Body::Sun, obs, window, qtty::Degree(0.0), opts).size(), 0u); + EXPECT_GT(body::below_threshold(Body::Sun, obs, window, qtty::Degree(0.0), opts).size(), 0u); + EXPECT_GT(body::crossings(Body::Sun, obs, window, qtty::Degree(0.0), opts).size(), 0u); + EXPECT_GT(body::culminations(Body::Sun, obs, window, opts).size(), 0u); + EXPECT_GT( + body::altitude_ranges(Body::Sun, obs, window, qtty::Degree(-90.0), qtty::Degree(90.0), opts) + .size(), + 0u); + EXPECT_GT(body::azimuth_crossings(Body::Sun, obs, window, qtty::Degree(180.0), opts).size(), 0u); + EXPECT_NO_THROW((void)body::azimuth_extrema(Body::Sun, obs, window, opts)); + EXPECT_GT( + body::in_azimuth_range(Body::Sun, obs, window, qtty::Degree(90.0), qtty::Degree(270.0), opts) + .size(), + 0u); +} + // ============================================================================ // StarTarget — Target implementation for catalog stars // ============================================================================ @@ -190,6 +242,24 @@ TEST(Bodies, StarTargetAltitude) { EXPECT_LT(alt.value(), 90.0); } +TEST(Bodies, StarTargetForwardsAllTrackableMethods) { + StarTarget target(VEGA()); + auto obs = geodetic(2.35, 48.85, 35.0); + auto mjd = Time(60000.5); + auto window = Period(Time(60000.0), Time(60001.0)); + SearchOptions opts; + opts.with_tolerance(qtty::Day(1e-9)); + + EXPECT_EQ(target.name(), "Vega"); + EXPECT_EQ(target.star().name(), "Vega"); + EXPECT_TRUE(std::isfinite(target.azimuth_at(obs, mjd).value())); + EXPECT_GT(target.above_threshold(obs, window, qtty::Degree(0.0), opts).size(), 0u); + EXPECT_GT(target.below_threshold(obs, window, qtty::Degree(0.0), opts).size(), 0u); + EXPECT_GT(target.crossings(obs, window, qtty::Degree(0.0), opts).size(), 0u); + EXPECT_GT(target.culminations(obs, window, opts).size(), 0u); + EXPECT_GT(target.azimuth_crossings(obs, window, qtty::Degree(180.0), opts).size(), 0u); +} + TEST(Bodies, StarTargetPolymorphicWithBodyTarget) { auto obs = geodetic(2.35, 48.85, 35.0); auto mjd = Time(60000.5); diff --git a/tests/test_context.cpp b/tests/test_context.cpp index c752b66..e2da3c5 100644 --- a/tests/test_context.cpp +++ b/tests/test_context.cpp @@ -1,5 +1,6 @@ #include #include +#include using namespace siderust; @@ -43,3 +44,50 @@ TEST(AstroContext, OwnedFfiContextModelRoundtrip) { detail::OwnedFfiContext fctx(EarthOrientationModel::Iau2000A); EXPECT_EQ(fctx.model(), EarthOrientationModel::Iau2000A); } + +TEST(FfiCore, CheckStatusTranslatesAllKnownErrors) { + EXPECT_NO_THROW(check_status(SIDERUST_STATUS_T_OK, "ok")); + EXPECT_THROW(check_status(SIDERUST_STATUS_T_NULL_POINTER, "op"), NullPointerError); + EXPECT_THROW(check_status(SIDERUST_STATUS_T_INVALID_FRAME, "op"), InvalidFrameError); + EXPECT_THROW(check_status(SIDERUST_STATUS_T_INVALID_CENTER, "op"), InvalidCenterError); + EXPECT_THROW(check_status(SIDERUST_STATUS_T_TRANSFORM_FAILED, "op"), TransformFailedError); + EXPECT_THROW(check_status(SIDERUST_STATUS_T_INVALID_BODY, "op"), InvalidBodyError); + EXPECT_THROW(check_status(SIDERUST_STATUS_T_UNKNOWN_STAR, "op"), UnknownStarError); + EXPECT_THROW(check_status(SIDERUST_STATUS_T_INVALID_PERIOD, "op"), InvalidPeriodError); + EXPECT_THROW(check_status(SIDERUST_STATUS_T_ALLOCATION_FAILED, "op"), AllocationFailedError); + EXPECT_THROW(check_status(SIDERUST_STATUS_T_INVALID_ARGUMENT, "op"), InvalidArgumentError); + EXPECT_THROW(check_status(SIDERUST_STATUS_T_INTERNAL_PANIC, "op"), InternalPanicError); + EXPECT_THROW(check_status(SIDERUST_STATUS_T_DATA_ERROR, "op"), DataLoadError); + EXPECT_THROW(check_status(SIDERUST_STATUS_T_OUT_OF_RANGE, "op"), OutOfRangeError); + EXPECT_THROW(check_status(SIDERUST_STATUS_T_NO_EOP_DATA, "op"), NoEopDataError); + EXPECT_THROW(check_status(SIDERUST_STATUS_T_INVALID_DIMENSION, "op"), InvalidDimensionError); + const auto unknown_status = + static_cast(9999); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange) + EXPECT_THROW(check_status(unknown_status, "op"), SiderustException); +} + +TEST(FfiCore, EnumStreamOperatorsCoverKnownAndUnknownValues) { + const auto unknown_crossing = + static_cast(999); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange) + const auto unknown_culmination = + static_cast(999); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange) + const auto unknown_eop_model = static_cast( + 999); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange) + + std::ostringstream os; + os << CrossingDirection::Rising << ' ' << CrossingDirection::Setting << ' ' << unknown_crossing + << ' ' << CulminationKind::Max << ' ' << CulminationKind::Min << ' ' << unknown_culmination + << ' ' << EarthOrientationModel::Iau2000A << ' ' << EarthOrientationModel::Iau2000B << ' ' + << EarthOrientationModel::Iau2006 << ' ' << EarthOrientationModel::Iau2006A << ' ' + << unknown_eop_model; + + const auto text = os.str(); + EXPECT_NE(text.find("rising"), std::string::npos); + EXPECT_NE(text.find("setting"), std::string::npos); + EXPECT_NE(text.find("max"), std::string::npos); + EXPECT_NE(text.find("min"), std::string::npos); + EXPECT_NE(text.find("Iau2000A"), std::string::npos); + EXPECT_NE(text.find("Iau2000B"), std::string::npos); + EXPECT_NE(text.find("Iau2006"), std::string::npos); + EXPECT_NE(text.find("Unknown"), std::string::npos); +} diff --git a/tests/test_orbits.cpp b/tests/test_orbits.cpp index 5889cb7..7a5f987 100644 --- a/tests/test_orbits.cpp +++ b/tests/test_orbits.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include using namespace siderust; @@ -66,6 +67,19 @@ TEST(Orbits, ConicOrbitClassifiesKinds) { EXPECT_EQ(hyperbolic_conic().kind(), ConicKind::Hyperbolic); } +TEST(Orbits, ConicKindStreamsKnownAndUnknownValues) { + const auto unknown_conic_kind = + static_cast(999); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange) + + std::ostringstream os; + os << ConicKind::Elliptic << ' ' << ConicKind::Hyperbolic << ' ' << unknown_conic_kind; + + const auto text = os.str(); + EXPECT_NE(text.find("Elliptic"), std::string::npos); + EXPECT_NE(text.find("Hyperbolic"), std::string::npos); + EXPECT_NE(text.find("Unknown"), std::string::npos); +} + TEST(Orbits, ConicOrbitHyperbolicPropagationProducesFinitePosition) { auto pos = hyperbolic_conic().position_at(Time(J2000 + 20.0)); diff --git a/tests/test_subject.cpp b/tests/test_subject.cpp index 6394b7a..2abf095 100644 --- a/tests/test_subject.cpp +++ b/tests/test_subject.cpp @@ -239,6 +239,7 @@ TEST(ProperMotionTarget, AutoName) { auto n = t.name(); // Auto-generated name should be non-empty. EXPECT_FALSE(n.empty()); + EXPECT_NE(n.find("ProperMotion"), std::string::npos); } TEST(ProperMotionTarget, EpochAccessor) { @@ -262,6 +263,32 @@ TEST(ProperMotionTarget, AltitudeAtDoesNotCrash) { EXPECT_NO_THROW(barnard.altitude_at(paris(), mid_day())); } +TEST(ProperMotionTarget, ForwardsTrackableSearches) { + ProperMotionTarget target(spherical::direction::ICRS(279.23_deg, 38.78_deg), + Time::J2000(), no_motion()); + SearchOptions opts; + opts.with_tolerance(qtty::Day(1e-9)); + + EXPECT_GT(target.above_threshold(paris(), one_day(), qtty::Degree(0.0), opts).size(), 0u); + EXPECT_GT(target.below_threshold(paris(), one_day(), qtty::Degree(0.0), opts).size(), 0u); + EXPECT_GT(target.crossings(paris(), one_day(), qtty::Degree(0.0), opts).size(), 0u); + EXPECT_GT(target.culminations(paris(), one_day(), opts).size(), 0u); + EXPECT_TRUE(std::isfinite(target.azimuth_at(paris(), mid_day()).value())); + EXPECT_GT(target.azimuth_crossings(paris(), one_day(), qtty::Degree(180.0), opts).size(), 0u); +} + +TEST(ProperMotionTarget, MoveAssignmentTransfersHandle) { + ProperMotionTarget source(spherical::direction::ICRS(279.23_deg, 38.78_deg), + Time::J2000(), no_motion(), "source"); + ProperMotionTarget dest(spherical::direction::ICRS(10.0_deg, 20.0_deg), Time::J2000(), + no_motion(), "dest"); + + dest = std::move(source); + + EXPECT_EQ(dest.name(), "source"); + EXPECT_TRUE(std::isfinite(dest.altitude_at(paris(), mid_day()).value())); +} + TEST(DirectionTarget, EpochAccessor) { auto epoch = Time::J2000(); ICRSTarget t(spherical::direction::ICRS(qtty::Degree(0.0), qtty::Degree(0.0)), epoch); @@ -274,3 +301,34 @@ TEST(DirectionTarget, DataAccessor) { EXPECT_NEAR(d.coord.spherical_dir.azimuth_deg, 45.0, 1.0); EXPECT_NEAR(d.coord.spherical_dir.polar_deg, 30.0, 1.0); } + +TEST(DirectionTarget, DefaultNameAndHandleAccessor) { + ICRSTarget target(spherical::direction::ICRS(qtty::Degree(45.0), qtty::Degree(30.0))); + + EXPECT_NE(target.c_handle(), nullptr); + EXPECT_NE(target.name().find("Direction"), std::string::npos); +} + +TEST(DirectionTarget, ForwardsTrackableSearches) { + ICRSTarget target(spherical::direction::ICRS(qtty::Degree(279.23), qtty::Degree(38.78))); + SearchOptions opts; + opts.with_tolerance(qtty::Day(1e-9)); + + EXPECT_GT(target.below_threshold(paris(), one_day(), qtty::Degree(0.0), opts).size(), 0u); + EXPECT_GT(target.crossings(paris(), one_day(), qtty::Degree(0.0), opts).size(), 0u); + EXPECT_GT(target.culminations(paris(), one_day(), opts).size(), 0u); + EXPECT_TRUE(std::isfinite(target.azimuth_at(paris(), mid_day()).value())); + EXPECT_GT(target.azimuth_crossings(paris(), one_day(), qtty::Degree(180.0), opts).size(), 0u); +} + +TEST(DirectionTarget, MoveAssignmentTransfersHandle) { + ICRSTarget source(spherical::direction::ICRS(qtty::Degree(279.23), qtty::Degree(38.78)), + Time::J2000(), "source"); + ICRSTarget dest(spherical::direction::ICRS(qtty::Degree(10.0), qtty::Degree(20.0)), + Time::J2000(), "dest"); + + dest = std::move(source); + + EXPECT_EQ(dest.name(), "source"); + EXPECT_TRUE(std::isfinite(dest.altitude_at(paris(), mid_day()).value())); +} From 4dd1e59bbd700b82b59c8a0fca2ad5bba2ad182b Mon Sep 17 00:00:00 2001 From: VPRamon Date: Mon, 15 Jun 2026 09:47:37 +0200 Subject: [PATCH 4/5] tests --- .github/workflows/ci-coverage.yml | 8 +++ tests/test_altitude.cpp | 22 +++++++ tests/test_bodies.cpp | 2 +- tests/test_ephemeris.cpp | 95 +++++++++++++++++++++++++++++++ tests/test_subject.cpp | 18 ++++++ 5 files changed, 144 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-coverage.yml b/.github/workflows/ci-coverage.yml index 6e7c330..083bd28 100644 --- a/.github/workflows/ci-coverage.yml +++ b/.github/workflows/ci-coverage.yml @@ -58,6 +58,14 @@ jobs: set -euo pipefail cmake --build build-coverage --target test_siderust + - name: Fetch DE440s kernel for runtime ephemeris coverage + shell: bash + run: | + set -euo pipefail + curl -fsSL -o /tmp/de440s.bsp \ + https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/de440s.bsp + echo "SIDERUST_BSP_PATH=/tmp/de440s.bsp" >> "$GITHUB_ENV" + - name: Run tests shell: bash run: | diff --git a/tests/test_altitude.cpp b/tests/test_altitude.cpp index 88d0137..4421d0b 100644 --- a/tests/test_altitude.cpp +++ b/tests/test_altitude.cpp @@ -304,3 +304,25 @@ TEST_F(AltitudeTest, StarAzimuthNamespaceHelpers) { EXPECT_NO_THROW( (void)star_altitude::outside_azimuth_range(vega, obs, window, 90.0_deg, 270.0_deg, opts)); } + +TEST_F(AltitudeTest, StarAzimuthExtremaReturnsEvents) { + const auto &vega = VEGA(); + SearchOptions opts; + opts.with_tolerance(qtty::Day(1e-9)); + const auto subj = Subject::star(vega); + + const auto evts = azimuth_extrema(subj, obs, window, opts); + EXPECT_GE(evts.size(), 2u); +} + +TEST_F(AltitudeTest, SunOutsideAzimuthRangeReturnsPeriods) { + SearchOptions opts; + opts.with_tolerance(qtty::Day(1e-9)); + EXPECT_GT(sun::outside_azimuth_range(obs, window, 90.0_deg, 270.0_deg, opts).size(), 0u); +} + +TEST_F(AltitudeTest, MoonBelowThresholdReturnsPeriods) { + SearchOptions opts; + opts.with_tolerance(qtty::Day(1e-9)); + EXPECT_GT(moon::below_threshold(obs, window, 0.0_deg, opts).size(), 0u); +} diff --git a/tests/test_bodies.cpp b/tests/test_bodies.cpp index 5c59b84..0011a33 100644 --- a/tests/test_bodies.cpp +++ b/tests/test_bodies.cpp @@ -220,7 +220,7 @@ TEST(Bodies, BodyNamespaceSearchFunctions) { .size(), 0u); EXPECT_GT(body::azimuth_crossings(Body::Sun, obs, window, qtty::Degree(180.0), opts).size(), 0u); - EXPECT_NO_THROW((void)body::azimuth_extrema(Body::Sun, obs, window, opts)); + EXPECT_GE(body::azimuth_extrema(Body::Sun, obs, window, opts).size(), 0u); EXPECT_GT( body::in_azimuth_range(Body::Sun, obs, window, qtty::Degree(90.0), qtty::Degree(270.0), opts) .size(), diff --git a/tests/test_ephemeris.cpp b/tests/test_ephemeris.cpp index d728f55..4af1d87 100644 --- a/tests/test_ephemeris.cpp +++ b/tests/test_ephemeris.cpp @@ -1,9 +1,35 @@ #include +#include +#include #include #include +#include using namespace siderust; +namespace { + +std::string runtime_bsp_fixture() { + if (const char *env = std::getenv("SIDERUST_BSP_PATH"); env != nullptr && env[0] != '\0') { + return env; + } + return {}; +} + +std::vector read_binary_file(const std::string &path) { + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file) { + return {}; + } + const auto size = static_cast(file.tellg()); + file.seekg(0); + std::vector buf(size); + file.read(reinterpret_cast(buf.data()), static_cast(size)); + return buf; +} + +} // namespace + // ============================================================================ // Typed API // ============================================================================ @@ -66,3 +92,72 @@ TEST(RuntimeEphemeris, CartesianVelocityFieldsExist) { EXPECT_DOUBLE_EQ(v.vy, 2.0); EXPECT_DOUBLE_EQ(v.vz, 3.0); } + +TEST(RuntimeEphemeris, LoadedFromBspQueriesFiniteStates) { + const std::string path = runtime_bsp_fixture(); + if (path.empty()) { + GTEST_SKIP() << "Set SIDERUST_BSP_PATH to a JPL DE BSP file."; + } + + RuntimeEphemeris eph(path); + ASSERT_TRUE(static_cast(eph)); + + const auto jd = Time::J2000(); + const auto sun = eph.sun_barycentric(jd); + const auto earth_bary = eph.earth_barycentric(jd); + const auto earth_helio = eph.earth_heliocentric(jd); + const auto moon = eph.moon_geocentric(jd); + const auto vel = eph.earth_barycentric_velocity(jd); + + EXPECT_TRUE(std::isfinite(sun.x().value())); + EXPECT_TRUE(std::isfinite(earth_bary.x().value())); + EXPECT_NEAR(earth_helio.distance().value(), 1.0, 0.05); + EXPECT_NEAR(moon.distance().value(), 384400.0, 50000.0); + EXPECT_TRUE(std::isfinite(vel.vx)); + EXPECT_EQ(vel.frame, SIDERUST_FRAME_T_ECLIPTIC_MEAN_J2000); +} + +TEST(RuntimeEphemeris, LoadFromMemoryBuffer) { + const std::string path = runtime_bsp_fixture(); + if (path.empty()) { + GTEST_SKIP() << "Set SIDERUST_BSP_PATH to a JPL DE BSP file."; + } + + const auto bytes = read_binary_file(path); + ASSERT_FALSE(bytes.empty()); + + RuntimeEphemeris eph(bytes.data(), bytes.size()); + ASSERT_TRUE(static_cast(eph)); + + const auto jd = Time::J2000(); + EXPECT_NEAR(eph.earth_heliocentric(jd).distance().value(), 1.0, 0.05); +} + +TEST(RuntimeEphemeris, MoveSemanticsTransferHandle) { + const std::string path = runtime_bsp_fixture(); + if (path.empty()) { + GTEST_SKIP() << "Set SIDERUST_BSP_PATH to a JPL DE BSP file."; + } + + RuntimeEphemeris eph1(path); + RuntimeEphemeris eph2(std::move(eph1)); + EXPECT_FALSE(static_cast(eph1)); + EXPECT_TRUE(static_cast(eph2)); + + const auto jd = Time::J2000(); + EXPECT_NO_THROW(eph2.sun_barycentric(jd)); +} + +TEST(RuntimeEphemeris, MoveAssignmentReleasesOldHandle) { + const std::string path = runtime_bsp_fixture(); + if (path.empty()) { + GTEST_SKIP() << "Set SIDERUST_BSP_PATH to a JPL DE BSP file."; + } + + RuntimeEphemeris eph1(path); + RuntimeEphemeris eph2(path); + eph2 = std::move(eph1); + EXPECT_FALSE(static_cast(eph1)); + EXPECT_TRUE(static_cast(eph2)); + EXPECT_NO_THROW(eph2.moon_geocentric(Time::J2000())); +} diff --git a/tests/test_subject.cpp b/tests/test_subject.cpp index 2abf095..bf10c32 100644 --- a/tests/test_subject.cpp +++ b/tests/test_subject.cpp @@ -190,6 +190,23 @@ TEST(SubjectTest, AzimuthExtremaBody) { EXPECT_TRUE(true); } +TEST(SubjectTest, AzimuthExtremaStarReturnsEvents) { + Star vega = Star::catalog("VEGA"); + auto subj = Subject::star(vega); + SearchOptions opts; + opts.with_tolerance(qtty::Day(1e-9)); + const auto window = Period(Time::from_utc({2026, 7, 15, 18, 0, 0}), + Time::from_utc({2026, 7, 16, 18, 0, 0})); + + const auto evts = azimuth_extrema(subj, ROQUE_DE_LOS_MUCHACHOS(), window, opts); + EXPECT_GE(evts.size(), 2u); + for (const auto &e : evts) { + EXPECT_GE(e.azimuth.value(), 0.0); + EXPECT_LT(e.azimuth.value(), 360.0); + EXPECT_TRUE(e.kind == AzimuthExtremumKind::Max || e.kind == AzimuthExtremumKind::Min); + } +} + // ── in_azimuth_range ───────────────────────────────────────────────────────── TEST(SubjectTest, InAzimuthRangeBody) { @@ -314,6 +331,7 @@ TEST(DirectionTarget, ForwardsTrackableSearches) { SearchOptions opts; opts.with_tolerance(qtty::Day(1e-9)); + EXPECT_GT(target.above_threshold(paris(), one_day(), qtty::Degree(0.0), opts).size(), 0u); EXPECT_GT(target.below_threshold(paris(), one_day(), qtty::Degree(0.0), opts).size(), 0u); EXPECT_GT(target.crossings(paris(), one_day(), qtty::Degree(0.0), opts).size(), 0u); EXPECT_GT(target.culminations(paris(), one_day(), opts).size(), 0u); From eba5ca271a04cc63238ca4e4534be71944cd3dfc Mon Sep 17 00:00:00 2001 From: VPRamon Date: Mon, 15 Jun 2026 10:06:33 +0200 Subject: [PATCH 5/5] fix workflow! --- .github/workflows/ci-coverage.yml | 4 ++-- scripts/coverage.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-coverage.yml b/.github/workflows/ci-coverage.yml index 083bd28..2bc0b3b 100644 --- a/.github/workflows/ci-coverage.yml +++ b/.github/workflows/ci-coverage.yml @@ -80,7 +80,7 @@ jobs: set -euo pipefail gcovr build-coverage \ --root . \ - --gcov-object-directory build-coverage \ + --object-directory build-coverage \ --filter 'include/siderust/.*' \ --exclude 'build-coverage/.*' \ --exclude 'siderust/.*' \ @@ -97,7 +97,7 @@ jobs: mkdir -p coverage_html gcovr build-coverage \ --root . \ - --gcov-object-directory build-coverage \ + --object-directory build-coverage \ --filter 'include/siderust/.*' \ --exclude 'build-coverage/.*' \ --exclude 'siderust/.*' \ diff --git a/scripts/coverage.sh b/scripts/coverage.sh index 2ecf56d..2ca2b9a 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -69,7 +69,7 @@ header "Coverage: gcovr" mkdir -p coverage_html gcovr "${BUILD_DIR}" \ --root . \ - --gcov-object-directory "${BUILD_DIR}" \ + --object-directory "${BUILD_DIR}" \ --filter 'include/siderust/.*' \ --exclude "${BUILD_DIR}/.*" \ --exclude 'siderust/.*' \ @@ -81,7 +81,7 @@ gcovr "${BUILD_DIR}" \ gcovr "${BUILD_DIR}" \ --root . \ - --gcov-object-directory "${BUILD_DIR}" \ + --object-directory "${BUILD_DIR}" \ --filter 'include/siderust/.*' \ --exclude "${BUILD_DIR}/.*" \ --exclude 'siderust/.*' \