From 629457abe0ec02a3a273966a859da894431abaf1 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Wed, 2 Sep 2026 15:51:56 +0100 Subject: [PATCH 01/24] Integrate Cargo into the CMake build Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- .github/workflows/ci.yml | 22 +- .github/workflows/rust-instrumented.yml | 2 - docs/getting-started/cpp.rst | 68 ++- docs/project/bindings.md | 3 +- lang/cpp/CMakeLists.txt | 202 ++++----- lang/cpp/README.md | 146 ++++++- lang/cpp/cmake/BuildVortexCargo.cmake | 157 +++++++ lang/cpp/cmake/VortexCargo.cmake | 511 ++++++++++++++++++++++ lang/cpp/cmake/VortexHelpers.cmake | 100 +++++ lang/cpp/cmake/VortexOptions.cmake | 38 ++ lang/cpp/cmake/VortexRustToolchain.cmake | 531 +++++++++++++++++++++++ lang/cpp/cmake/VortexStaticLink.cmake | 68 +++ lang/cpp/examples/CMakeLists.txt | 4 +- lang/cpp/gcov-report.sh | 5 +- lang/cpp/include/vortex/common.hpp | 1 - lang/cpp/tests/CMakeLists.txt | 2 +- 16 files changed, 1669 insertions(+), 191 deletions(-) create mode 100644 lang/cpp/cmake/BuildVortexCargo.cmake create mode 100644 lang/cpp/cmake/VortexCargo.cmake create mode 100644 lang/cpp/cmake/VortexHelpers.cmake create mode 100644 lang/cpp/cmake/VortexOptions.cmake create mode 100644 lang/cpp/cmake/VortexRustToolchain.cmake create mode 100644 lang/cpp/cmake/VortexStaticLink.cmake diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c647a95d42e..2e26f90007e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -585,23 +585,21 @@ jobs: - uses: ./.github/actions/setup-prebuild with: enable-sccache: "true" + - name: Install Rust nightly toolchain run: | rustup toolchain install $NIGHTLY_TOOLCHAIN rustup component add --toolchain $NIGHTLY_TOOLCHAIN rust-src rustfmt clippy llvm-tools-preview - - name: Build FFI library (asan) - run: | - RUSTFLAGS="-A warnings -Cunsafe-allow-abi-mismatch=sanitizer \ - -C debuginfo=2 -C opt-level=0 -C strip=none -Zexternal-clangrt \ - -Zsanitizer=address,leak" \ - cargo +$NIGHTLY_TOOLCHAIN build --locked --no-default-features \ - --target x86_64-unknown-linux-gnu -Zbuild-std \ - -p vortex-ffi - name: Build C++ tests and examples (asan) - run: | - mkdir -p build - cmake -S lang/cpp -Bbuild -DSANITIZER=asan -DBUILD_TESTS=1 -DBUILD_EXAMPLES=1 -DTARGET_TRIPLE="x86_64-unknown-linux-gnu" - cmake --build build --parallel $(nproc) + env: + RUSTUP_TOOLCHAIN: ${{ env.NIGHTLY_TOOLCHAIN }} + run: | + cmake -S lang/cpp -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DVORTEX_SANITIZER=asan \ + -DVORTEX_BUILD_TESTING=ON \ + -DVORTEX_BUILD_EXAMPLES=ON + cmake --build build --parallel - name: Run C++ tests run: | build/tests/vortex_cxx_test diff --git a/.github/workflows/rust-instrumented.yml b/.github/workflows/rust-instrumented.yml index d186ce8dbac..77607c0ed3d 100644 --- a/.github/workflows/rust-instrumented.yml +++ b/.github/workflows/rust-instrumented.yml @@ -140,8 +140,6 @@ jobs: sudo apt-get update sudo apt-get install -y lcov libjson-xs-perl fi - - name: Build FFI library - run: cargo build -p vortex-ffi - name: Build and test C++ API with coverage working-directory: lang/cpp run: ./gcov-report.sh diff --git a/docs/getting-started/cpp.rst b/docs/getting-started/cpp.rst index fa56a41ffa6..b77eef75b11 100644 --- a/docs/getting-started/cpp.rst +++ b/docs/getting-started/cpp.rst @@ -10,39 +10,59 @@ The only dependency apart from Vortex is ``nanoarrow``. in using Vortex from C++ or you want a feature not covered yet e.g. extension support. -Installation ------------- +Building from source +-------------------- -We don't provide prebuilt library files (yet) so you will need to build Vortex -from source, and for that you will need: - -- C++20, -- Rust toolchain, -- and CMake 3.10. +Vortex does not provide prebuilt C++ libraries yet. Building from source requires +C++20, CMake 3.28 or newer with a single-config generator, and native Cargo and +rustc 1.95 or newer with the target standard library installed. The initial CMake +integration builds static position-independent-code libraries only. Use a complete +workspace checkout; the C++ directory cannot be built from an isolated source copy. .. code-block:: bash git clone --depth 1 https://github.com/vortex-data/vortex cd vortex - cargo build --release -p vortex-ffi - cmake -S lang/cpp -Bbuild -DCMAKE_BUILD_TYPE=Release - # To build the examples, pass -DBUILD_EXAMPLES=1 - # cmake -S lang/cpp -Bbuild -DBUILD_EXAMPLES=1 + cmake -S lang/cpp -B build/cpp -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DVORTEX_BUILD_EXAMPLES=ON + cmake --build build/cpp --parallel + +Configuration queries Cargo and rustc and validates the native target and Rust +standard library. The locked ``vortex-ffi`` compilation occurs only when the CMake +build runs, so a separate ``cargo build`` step is neither required nor recommended. +Building examples can download nanoarrow during configure; Cargo can download +locked Rust dependencies during the build. - cmake --build build -j +Tests and examples default to ``OFF``. ``VORTEX_WARNINGS_AS_ERRORS`` defaults to +``ON`` for a standalone build and ``OFF`` when a parent adds ``lang/cpp``. -This produces a shared and a static library which you can use directly or via +Source-tree consumers add ``lang/cpp`` and link the canonical target: .. code-block:: cmake - # static library - target_link_libraries(target PRIVATE vortex_cxx) - # shared library - target_link_libraries(target PRIVATE vortex_cxx_shared) + add_subdirectory(path/to/vortex/lang/cpp vortex-cpp) + target_link_libraries(target PRIVATE Vortex::cpp_static) + +The Vortex archives are PIC and can be embedded into a shared parent. Keep calls +behind a private C++ translation unit and use the parent's normal version script or +exported-symbol allowlist: the shared parent owns its public ABI, and the Vortex +interface target does not apply parent-wide symbol-export policy. + +The CMake integration is source-only: it does not provide installation rules or a +``find_package(Vortex)`` package. Downstream projects should vendor or fetch a pinned +Vortex checkout and add ``lang/cpp`` directly. + +macOS arm64 and x86_64 are modeled, but only Apple Silicon is currently validated; +macOS is not a cuDF integration target. GNU/Linux x86_64 and aarch64 are modeled, +while full GCC 14, Conda compiler-wrapper, glibc 2.28, and cuDF validation remains +deferred. -Have a look at the `examples -`_ +See the `C++ bindings README +`_ for all CMake +options, cache behavior, and source-integration rules. Have a look at the +`examples `_ directory as well. Reading files @@ -205,7 +225,7 @@ Now you can build the example and read back the generated files: .. code-block:: - ./build/examples/writer people0.vortex - ./build/examples/writer people1.vortex - ./build/examples/writer me.vortex - ./build/examples/reader + ./build/cpp/examples/writer people0.vortex + ./build/cpp/examples/writer people1.vortex + ./build/cpp/examples/writer me.vortex + ./build/cpp/examples/reader diff --git a/docs/project/bindings.md b/docs/project/bindings.md index 3311a7a3663..d2d7dc7f922 100644 --- a/docs/project/bindings.md +++ b/docs/project/bindings.md @@ -91,7 +91,8 @@ Migrate from cxx to wrapping the C API: - **RAII wrappers:** Provide C++ classes that manage lifetime of C API objects (files, scanners, arrays, streams). -- **CMake integration:** Ship a CMake config so downstream projects can `find_package(Vortex)`. +- **CMake integration:** Provide source-tree CMake targets so vendored downstream projects can + build Vortex with `add_subdirectory`. - **Header generation:** Auto-generate C++ headers from the C API headers, adding type safety and namespace scoping. - **Target:** Tier 2 (native array access through the C API). diff --git a/lang/cpp/CMakeLists.txt b/lang/cpp/CMakeLists.txt index 59beccb5cac..002f380e261 100644 --- a/lang/cpp/CMakeLists.txt +++ b/lang/cpp/CMakeLists.txt @@ -1,159 +1,117 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -cmake_minimum_required(VERSION 3.10) -project(VortexCXX - VERSION 0.0.1 - LANGUAGES CXX) +# Entry point for the static Vortex C++ and Rust FFI libraries. It supports both +# standalone configuration and inclusion from a parent via add_subdirectory(). -include(FetchContent) +cmake_minimum_required(VERSION 3.28) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(_vortex_workspace_root "${CMAKE_CURRENT_SOURCE_DIR}/../..") +set(_vortex_cmake_dir "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -find_program(SCCACHE_PROGRAM sccache) -if (SCCACHE_PROGRAM) - set(CMAKE_C_COMPILER_LAUNCHER "${SCCACHE_PROGRAM}") - set(CMAKE_CXX_COMPILER_LAUNCHER "${SCCACHE_PROGRAM}") - message(STATUS "Sccache found: ${SCCACHE_PROGRAM}") -else () - message(STATUS "Sccache not found") -endif () - -option(BUILD_TESTS "Build tests" OFF) - -set(SANITIZER "" CACHE STRING "Build with sanitizers") -set(TARGET_TRIPLE "" CACHE STRING "Rust target triple for FFI library") -set(RUST_BUILD_PROFILE "" CACHE STRING "Cargo profile name for Rust FFI library") - -if (NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug) -endif() - -if (NOT SANITIZER STREQUAL "") - message(NOTICE "Sanitizer: ${SANITIZER}") - if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") - message(FATAL_ERROR "Only debug build is supported for sanitizer builds") +# A standalone configure owns project() and its language set. When embedded, +# preserve the parent's project metadata and enable only languages it has not +# already initialized. +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + project(VortexCXX DESCRIPTION "Vortex C++ wrapper" LANGUAGES C CXX) + set(_vortex_cpp_standalone ON) +else() + set(_vortex_cpp_standalone OFF) + get_property(_vortex_enabled_languages GLOBAL PROPERTY ENABLED_LANGUAGES) + if(NOT "C" IN_LIST _vortex_enabled_languages) + enable_language(C) endif() - - if (SANITIZER STREQUAL "asan") - set(SANITIZER_FLAGS "-fsanitize=address,undefined,leak") - elseif (SANITIZER STREQUAL "tsan") - set(SANITIZER_FLAGS "-fsanitize=thread") - else() - message(FATAL_ERROR "Unknown sanitizer ${SANITIZER}") + if(NOT "CXX" IN_LIST _vortex_enabled_languages) + enable_language(CXX) endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SANITIZER_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SANITIZER_FLAGS}") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${SANITIZER_FLAGS}") endif() -if (RUST_BUILD_PROFILE STREQUAL "") - if (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") - set(RUST_BUILD_PROFILE "release_debug") - else() - string(TOLOWER "${CMAKE_BUILD_TYPE}" RUST_BUILD_PROFILE) - endif() -endif() +# A toolchain-file change must re-run configure so Cargo and rustc are resolved +# again. Cargo itself tracks manifests, the lockfile, and Rust sources at build +# time because its phony CMake target runs on every build. +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + "${_vortex_workspace_root}/rust-toolchain.toml") -set(VORTEX_FFI_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../vortex-ffi") -set(VORTEX_FFI_LIB_DIR "${VORTEX_FFI_DIR}/../target/${TARGET_TRIPLE}/${RUST_BUILD_PROFILE}") -set(VORTEX_FFI_HEADERS "${VORTEX_FFI_DIR}/cinclude") +include("${_vortex_cmake_dir}/VortexOptions.cmake") +include("${_vortex_cmake_dir}/VortexCargo.cmake") -if(APPLE) - set(VORTEX_FFI_STATIC "${VORTEX_FFI_LIB_DIR}/libvortex_ffi.a") - set(VORTEX_FFI_SHARED "${VORTEX_FFI_LIB_DIR}/libvortex_ffi.dylib") -elseif(WIN32) - set(VORTEX_FFI_STATIC "${VORTEX_FFI_LIB_DIR}/libvortex_ffi.lib") - set(VORTEX_FFI_SHARED "${VORTEX_FFI_LIB_DIR}/libvortex_ffi.dll") +# Standalone builds treat warnings as errors; embedded builds do not impose that +# policy on a parent. +if(_vortex_cpp_standalone) + set(_vortex_warnings_as_errors_default ON) else() - set(VORTEX_FFI_STATIC "${VORTEX_FFI_LIB_DIR}/libvortex_ffi.a") - set(VORTEX_FFI_SHARED "${VORTEX_FFI_LIB_DIR}/libvortex_ffi.so") -endif() - -if(NOT EXISTS "${VORTEX_FFI_STATIC}") - message(FATAL_ERROR - "vortex-ffi static library not found at ${VORTEX_FFI_STATIC}. " - "Run: cargo build --profile -p vortex-ffi") -endif() - -add_library(vortex_ffi STATIC IMPORTED) -set_target_properties(vortex_ffi PROPERTIES - IMPORTED_LOCATION "${VORTEX_FFI_STATIC}" - INTERFACE_INCLUDE_DIRECTORIES "${VORTEX_FFI_HEADERS}") - -if(EXISTS "${VORTEX_FFI_SHARED}") - add_library(vortex_ffi_shared SHARED IMPORTED) - set_target_properties(vortex_ffi_shared PROPERTIES - IMPORTED_LOCATION "${VORTEX_FFI_SHARED}" - INTERFACE_INCLUDE_DIRECTORIES "${VORTEX_FFI_HEADERS}" - INTERFACE_LINK_OPTIONS "LINKER:-rpath,${VORTEX_FFI_LIB_DIR}") + set(_vortex_warnings_as_errors_default OFF) endif() - -file(GLOB VORTEX_CXX_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") -add_library(vortex_cxx STATIC ${VORTEX_CXX_SOURCES}) -target_include_directories(vortex_cxx PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${CMAKE_CURRENT_SOURCE_DIR}/../vortex-ffi/cinclude -) -target_link_libraries(vortex_cxx PUBLIC vortex_ffi) -add_library(vortex::cxx ALIAS vortex_cxx) +option(VORTEX_WARNINGS_AS_ERRORS + "Treat warnings in Vortex-owned C++ sources as errors" + "${_vortex_warnings_as_errors_default}") + +# Build the private Rust FFI dependency before defining the public C++ target. +_vortex_add_ffi_static() + +set(_vortex_cpp_sources + src/array.cpp + src/data_source.cpp + src/dtype.cpp + src/expression.cpp + src/scalar.cpp + src/scan.cpp + src/session.cpp + src/writer.cpp) +add_library(vortex_cxx STATIC ${_vortex_cpp_sources}) +set_target_properties(vortex_cxx PROPERTIES + POSITION_INDEPENDENT_CODE ON + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON) target_compile_features(vortex_cxx PUBLIC cxx_std_20) - -set(APPLE_LINK_FLAGS "-framework CoreFoundation -framework Security") - -if(APPLE) - target_link_libraries(vortex_cxx PRIVATE ${APPLE_LINK_FLAGS}) -endif() - -if(TARGET vortex_ffi_shared) - add_library(vortex_cxx_shared SHARED ${VORTEX_CXX_SOURCES}) - set_target_properties(vortex_cxx_shared PROPERTIES POSITION_INDEPENDENT_CODE ON) - target_include_directories(vortex_cxx_shared PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${CMAKE_CURRENT_SOURCE_DIR}/../vortex-ffi/cinclude - ) - target_link_libraries(vortex_cxx_shared PUBLIC vortex_ffi_shared) - add_library(vortex::cxx_shared ALIAS vortex_cxx_shared) - target_compile_features(vortex_cxx_shared PUBLIC cxx_std_20) - - if(APPLE) - target_link_libraries(vortex_cxx_shared PRIVATE ${APPLE_LINK_FLAGS}) +# Warning policy is private to Vortex-owned sources so embedding this target +# never injects warning flags into consumers. +if(MSVC) + target_compile_options(vortex_cxx PRIVATE /W4) + if(VORTEX_WARNINGS_AS_ERRORS) + target_compile_options(vortex_cxx PRIVATE /WX) + endif() +else() + target_compile_options(vortex_cxx PRIVATE -Wall -Wextra -Wpedantic) + if(VORTEX_WARNINGS_AS_ERRORS) + target_compile_options(vortex_cxx PRIVATE -Werror) endif() endif() +target_include_directories(vortex_cxx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(vortex_cxx PUBLIC vortex_ffi_static) + +add_library(Vortex::cpp_static ALIAS vortex_cxx) -if (BUILD_TESTS OR BUILD_EXAMPLES) +if(VORTEX_BUILD_TESTING OR VORTEX_BUILD_EXAMPLES) + include(FetchContent) FetchContent_Declare( - Nanoarrow - GIT_REPOSITORY https://github.com/apache/arrow-nanoarrow - GIT_TAG apache-arrow-nanoarrow-0.8.0 - ) + Nanoarrow + GIT_REPOSITORY https://github.com/apache/arrow-nanoarrow + GIT_TAG apache-arrow-nanoarrow-0.8.0) FetchContent_MakeAvailable(Nanoarrow) - # arrow-nanoarrow creates a self-referential symlink - # python/subprojects/arrow-nanoarrow -> ../.. that makes any recursive - if (IS_SYMLINK "${nanoarrow_SOURCE_DIR}/python/subprojects/arrow-nanoarrow") + # arrow-nanoarrow creates a self-referential symlink: + # python/subprojects/arrow-nanoarrow -> ../.. + if(IS_SYMLINK "${nanoarrow_SOURCE_DIR}/python/subprojects/arrow-nanoarrow") file(REMOVE "${nanoarrow_SOURCE_DIR}/python/subprojects/arrow-nanoarrow") endif() endif() -if (BUILD_TESTS) +if(VORTEX_BUILD_TESTING) FetchContent_Declare( Catch GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.8.1 - ) + GIT_TAG v3.8.1) FetchContent_MakeAvailable(Catch) include(Catch) target_compile_definitions(Catch2 PRIVATE CATCH_CONFIG_NO_POSIX_SIGNALS) - include(CTest) + if(_vortex_cpp_standalone) + enable_testing() + endif() add_subdirectory(tests) endif() -if (BUILD_EXAMPLES) +if(VORTEX_BUILD_EXAMPLES) add_subdirectory(examples) endif() diff --git a/lang/cpp/README.md b/lang/cpp/README.md index fe9d2527e63..70012926060 100644 --- a/lang/cpp/README.md +++ b/lang/cpp/README.md @@ -1,45 +1,143 @@ # Vortex C++ bindings -For a usage guide, see docs/api/cpp/index.rst. +Vortex provides a C++20 API for reading and writing Vortex files. See the +[C++ quickstart](../../docs/getting-started/cpp.rst) for API examples. -## Requirements +## Quick start -- CMake 3.10+ -- C++20 compiler (C++23 compiler for tests). -- Rust toolchain for building `vortex-ffi`. - -## Build +Build from the repository root with CMake 3.28 or newer, Ninja, native C/C++ compilers, and the +workspace Rust toolchain: ```sh -cargo build --release -p vortex-ffi -cmake -Bbuild -DCMAKE_BUILD_TYPE=Release -cmake --build build -j +cmake -S lang/cpp -B build/cpp -G Ninja -DCMAKE_BUILD_TYPE=Release +cmake --build build/cpp --parallel +``` + +CMake builds the Rust FFI through Cargo. A separate `cargo build` is not required. + +## Embed in a CMake project + +Vendor or fetch a pinned, complete Vortex checkout, then add `lang/cpp` once: + +```cmake +add_subdirectory(path/to/vortex/lang/cpp vortex-cpp) +target_link_libraries(my_target PRIVATE Vortex::cpp_static) ``` -This will generate `libvortex_cxx` shared and static libraries. -You can use `vortex_cxx` and `vortex_cxx_shared` CMake targets. +`lang/cpp/CMakeLists.txt` is the only supported entry point. It provides one public target: +`Vortex::cpp_static`, a static PIC C++20 library with all required transitive dependencies. + +The integration is source-only: it does not install Vortex or provide `find_package(Vortex)`. It +enables C and C++ when needed, but keeps compile and link policy target-scoped and does not replace +the parent's build type, language standards, compiler flags, linker flags, or `BUILD_SHARED_LIBS`. + +### Shared-library boundary + +Vortex can be linked privately into a shared library such as `libcudf`. Keep Vortex calls in private +C++ implementation files and preserve the parent's symbol-export policy. The parent must prevent +`vx_*` and other implementation symbols from becoming part of its public ABI, for example with an +ELF version script and `--exclude-libs,ALL` or a macOS exported-symbol allowlist. + +## Supported configurations + +The build supports: + +- native GNU/Linux on x86_64 and aarch64; +- native macOS on x86_64 and arm64 for standalone development; +- an empty `CMAKE_BUILD_TYPE` (Rust development profile), `Debug`, `Release`, and `RelWithDebInfo`; and +- Cargo and rustc 1.95.0 or newer, with the selected target's standard library installed. + +Cargo, rustc, and the CMake-selected C/C++ compilers must target the same native platform and +architecture. The workspace toolchain is used by default. + +Cross-compilation, Apple universal binaries, Windows, musl, multi-config generators, and shared +Vortex targets are not supported. macOS is not a supported cuDF integration target. + +## Configuration + +Set options before `add_subdirectory`, or pass them with `-D` for a standalone build. -## Test +### Build options + +- `VORTEX_BUILD_TESTING=ON` builds the C++23 tests. Embedded builds require the parent to enable + CTest. Default: `OFF`. +- `VORTEX_BUILD_EXAMPLES=ON` builds the examples. Default: `OFF`. +- `VORTEX_WARNINGS_AS_ERRORS` applies only to Vortex-owned C++ sources. Default: `ON` standalone and + `OFF` when embedded. +- `VORTEX_SANITIZER=asan|tsan` enables sanitizer instrumentation. Use an explicit Debug build and a + nightly Rust toolchain with `rust-src`. Flags propagate to targets that link Vortex, and `mimalloc` + cannot be used with sanitizers. + +### Cargo options + +- `VORTEX_CARGO_EXECUTABLE` and `VORTEX_RUSTC_EXECUTABLE` override tool discovery. +- `VORTEX_CARGO_TARGET_DIR` selects the Cargo cache root. +- `VORTEX_CARGO_JOBS` sets Cargo's job limit. +- `VORTEX_CARGO_OFFLINE=ON` passes `--offline` to Cargo; the Cargo cache must already be complete. +- `VORTEX_CARGO_FEATURES=mimalloc` enables the only supported optional FFI feature. +- `VORTEX_RUSTFLAGS` adds rustc arguments. `target-cpu=native` is rejected because parent artifacts + may run on a different CPU. + +CMake resolves and validates the toolchain during configure. The selected Cargo and rustc binaries +remain fixed until the project is reconfigured, and CMake forwards its +native toolchain settings to +Cargo build scripts. + +Cargo artifacts are isolated by a fingerprint of the toolchain and build settings. CMake clean +removes the staged Vortex archive but preserves the Cargo cache. Ambient Rust flag variables are +ignored; use `VORTEX_RUSTFLAGS` for intentional additions. + +Stable builds use the checked-in `vortex-ffi/cinclude/vortex.h`. A non-sanitizer nightly build may +regenerate that header in the source checkout and run `clang-format`; sanitizer builds skip header +generation. + +Cargo may download locked Rust dependencies during the build. Tests and examples may download their +CMake `FetchContent` dependencies during configure; `VORTEX_CARGO_OFFLINE` does not affect those +downloads. + +## Development + +### Tests ```sh -cargo build --release -p vortex-ffi -cmake -Bbuild -DBUILD_TESTS=ON -cmake --build build -j -ctest --test-dir build -j "$(nproc)" +cmake -S lang/cpp -B build/cpp-tests -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DVORTEX_BUILD_TESTING=ON +cmake --build build/cpp-tests --parallel +ctest --test-dir build/cpp-tests --output-on-failure ``` -## Run examples +### Examples + +```sh +cmake -S lang/cpp -B build/cpp-examples -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DVORTEX_BUILD_EXAMPLES=ON +cmake --build build/cpp-examples --parallel +``` + +The executables are written to `build/cpp-examples/examples/`. + +### Sanitizers + +Install nightly Rust with `rust-src`, then select it explicitly: ```sh -cmake -Bbuild -DBUILD_EXAMPLES=ON -cmake --build build -j -./build/examples/hello-vortex +rustup toolchain install nightly --component rust-src +RUSTUP_TOOLCHAIN=nightly cmake -S lang/cpp -B build/cpp-asan -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DVORTEX_SANITIZER=asan \ + -DVORTEX_BUILD_TESTING=ON +cmake --build build/cpp-asan --parallel +ctest --test-dir build/cpp-asan --output-on-failure ``` -## Check coverage +### Coverage -This will generate an LCOV directory `coverage`: +Run the coverage helper from `lang/cpp`. It requires LCOV's `geninfo`, plus `genhtml` for HTML output: ```sh -./gcov-report.sh generate +cd lang/cpp +./gcov-report.sh +./gcov-report.sh html # Also writes coverage/ ``` diff --git a/lang/cpp/cmake/BuildVortexCargo.cmake b/lang/cpp/cmake/BuildVortexCargo.cmake new file mode 100644 index 00000000000..79395d6334f --- /dev/null +++ b/lang/cpp/cmake/BuildVortexCargo.cmake @@ -0,0 +1,157 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright the Vortex contributors + +# Build-time `cmake -P` driver for the custom target defined by +# VortexCargo.cmake. Configuration arrives through -D variables; this script +# defines no CMake targets. It recreates the validated Cargo environment and +# stages the resulting archive at the stable path consumed by CMake. Missing +# inputs, Cargo failure, or a missing/staging-failed archive are fatal. + +cmake_minimum_required(VERSION 3.28) + +# These non-empty inputs must cross from configure time as -D definitions. Fail +# before invoking Cargo rather than falling back to ambient build settings. +foreach(_required IN ITEMS + VORTEX_CARGO_EXECUTABLE + VORTEX_RUSTC_EXECUTABLE + VORTEX_RUST_TARGET + VORTEX_WORKSPACE_ROOT + VORTEX_FFI_MANIFEST + VORTEX_CARGO_CONFIG_FILE + VORTEX_CARGO_TARGET_DIR + VORTEX_CARGO_PROFILE + VORTEX_CARGO_FFI_ARCHIVE + VORTEX_CMAKE_FFI_ARCHIVE + VORTEX_TARGET_ENV_KEY_UPPER + VORTEX_TARGET_ENV_KEY_LOWER + VORTEX_RUSTFLAGS_FILE + VORTEX_CFLAGS_FILE + VORTEX_CXXFLAGS_FILE) + if(NOT DEFINED ${_required} OR "${${_required}}" STREQUAL "") + message(FATAL_ERROR "BuildVortexCargo.cmake requires ${_required}") + endif() +endforeach() + +file(MAKE_DIRECTORY "${VORTEX_CARGO_TARGET_DIR}") + +# Build commands are CMake lists so every option and value remains a distinct +# argv element. The calling custom target uses VERBATIM for its outer cmake -P +# invocation; execute_process consumes this inner list without a shell string. +set(_cargo_command "${VORTEX_CARGO_EXECUTABLE}" --config "${VORTEX_CARGO_CONFIG_FILE}") +if(VORTEX_CARGO_OFFLINE) + list(APPEND _cargo_command --offline) +endif() +list(APPEND _cargo_command + rustc + --locked + --package vortex-ffi + --lib + --crate-type=staticlib + --manifest-path "${VORTEX_FFI_MANIFEST}" + --target "${VORTEX_RUST_TARGET}" + --target-dir "${VORTEX_CARGO_TARGET_DIR}" + --profile "${VORTEX_CARGO_PROFILE}") +if(DEFINED VORTEX_CARGO_JOBS AND NOT VORTEX_CARGO_JOBS STREQUAL "") + list(APPEND _cargo_command --jobs "${VORTEX_CARGO_JOBS}") +endif() +if(VORTEX_CARGO_NO_DEFAULT_FEATURES) + list(APPEND _cargo_command --no-default-features) +endif() +if(DEFINED VORTEX_CARGO_FEATURES AND NOT VORTEX_CARGO_FEATURES STREQUAL "") + list(APPEND _cargo_command --features "${VORTEX_CARGO_FEATURES}") +endif() +if(VORTEX_CARGO_BUILD_STD) + list(APPEND _cargo_command -Zbuild-std) +endif() + +# Flag payloads use files because shell-quoted native flags and ASCII-31 encoded +# Rust flags cannot safely traverse another -D and CMake-list expansion. +foreach(_flags_file IN ITEMS VORTEX_RUSTFLAGS_FILE VORTEX_CFLAGS_FILE VORTEX_CXXFLAGS_FILE) + if(NOT EXISTS "${${_flags_file}}") + message(FATAL_ERROR "BuildVortexCargo.cmake cannot read ${_flags_file}: ${${_flags_file}}") + endif() +endforeach() +file(READ "${VORTEX_RUSTFLAGS_FILE}" _encoded_rustflags) +file(READ "${VORTEX_CFLAGS_FILE}" _native_c_flags) +file(READ "${VORTEX_CXXFLAGS_FILE}" _native_cxx_flags) + +# Cargo and its subprocesses must use the concrete rustc selected at configure +# time, not a rustup proxy or another PATH entry. Put its bin directory first +# and set both RUSTC and CARGO_BUILD_RUSTC below. +get_filename_component(_rust_toolchain_bin_dir "${VORTEX_RUSTC_EXECUTABLE}" DIRECTORY) +if(DEFINED ENV{PATH} AND NOT "$ENV{PATH}" STREQUAL "") + if("$ENV{PATH}" MATCHES ";") + message(FATAL_ERROR "The CMake-owned Cargo build does not support semicolons in PATH") + endif() + set(_cargo_path "${_rust_toolchain_bin_dir}:$ENV{PATH}") +else() + set(_cargo_path "${_rust_toolchain_bin_dir}") +endif() + +# Target-qualified linker, CC/CXX, AR, RANLIB, and flag variables force Cargo +# build scripts and native dependencies through CMake's selected toolchain while +# leaving unrelated host-target settings untouched. +set(_environment + "PATH=${_cargo_path}" + "RUSTC=${VORTEX_RUSTC_EXECUTABLE}" + "CARGO_BUILD_RUSTC=${VORTEX_RUSTC_EXECUTABLE}" + "CC_SHELL_ESCAPED_FLAGS=1" + "CARGO_TARGET_${VORTEX_TARGET_ENV_KEY_UPPER}_LINKER=${VORTEX_C_LINKER}" + "CC_${VORTEX_TARGET_ENV_KEY_LOWER}=${VORTEX_C_COMPILER}" + "CXX_${VORTEX_TARGET_ENV_KEY_LOWER}=${VORTEX_CXX_COMPILER}") +if(DEFINED VORTEX_AR AND NOT VORTEX_AR STREQUAL "") + list(APPEND _environment "AR_${VORTEX_TARGET_ENV_KEY_LOWER}=${VORTEX_AR}") +endif() +if(DEFINED VORTEX_RANLIB AND NOT VORTEX_RANLIB STREQUAL "") + list(APPEND _environment "RANLIB_${VORTEX_TARGET_ENV_KEY_LOWER}=${VORTEX_RANLIB}") +endif() +if(_native_c_flags) + list(APPEND _environment "CFLAGS_${VORTEX_TARGET_ENV_KEY_LOWER}=${_native_c_flags}") +endif() +if(_native_cxx_flags) + list(APPEND _environment "CXXFLAGS_${VORTEX_TARGET_ENV_KEY_LOWER}=${_native_cxx_flags}") +endif() +if(DEFINED VORTEX_APPLE_DEPLOYMENT_TARGET AND NOT VORTEX_APPLE_DEPLOYMENT_TARGET STREQUAL "") + list(APPEND _environment "MACOSX_DEPLOYMENT_TARGET=${VORTEX_APPLE_DEPLOYMENT_TARGET}") +endif() +if(DEFINED VORTEX_APPLE_SDKROOT AND NOT VORTEX_APPLE_SDKROOT STREQUAL "") + list(APPEND _environment "SDKROOT=${VORTEX_APPLE_SDKROOT}") +endif() + +# Use one validated, fingerprinted flag sequence at Cargo's highest-precedence +# environment layer. The explicit unsets remove ambient global and target Rust +# flags before installing that sequence, preventing hidden target-CPU or PIC +# changes from bypassing configure-time validation. +list(APPEND _environment "CARGO_ENCODED_RUSTFLAGS=${_encoded_rustflags}") +set(_command + "${CMAKE_COMMAND}" -E env + --unset=RUSTFLAGS + --unset=CARGO_ENCODED_RUSTFLAGS + "--unset=CARGO_TARGET_${VORTEX_TARGET_ENV_KEY_UPPER}_RUSTFLAGS" + ${_environment} + ${_cargo_command}) +# The caller's always-out-of-date custom target runs Cargo on every build. +# Cargo's own dependency cache decides whether compilation work is necessary. +execute_process( + COMMAND ${_command} + WORKING_DIRECTORY "${VORTEX_WORKSPACE_ROOT}" + COMMAND_ECHO STDOUT + RESULT_VARIABLE _cargo_result) +if(NOT _cargo_result EQUAL 0) + message(FATAL_ERROR "Cargo failed while building vortex-ffi (${_cargo_result})") +endif() + +if(NOT EXISTS "${VORTEX_CARGO_FFI_ARCHIVE}") + message(FATAL_ERROR + "Cargo completed successfully but did not produce the expected " + "static archive: ${VORTEX_CARGO_FFI_ARCHIVE}") +endif() + +get_filename_component(_cmake_archive_directory "${VORTEX_CMAKE_FFI_ARCHIVE}" DIRECTORY) +file(MAKE_DIRECTORY "${_cmake_archive_directory}") +# Stage only changed bytes so repeated no-op Cargo runs do not advance the +# imported archive's timestamp and trigger avoidable downstream relinks. +file(COPY_FILE "${VORTEX_CARGO_FFI_ARCHIVE}" "${VORTEX_CMAKE_FFI_ARCHIVE}" ONLY_IF_DIFFERENT) +if(NOT EXISTS "${VORTEX_CMAKE_FFI_ARCHIVE}") + message(FATAL_ERROR "Failed to stage the vortex-ffi archive for CMake: ${VORTEX_CMAKE_FFI_ARCHIVE}") +endif() diff --git a/lang/cpp/cmake/VortexCargo.cmake b/lang/cpp/cmake/VortexCargo.cmake new file mode 100644 index 00000000000..a6ac34cc1c4 --- /dev/null +++ b/lang/cpp/cmake/VortexCargo.cmake @@ -0,0 +1,511 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright the Vortex contributors + +# Configure-time integration that builds vortex-ffi with Cargo and exposes it as +# a CMake static-library target. Its lifecycle is validation -> toolchain +# resolution -> fingerprinting -> support files -> custom target -> imported +# target. + +include_guard(GLOBAL) + +include("${CMAKE_CURRENT_LIST_DIR}/VortexHelpers.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/VortexRustToolchain.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/VortexStaticLink.cmake") + +# Normalize the user-facing comma-separated feature scalar into the canonical +# value written to `output` in PARENT_SCOPE. Semicolons and features outside the +# allowlist are fatal because they cannot participate in a supported build. +function(_vortex_normalize_cargo_features input output) + _vortex_reject_semicolon("VORTEX_CARGO_FEATURES" "${input}") + string(REPLACE "," ";" _features "${input}") + set(_normalized_features) + foreach(_feature IN LISTS _features) + string(STRIP "${_feature}" _feature) + if(_feature STREQUAL "") + continue() + endif() + if(NOT _feature STREQUAL "mimalloc") + message(FATAL_ERROR + "Unsupported vortex-ffi Cargo feature '${_feature}'. The " + "validated feature sets are empty and mimalloc.") + endif() + list(APPEND _normalized_features "${_feature}") + endforeach() + list(REMOVE_DUPLICATES _normalized_features) + list(SORT _normalized_features) + string(JOIN "," _normalized_features ${_normalized_features}) + set(${output} "${_normalized_features}" PARENT_SCOPE) +endfunction() + +# Validate the tokenized rustc arguments in ARGN against portability policy. +# This function has no output; target-cpu=native and incomplete codegen options +# are fatal. Recognizing every supported -C/--codegen spelling prevents +# alternate syntax from bypassing the check. +function(_vortex_validate_rustflags) + set(_expect_codegen_option FALSE) + foreach(_rustflag IN LISTS ARGN) + set(_codegen_option "") + if(_expect_codegen_option) + set(_codegen_option "${_rustflag}") + set(_expect_codegen_option FALSE) + elseif(_rustflag STREQUAL "-C" OR _rustflag STREQUAL "--codegen") + set(_expect_codegen_option TRUE) + continue() + elseif(_rustflag MATCHES "^-C=(.+)$") + set(_codegen_option "${CMAKE_MATCH_1}") + elseif(_rustflag MATCHES "^-C(.+)$") + set(_codegen_option "${CMAKE_MATCH_1}") + elseif(_rustflag MATCHES "^--codegen=(.+)$") + set(_codegen_option "${CMAKE_MATCH_1}") + else() + continue() + endif() + + if(_codegen_option MATCHES "^target-cpu=(.+)$") + string(TOLOWER "${CMAKE_MATCH_1}" _target_cpu) + if(_target_cpu STREQUAL "native") + message(FATAL_ERROR + "Vortex builds do not support Rust target-cpu=native because " + "build-host CPU features are unsafe for distributed parent " + "artifacts") + endif() + endif() + endforeach() + + if(_expect_codegen_option) + message(FATAL_ERROR "VORTEX_RUSTFLAGS ends with an incomplete codegen option") + endif() +endfunction() + +# Build the repository's vortex-ffi crate as a private C++ dependency. +# VORTEX_* options supply build policy. Unsupported configuration or incoherent +# toolchain metadata is fatal. +function(_vortex_add_ffi_static) + if(ARGC GREATER 0) + message(FATAL_ERROR "_vortex_add_ffi_static does not accept arguments") + endif() + if(TARGET vortex_ffi_static) + message(FATAL_ERROR "The internal vortex_ffi_static target already exists") + endif() + _vortex_normalize_cargo_features("${VORTEX_CARGO_FEATURES}" _cargo_features) + + # One Cargo profile is selected per invocation, so multi-config generators + # cannot share this target without ambiguous artifact locations. + if(CMAKE_CONFIGURATION_TYPES) + message(FATAL_ERROR + "The initial Vortex CMake integration supports single-config " + "generators only; use Ninja with CMAKE_BUILD_TYPE=Debug, Release, " + "or RelWithDebInfo") + endif() + string(TOUPPER "${CMAKE_BUILD_TYPE}" _configuration) + if(_configuration STREQUAL "") + message(STATUS + "Vortex: CMAKE_BUILD_TYPE is empty; using the Debug Cargo " + "profile without modifying the parent build type") + elseif(NOT _configuration STREQUAL "DEBUG" AND + NOT _configuration STREQUAL "RELEASE" AND + NOT _configuration STREQUAL "RELWITHDEBINFO") + message(FATAL_ERROR + "The ${CMAKE_BUILD_TYPE} CMake configuration has no configured " + "Cargo profile. Supported configurations are Debug, Release, and " + "RelWithDebInfo.") + endif() + + # Keep sanitizer builds on their validated nightly/Debug path and reject the + # allocator combination whose behavior cannot be guaranteed. + string(TOLOWER "${VORTEX_SANITIZER}" _sanitizer) + if(NOT _sanitizer STREQUAL "" AND + NOT _sanitizer STREQUAL "asan" AND + NOT _sanitizer STREQUAL "tsan") + message(FATAL_ERROR + "VORTEX_SANITIZER must be empty, asan, or tsan; got " + "${VORTEX_SANITIZER}") + endif() + if(NOT _sanitizer STREQUAL "" AND _cargo_features STREQUAL "mimalloc") + message(FATAL_ERROR + "VORTEX_CARGO_FEATURES=mimalloc is incompatible with " + "VORTEX_SANITIZER=${_sanitizer}; disable mimalloc for sanitizer " + "builds") + endif() + if(NOT _sanitizer STREQUAL "" AND + NOT _configuration STREQUAL "" AND + NOT _configuration STREQUAL "DEBUG") + message(FATAL_ERROR "Vortex sanitizer builds require CMAKE_BUILD_TYPE=Debug") + endif() + + if(VORTEX_CARGO_JOBS AND NOT VORTEX_CARGO_JOBS MATCHES "^[1-9][0-9]*$") + message(FATAL_ERROR "VORTEX_CARGO_JOBS must be a positive integer") + endif() + + # Keep the CMake configuration, Cargo profile, and Cargo artifact directory + # in a single explicit mapping used by both the command and staged path. + if(_configuration STREQUAL "" OR _configuration STREQUAL "DEBUG") + set(_configuration_name "Debug") + set(_cargo_profile "dev") + set(_cargo_artifact_directory "debug") + elseif(_configuration STREQUAL "RELEASE") + set(_configuration_name "Release") + set(_cargo_profile "release") + set(_cargo_artifact_directory "release") + else() + set(_configuration_name "RelWithDebInfo") + set(_cargo_profile "release_debug") + set(_cargo_artifact_directory "release_debug") + endif() + + # Canonicalize workspace identity before resolving tools. Values that cross + # CMake list and COMMAND boundaries must remain scalar: a semicolon would + # split one path or shell-style flag string into multiple elements. + get_filename_component(_workspace_root "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../.." ABSOLUTE) + set(_ffi_source_dir "${_workspace_root}/vortex-ffi") + set(_ffi_manifest "${_ffi_source_dir}/Cargo.toml") + if(NOT EXISTS "${_workspace_root}/Cargo.toml" OR + NOT EXISTS "${_workspace_root}/Cargo.lock" OR + NOT EXISTS "${_ffi_manifest}") + message(FATAL_ERROR + "Vortex's CMake source build requires a complete workspace " + "checkout with Cargo.toml, Cargo.lock, and vortex-ffi/Cargo.toml") + endif() + file(REAL_PATH "${_workspace_root}" _workspace_real_path) + _vortex_reject_semicolon("Vortex workspace path" "${_workspace_root}") + _vortex_reject_semicolon("Vortex workspace real path" "${_workspace_real_path}") + _vortex_reject_semicolon("Vortex FFI source path" "${_ffi_source_dir}") + _vortex_reject_semicolon("Vortex FFI manifest path" "${_ffi_manifest}") + _vortex_reject_semicolon("Vortex binary path" "${CMAKE_CURRENT_BINARY_DIR}") + _vortex_reject_semicolon("VORTEX_CARGO_TARGET_DIR" "${VORTEX_CARGO_TARGET_DIR}") + _vortex_reject_semicolon("VORTEX_RUSTFLAGS" "${VORTEX_RUSTFLAGS}") + + # Resolve rustup proxies to concrete Cargo/rustc binaries, then verify the + # Rust target and native C/C++ compilers describe the same host ABI. + _vortex_find_rust_tools("${_workspace_root}") + if(NOT _sanitizer STREQUAL "" AND NOT VORTEX_RESOLVED_RUSTC_RELEASE MATCHES "nightly") + message(FATAL_ERROR + "VORTEX_SANITIZER=${_sanitizer} requires a nightly Rust " + "toolchain; found ${VORTEX_RESOLVED_RUSTC_RELEASE}") + endif() + _vortex_resolve_native_target("${VORTEX_RESOLVED_RUSTC_EXECUTABLE}" "${_workspace_root}" _rust_target) + _vortex_resolve_apple_deployment_target(_apple_deployment_target) + _vortex_resolve_apple_sdkroot(_apple_sdkroot _apple_sdk_version) + + string(TOUPPER "${_rust_target}" _target_env_upper) + string(REPLACE "-" "_" _target_env_upper "${_target_env_upper}") + string(TOLOWER "${_rust_target}" _target_env_lower) + string(REPLACE "-" "_" _target_env_lower "${_target_env_lower}") + + # Parse the opted-in flag string with shell rules so rustc receives the + # intended argv. Ambient Rust flags are excluded because unvalidated state + # must not evade portability checks or the build fingerprint. + set(_user_rustflags) + if(VORTEX_RUSTFLAGS) + separate_arguments(_user_rustflags UNIX_COMMAND "${VORTEX_RUSTFLAGS}") + endif() + set(_ambient_rustflags FALSE) + if(DEFINED ENV{CARGO_ENCODED_RUSTFLAGS}) + if(NOT "$ENV{CARGO_ENCODED_RUSTFLAGS}" STREQUAL "") + set(_ambient_rustflags TRUE) + endif() + endif() + if(DEFINED ENV{RUSTFLAGS}) + if(NOT "$ENV{RUSTFLAGS}" STREQUAL "") + set(_ambient_rustflags TRUE) + endif() + endif() + if(_ambient_rustflags) + message(STATUS + "Vortex ignores ambient Rust flags; use VORTEX_RUSTFLAGS so " + "the validated flags participate in the CMake fingerprint") + endif() + set(_cargo_build_std OFF) + set(_cargo_no_default_features ON) + set(_sanitizer_compile_flag "") + if(_sanitizer STREQUAL "asan") + set(_cargo_build_std ON) + set(_sanitizer_compile_flag "-fsanitize=address,undefined,leak") + list(APPEND _user_rustflags + -A warnings + -Cunsafe-allow-abi-mismatch=sanitizer + -C debuginfo=2 + -C opt-level=0 + -C strip=none + -Zexternal-clangrt + -Zsanitizer=address,leak) + elseif(_sanitizer STREQUAL "tsan") + set(_cargo_build_std ON) + set(_sanitizer_compile_flag "-fsanitize=thread") + list(APPEND _user_rustflags + -A warnings + -Cunsafe-allow-abi-mismatch=sanitizer + -C debuginfo=2 + -C opt-level=0 + -C strip=none + -Zexternal-clangrt + -Zsanitizer=thread) + endif() + + _vortex_validate_rustflags(${_user_rustflags}) + + # Preserve CMake's separate compile and link sysroots. C/C++ compilations from + # Cargo build scripts consume the former, while rustc forwards the latter at + # link. + if(CMAKE_SYSROOT_COMPILE) + set(_compile_sysroot "${CMAKE_SYSROOT_COMPILE}") + elseif(CMAKE_SYSROOT) + set(_compile_sysroot "${CMAKE_SYSROOT}") + elseif(APPLE) + set(_compile_sysroot "${_apple_sdkroot}") + else() + set(_compile_sysroot "") + endif() + if(CMAKE_SYSROOT_LINK) + set(_link_sysroot "${CMAKE_SYSROOT_LINK}") + elseif(CMAKE_SYSROOT) + set(_link_sysroot "${CMAKE_SYSROOT}") + elseif(APPLE) + set(_link_sysroot "${_apple_sdkroot}") + else() + set(_link_sysroot "") + endif() + + # Cargo build scripts must use the exact CMake-selected native toolchain. + # Compiler wrappers generated below retain any CMAKE_*_COMPILER_ARG1 prefix. + _vortex_optional_value(CMAKE_C_COMPILER_ARG1 _c_compiler_arg1) + _vortex_optional_value(CMAKE_C_COMPILER_TARGET _c_compiler_target) + _vortex_optional_value(CMAKE_CXX_COMPILER _cxx_compiler) + _vortex_optional_value(CMAKE_CXX_COMPILER_ARG1 _cxx_compiler_arg1) + _vortex_optional_value(CMAKE_CXX_COMPILER_ID _cxx_compiler_id) + _vortex_optional_value(CMAKE_CXX_COMPILER_VERSION _cxx_compiler_version) + _vortex_optional_value(CMAKE_CXX_COMPILER_TARGET _cxx_compiler_target) + _vortex_optional_value(CMAKE_AR _archiver) + _vortex_optional_value(CMAKE_RANLIB _ranlib) + + if(_c_compiler_target AND CMAKE_C_COMPILER_ID MATCHES "^(AppleClang|Clang)$") + set(_native_c_compiler_target "${_c_compiler_target}") + else() + set(_native_c_compiler_target "") + endif() + if(_cxx_compiler_target AND _cxx_compiler_id MATCHES "^(AppleClang|Clang)$") + set(_native_cxx_compiler_target "${_cxx_compiler_target}") + else() + set(_native_cxx_compiler_target "") + endif() + + _vortex_reject_semicolon("CMAKE_C_COMPILER" "${CMAKE_C_COMPILER}") + _vortex_reject_semicolon("CMAKE_C_COMPILER_ARG1" "${_c_compiler_arg1}") + _vortex_reject_semicolon("CMAKE_CXX_COMPILER" "${_cxx_compiler}") + _vortex_reject_semicolon("CMAKE_CXX_COMPILER_ARG1" "${_cxx_compiler_arg1}") + _vortex_reject_semicolon("CMAKE_AR" "${_archiver}") + _vortex_reject_semicolon("CMAKE_RANLIB" "${_ranlib}") + _vortex_reject_semicolon("compile sysroot" "${_compile_sysroot}") + _vortex_reject_semicolon("link sysroot" "${_link_sysroot}") + + # Reconstruct effective configuration-specific native flags as argv, then add + # target, sysroot, deployment, sanitizer, and PIC requirements consistently + # for native dependencies compiled from Cargo build scripts. + _vortex_optional_value(CMAKE_C_FLAGS _cmake_c_flags) + _vortex_optional_value(CMAKE_CXX_FLAGS _cmake_cxx_flags) + if(NOT _configuration STREQUAL "") + _vortex_optional_value(CMAKE_C_FLAGS_${_configuration} _cmake_c_configuration_flags) + _vortex_optional_value(CMAKE_CXX_FLAGS_${_configuration} _cmake_cxx_configuration_flags) + string(APPEND _cmake_c_flags " ${_cmake_c_configuration_flags}") + string(APPEND _cmake_cxx_flags " ${_cmake_cxx_configuration_flags}") + endif() + _vortex_reject_semicolon("effective CMAKE_C_FLAGS" "${_cmake_c_flags}") + _vortex_reject_semicolon("effective CMAKE_CXX_FLAGS" "${_cmake_cxx_flags}") + separate_arguments(_native_c_flags UNIX_COMMAND "${_cmake_c_flags}") + separate_arguments(_native_cxx_flags UNIX_COMMAND "${_cmake_cxx_flags}") + if(_compile_sysroot) + list(APPEND _native_c_flags "--sysroot=${_compile_sysroot}") + list(APPEND _native_cxx_flags "--sysroot=${_compile_sysroot}") + endif() + if(_native_c_compiler_target) + list(APPEND _native_c_flags "--target=${_native_c_compiler_target}") + endif() + if(_native_cxx_compiler_target) + list(APPEND _native_cxx_flags "--target=${_native_cxx_compiler_target}") + elseif(_native_c_compiler_target AND NOT _cxx_compiler) + list(APPEND _native_cxx_flags "--target=${_native_c_compiler_target}") + endif() + if(_apple_deployment_target) + list(APPEND _native_c_flags "-mmacosx-version-min=${_apple_deployment_target}") + list(APPEND _native_cxx_flags "-mmacosx-version-min=${_apple_deployment_target}") + endif() + if(_sanitizer_compile_flag) + list(APPEND _native_c_flags "${_sanitizer_compile_flag}") + list(APPEND _native_cxx_flags "${_sanitizer_compile_flag}") + endif() + # Native dependencies compiled by Cargo build scripts are part of the static + # archive and must remain suitable for embedding in a shared parent. + list(APPEND _native_c_flags -fPIC) + list(APPEND _native_cxx_flags -fPIC) + _vortex_encode_shell_arguments(_native_c_flags_shell ${_native_c_flags}) + _vortex_encode_shell_arguments(_native_cxx_flags_shell ${_native_cxx_flags}) + + # Cargo's encoded rustflags format uses ASCII unit separator (31) between + # arguments, preserving exact rustc argv without another shell/list parse. + set(_final_rustflags ${_user_rustflags}) + list(APPEND _final_rustflags + -C force-frame-pointers=yes + -C relocation-model=pic) + if(_link_sysroot) + list(APPEND _final_rustflags -C "link-arg=--sysroot=${_link_sysroot}") + endif() + if(_native_c_compiler_target) + list(APPEND _final_rustflags -C "link-arg=--target=${_native_c_compiler_target}") + endif() + string(ASCII 31 _rustflags_separator) + string(JOIN "${_rustflags_separator}" _encoded_rustflags ${_final_rustflags}) + + # Hash every resolved toolchain and ABI/codegen input that can affect the + # archive. A fingerprint-specific Cargo tree isolates incompatible caches; + # profile outputs are separated again by configuration below. + set(_fingerprint_input + "${_workspace_real_path}|" + "${VORTEX_RESOLVED_CARGO_EXECUTABLE}|${VORTEX_RESOLVED_CARGO_VERBOSE}|" + "${VORTEX_RESOLVED_RUSTC_EXECUTABLE}|${VORTEX_RESOLVED_RUSTC_VERBOSE}|" + "${_rust_target}|${_cargo_features}|${_cargo_no_default_features}|" + "${CMAKE_C_COMPILER}|${_c_compiler_arg1}|" + "${CMAKE_C_COMPILER_ID}|${CMAKE_C_COMPILER_VERSION}|" + "${_c_compiler_target}|" + "${_cxx_compiler}|${_cxx_compiler_arg1}|" + "${_cxx_compiler_id}|${_cxx_compiler_version}|" + "${_cxx_compiler_target}|${_archiver}|${_ranlib}|" + "${_compile_sysroot}|${_link_sysroot}|${_apple_sdkroot}|" + "${_apple_sdk_version}|" + "${_apple_deployment_target}|" + "${_sanitizer}|${_native_c_flags_shell}|" + "${_native_cxx_flags_shell}|${_encoded_rustflags}") + string(SHA256 _fingerprint "${_fingerprint_input}") + + if(VORTEX_CARGO_TARGET_DIR) + get_filename_component(_cargo_target_root + "${VORTEX_CARGO_TARGET_DIR}" ABSOLUTE + BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + else() + set(_cargo_target_root "${CMAKE_CURRENT_BINARY_DIR}/cargo-target") + endif() + set(_cargo_target_base "${_cargo_target_root}/${_fingerprint}") + set(_cargo_support_dir "${_cargo_target_base}/cmake-support") + + # An explicit target root may be shared by concurrent configure processes. + # Hold a function-scoped lock while producing wrappers and support files so a + # build cannot observe a mixed or partially generated configuration. + file(MAKE_DIRECTORY "${_cargo_support_dir}") + file(LOCK "${_cargo_support_dir}/configure.lock" + GUARD FUNCTION + TIMEOUT 60 + RESULT_VARIABLE _cargo_support_lock_result) + if(NOT _cargo_support_lock_result EQUAL 0) + message(FATAL_ERROR + "Could not lock Vortex Cargo support directory " + "${_cargo_support_dir}: ${_cargo_support_lock_result}") + endif() + + _vortex_make_compiler_wrapper( + "${CMAKE_C_COMPILER}" "${_c_compiler_arg1}" + "${_cargo_support_dir}" cc _native_c_compiler) + if(_cxx_compiler) + _vortex_make_compiler_wrapper( + "${_cxx_compiler}" "${_cxx_compiler_arg1}" + "${_cargo_support_dir}" cxx _native_cxx_compiler) + else() + set(_native_cxx_compiler "${_native_c_compiler}") + endif() + + # Passing this generated file via Cargo --config makes its linker and profile + # policy explicit rather than dependent on directory discovery. The driver's + # target-specific environment has higher precedence for linker and rustflags. + set(_cargo_config "${_cargo_support_dir}/config.toml") + _vortex_toml_string("${_native_c_compiler}" _linker_toml) + string(CONCAT _cargo_config_contents + "[target.\"${_rust_target}\"]\n" + "linker = ${_linker_toml}\n" + "\n" + "[profile.dev]\n" + "strip = \"none\"\n" + "\n" + "[profile.release]\n" + "codegen-units = 1\n" + "lto = \"off\"\n" + "strip = \"none\"\n" + "\n" + "[profile.release_debug]\n" + "debug = \"full\"\n" + "strip = \"none\"\n") + _vortex_write_if_different("${_cargo_config}" "${_cargo_config_contents}") + + # Files carry shell-quoted native flags and ASCII-31 Rust flags safely across + # the configure/build boundary. Write-if-different avoids needless timestamp + # churn in the shared support directory. + set(_rustflags_file "${_cargo_support_dir}/rustflags") + set(_cflags_file "${_cargo_support_dir}/cflags") + set(_cxxflags_file "${_cargo_support_dir}/cxxflags") + _vortex_write_if_different("${_rustflags_file}" "${_encoded_rustflags}") + _vortex_write_if_different("${_cflags_file}" "${_native_c_flags_shell}") + _vortex_write_if_different("${_cxxflags_file}" "${_native_cxx_flags_shell}") + + set(_archive_name "libvortex_ffi.a") + set(_cargo_target_dir "${_cargo_target_base}/${_configuration_name}") + string(CONCAT _cargo_ffi_archive + "${_cargo_target_dir}/${_rust_target}/" + "${_cargo_artifact_directory}/${_archive_name}") + string(CONCAT _ffi_archive + "${CMAKE_CURRENT_BINARY_DIR}/vortex-artifacts/" + "${_configuration_name}/${_archive_name}") + + # This phony target intentionally has no OUTPUT, so Cargo checks freshness on + # every build. BYPRODUCTS describes the staged archive to generators; each -D + # assignment is one list element and VERBATIM protects the outer cmake -P + # driver's argument boundaries. + add_custom_target(vortex_ffi_cargo_build ALL + COMMAND "${CMAKE_COMMAND}" + "-DVORTEX_CARGO_EXECUTABLE=${VORTEX_RESOLVED_CARGO_EXECUTABLE}" + "-DVORTEX_RUSTC_EXECUTABLE=${VORTEX_RESOLVED_RUSTC_EXECUTABLE}" + "-DVORTEX_RUST_TARGET=${_rust_target}" + "-DVORTEX_WORKSPACE_ROOT=${_workspace_root}" + "-DVORTEX_FFI_MANIFEST=${_ffi_manifest}" + "-DVORTEX_CARGO_CONFIG_FILE=${_cargo_config}" + "-DVORTEX_CARGO_TARGET_DIR=${_cargo_target_dir}" + "-DVORTEX_CARGO_PROFILE=${_cargo_profile}" + "-DVORTEX_CARGO_JOBS=${VORTEX_CARGO_JOBS}" + "-DVORTEX_CARGO_OFFLINE=${VORTEX_CARGO_OFFLINE}" + "-DVORTEX_CARGO_FEATURES=${_cargo_features}" + "-DVORTEX_CARGO_BUILD_STD=${_cargo_build_std}" + "-DVORTEX_CARGO_NO_DEFAULT_FEATURES=${_cargo_no_default_features}" + "-DVORTEX_CARGO_FFI_ARCHIVE=${_cargo_ffi_archive}" + "-DVORTEX_CMAKE_FFI_ARCHIVE=${_ffi_archive}" + "-DVORTEX_TARGET_ENV_KEY_UPPER=${_target_env_upper}" + "-DVORTEX_TARGET_ENV_KEY_LOWER=${_target_env_lower}" + "-DVORTEX_C_LINKER=${_native_c_compiler}" + "-DVORTEX_C_COMPILER=${_native_c_compiler}" + "-DVORTEX_CXX_COMPILER=${_native_cxx_compiler}" + "-DVORTEX_AR=${_archiver}" + "-DVORTEX_RANLIB=${_ranlib}" + "-DVORTEX_RUSTFLAGS_FILE=${_rustflags_file}" + "-DVORTEX_CFLAGS_FILE=${_cflags_file}" + "-DVORTEX_CXXFLAGS_FILE=${_cxxflags_file}" + "-DVORTEX_APPLE_DEPLOYMENT_TARGET=${_apple_deployment_target}" + "-DVORTEX_APPLE_SDKROOT=${_apple_sdkroot}" + -P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/BuildVortexCargo.cmake" + BYPRODUCTS "${_ffi_archive}" + WORKING_DIRECTORY "${_workspace_root}" + COMMENT "Building the PIC vortex-ffi static archive with Cargo" + USES_TERMINAL + VERBATIM) + + # Keep the imported archive scoped to the C++ source directory. The public + # C++ target carries its include path, native libraries, and build dependency + # transitively without exposing a second supported consumer target. + add_library(vortex_ffi_static STATIC IMPORTED) + set_target_properties(vortex_ffi_static PROPERTIES + IMPORTED_LOCATION "${_ffi_archive}" + INTERFACE_INCLUDE_DIRECTORIES "${_ffi_source_dir}/cinclude") + add_dependencies(vortex_ffi_static vortex_ffi_cargo_build) + _vortex_configure_static_link(vortex_ffi_static "${_rust_target}") + if(_sanitizer_compile_flag) + target_compile_options(vortex_ffi_static INTERFACE "${_sanitizer_compile_flag}") + target_link_options(vortex_ffi_static INTERFACE "${_sanitizer_compile_flag}") + endif() + + message(STATUS "Vortex Rust target: ${_rust_target}") + message(STATUS "Vortex Cargo target base: ${_cargo_target_base}") +endfunction() diff --git a/lang/cpp/cmake/VortexHelpers.cmake b/lang/cpp/cmake/VortexHelpers.cmake new file mode 100644 index 00000000000..58ce3496843 --- /dev/null +++ b/lang/cpp/cmake/VortexHelpers.cmake @@ -0,0 +1,100 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright the Vortex contributors + +# Internal quoting and file-generation primitives shared by the CMake modules. +# Value-producing helpers accept an output variable name and assign it with +# PARENT_SCOPE because CMake functions otherwise isolate their variables. + +include_guard(GLOBAL) + +# Copy an optional variable to a named output, normalizing undefined to empty. +function(_vortex_optional_value variable output) + if(DEFINED ${variable}) + set(${output} "${${variable}}" PARENT_SCOPE) + else() + set(${output} "" PARENT_SCOPE) + endif() +endfunction() + +# Reject values that cannot remain scalar: CMake stores lists as semicolon- +# delimited strings, so downstream list operations would change argv boundaries. +function(_vortex_reject_semicolon label value) + if("${value}" MATCHES ";") + message(FATAL_ERROR + "${label} contains a semicolon, which is unsupported because " + "CMake uses semicolons as list separators: ${value}") + endif() +endfunction() + +# Encode a validated scalar as a quoted TOML basic string. These escapes belong +# to TOML and are intentionally distinct from the shell quoting below. +function(_vortex_toml_string input output) + string(REPLACE "\\" "\\\\" _value "${input}") + string(REPLACE "\"" "\\\"" _value "${_value}") + set(${output} "\"${_value}\"" PARENT_SCOPE) +endfunction() + +# Encode one POSIX shell word; embedded apostrophes close and reopen the quoted +# segment so the original argument remains a single word. +function(_vortex_shell_quote input output) + string(REPLACE "'" "'\"'\"'" _quoted "${input}") + set(${output} "'${_quoted}'" PARENT_SCOPE) +endfunction() + +# Preserve generated-file mtimes when content is unchanged, avoiding needless +# downstream CMake or Cargo rebuild triggers. +function(_vortex_write_if_different path content) + set(_write_file TRUE) + if(EXISTS "${path}") + file(READ "${path}" _existing_content) + if("${_existing_content}" STREQUAL "${content}") + set(_write_file FALSE) + endif() + endif() + if(_write_file) + file(WRITE "${path}" "${content}") + endif() +endfunction() + +# Serialize a CMake argv list as a single POSIX-shell command fragment while +# preserving each list element as one shell word. +function(_vortex_encode_shell_arguments output) + set(_encoded "") + foreach(_argument IN LISTS ARGN) + _vortex_shell_quote("${_argument}" _argument_quoted) + if(_encoded) + string(APPEND _encoded " ") + endif() + string(APPEND _encoded "${_argument_quoted}") + endforeach() + set(${output} "${_encoded}" PARENT_SCOPE) +endfunction() + +# Return the compiler directly unless CMAKE__COMPILER_ARG1 contributes +# required prefix arguments. Cargo accepts a compiler executable path, so a +# generated wrapper preserves CMake's complete compiler command for CC/CXX. +function(_vortex_make_compiler_wrapper compiler arg1 directory name output) + if(arg1 STREQUAL "") + set(${output} "${compiler}" PARENT_SCOPE) + return() + endif() + + set(_command "${compiler}") + separate_arguments(_compiler_arg1 NATIVE_COMMAND "${arg1}") + list(APPEND _command ${_compiler_arg1}) + set(_script "#!/bin/sh\nexec") + foreach(_argument IN LISTS _command) + _vortex_shell_quote("${_argument}" _argument_quoted) + string(APPEND _script " ${_argument_quoted}") + endforeach() + string(APPEND _script " \"$@\"\n") + + set(_wrapper "${directory}/${name}") + _vortex_write_if_different("${_wrapper}" "${_script}") + file(CHMOD "${_wrapper}" + PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) + set(${output} "${_wrapper}" PARENT_SCOPE) +endfunction() diff --git a/lang/cpp/cmake/VortexOptions.cmake b/lang/cpp/cmake/VortexOptions.cmake new file mode 100644 index 00000000000..300112ffece --- /dev/null +++ b/lang/cpp/cmake/VortexOptions.cmake @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright the Vortex contributors + +# Public options for the source-only C++ and Cargo integration. Normal variables +# set by an embedding parent take precedence over the cache defaults below. + +include_guard(GLOBAL) + +option(VORTEX_BUILD_TESTING "Build Vortex C++ tests" OFF) +option(VORTEX_BUILD_EXAMPLES "Build Vortex C++ examples" OFF) +option(VORTEX_CARGO_OFFLINE "Require Cargo to run in offline mode" OFF) + +if(NOT DEFINED VORTEX_SANITIZER) + set(VORTEX_SANITIZER "" CACHE STRING "Instrument Vortex and its consumers with asan or tsan") +endif() +get_property(_sanitizer_is_cached CACHE VORTEX_SANITIZER PROPERTY TYPE SET) +if(_sanitizer_is_cached) + set_property(CACHE VORTEX_SANITIZER PROPERTY STRINGS "" asan tsan) +endif() + +if(NOT DEFINED VORTEX_CARGO_EXECUTABLE) + set(VORTEX_CARGO_EXECUTABLE "" CACHE FILEPATH "Path to the Cargo executable") +endif() +if(NOT DEFINED VORTEX_RUSTC_EXECUTABLE) + set(VORTEX_RUSTC_EXECUTABLE "" CACHE FILEPATH "Path to the rustc executable") +endif() +if(NOT DEFINED VORTEX_CARGO_TARGET_DIR) + set(VORTEX_CARGO_TARGET_DIR "" CACHE PATH "Root for fingerprinted Cargo target directories") +endif() +if(NOT DEFINED VORTEX_CARGO_JOBS) + set(VORTEX_CARGO_JOBS "" CACHE STRING "Maximum number of nested Cargo jobs") +endif() +if(NOT DEFINED VORTEX_CARGO_FEATURES) + set(VORTEX_CARGO_FEATURES "" CACHE STRING "Comma-separated vortex-ffi Cargo features") +endif() +if(NOT DEFINED VORTEX_RUSTFLAGS) + set(VORTEX_RUSTFLAGS "" CACHE STRING "Shell-style Rust flags for the CMake-owned vortex-ffi build") +endif() diff --git a/lang/cpp/cmake/VortexRustToolchain.cmake b/lang/cpp/cmake/VortexRustToolchain.cmake new file mode 100644 index 00000000000..63e4b0bd22c --- /dev/null +++ b/lang/cpp/cmake/VortexRustToolchain.cmake @@ -0,0 +1,531 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright the Vortex contributors + +# Rust toolchain and native-ABI validation for Cargo-backed Vortex CMake builds. +# This module resolves concrete tools and one supported native Rust target +# before build rules are created; it neither installs toolchains nor enables +# cross builds. + +include_guard(GLOBAL) + +include("${CMAKE_CURRENT_LIST_DIR}/VortexHelpers.cmake") + +# Normalize a processor or target-like value to Vortex's architecture spelling. +# Unknown values pass through lowercased so callers can report the original +# unsupported selection. +function(_vortex_normalize_arch input output) + string(TOLOWER "${input}" _arch) + if(_arch MATCHES "^(x86_64|amd64)(-|$)") + set(_arch "x86_64") + elseif(_arch MATCHES "^(aarch64|arm64)(-|$)") + set(_arch "aarch64") + endif() + set(${output} "${_arch}" PARENT_SCOPE) +endfunction() + +# Resolve a Cargo-family executable candidate. `configured_value` is an absolute +# path or command name; an empty value selects `program_name`. Names search +# normal system paths before conventional Cargo homes. The variable named by +# `output` receives an absolute path in PARENT_SCOPE; a missing or non-file +# result is fatal. +function(_vortex_find_rust_program output program_name configured_value) + unset(_program) + if(configured_value) + if(IS_ABSOLUTE "${configured_value}") + set(_program "${configured_value}") + else() + find_program(_program NAMES "${configured_value}" NO_CACHE) + endif() + else() + find_program(_program NAMES "${program_name}" NO_CACHE) + endif() + + # Explicit paths and normal command lookup take precedence. Cargo homes are a + # fallback for embedding environments whose PATH omits rustup's installation. + if(NOT _program) + set(_rust_program_hints) + if(DEFINED ENV{CARGO_HOME} AND NOT "$ENV{CARGO_HOME}" STREQUAL "") + _vortex_reject_semicolon("CARGO_HOME" "$ENV{CARGO_HOME}") + list(APPEND _rust_program_hints "$ENV{CARGO_HOME}/bin") + endif() + if(DEFINED ENV{HOME} AND NOT "$ENV{HOME}" STREQUAL "") + _vortex_reject_semicolon("HOME" "$ENV{HOME}") + list(APPEND _rust_program_hints "$ENV{HOME}/.cargo/bin") + endif() + if(configured_value) + set(_program_names "${configured_value}") + else() + set(_program_names "${program_name}") + endif() + find_program(_program + NAMES ${_program_names} + HINTS ${_rust_program_hints} + NO_DEFAULT_PATH + NO_CACHE) + endif() + + if(NOT _program OR NOT EXISTS "${_program}" OR IS_DIRECTORY "${_program}") + if(configured_value) + message(FATAL_ERROR "Configured ${program_name} executable does not exist: ${configured_value}") + else() + message(FATAL_ERROR "Could not find ${program_name} in PATH, CARGO_HOME/bin, or HOME/.cargo/bin") + endif() + endif() + get_filename_component(_program "${_program}" ABSOLUTE) + _vortex_reject_semicolon("Resolved ${program_name} executable" "${_program}") + set(${output} "${_program}" PARENT_SCOPE) +endfunction() + +# Turn `program` into the concrete `program_name` binary selected for +# `workspace_root`. The variables named by `output` and `proxy_output` receive +# the real path and a proxy boolean in PARENT_SCOPE. Probes or failed resolution +# are fatal; there is no recoverable result. +function(_vortex_resolve_rustup_proxy program program_name workspace_root output proxy_output) + # A rustup proxy normally dispatches from argv[0]. Forcing that identity to + # `rustup` makes the proxy identify its manager, while a real tool still + # reports itself, so detection does not depend on the candidate's filename. + execute_process( + COMMAND "${CMAKE_COMMAND}" -E env RUSTUP_FORCE_ARG0=rustup "${program}" --version + WORKING_DIRECTORY "${workspace_root}" + OUTPUT_VARIABLE _proxy_probe_output + ERROR_VARIABLE _proxy_probe_error + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _proxy_probe_result) + if(NOT _proxy_probe_result EQUAL 0) + message(FATAL_ERROR "Failed to inspect ${program_name} at ${program}: ${_proxy_probe_error}") + endif() + + if(NOT _proxy_probe_output MATCHES "^rustup [0-9]+\\.[0-9]+") + get_filename_component(_resolved_program "${program}" REALPATH) + set(${output} "${_resolved_program}" PARENT_SCOPE) + set(${proxy_output} FALSE PARENT_SCOPE) + return() + endif() + + get_filename_component(_proxy_directory "${program}" DIRECTORY) + find_program(_rustup + NAMES rustup + HINTS "${_proxy_directory}" + NO_DEFAULT_PATH + NO_CACHE) + if(NOT _rustup) + message(FATAL_ERROR "${program} is a rustup proxy, but rustup was not found next to it") + endif() + # rustup selection honors directory overrides and rust-toolchain files. Query + # from the workspace so the resolved binary matches the later Cargo + # invocation. + execute_process( + COMMAND "${_rustup}" which "${program_name}" + WORKING_DIRECTORY "${workspace_root}" + OUTPUT_VARIABLE _resolved_program + ERROR_VARIABLE _rustup_error + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _rustup_result) + if(NOT _rustup_result EQUAL 0 OR + NOT EXISTS "${_resolved_program}" OR + IS_DIRECTORY "${_resolved_program}") + message(FATAL_ERROR + "rustup could not resolve the active ${program_name} toolchain " + "for ${workspace_root}: ${_rustup_error}") + endif() + get_filename_component(_resolved_program "${_resolved_program}" REALPATH) + _vortex_reject_semicolon("Resolved rustup ${program_name} executable" "${_resolved_program}") + message(STATUS "Vortex resolved rustup ${program_name} proxy ${program} to ${_resolved_program}") + set(${output} "${_resolved_program}" PARENT_SCOPE) + set(${proxy_output} TRUE PARENT_SCOPE) +endfunction() + +# Resolve and validate the Cargo/rustc pair used for `workspace_root`. Optional +# inputs are VORTEX_CARGO_EXECUTABLE and VORTEX_RUSTC_EXECUTABLE; defaults are +# cached as user-visible FILEPATH values. VORTEX_RESOLVED_CARGO_EXECUTABLE, +# VORTEX_RESOLVED_CARGO_VERBOSE, VORTEX_RESOLVED_RUSTC_EXECUTABLE, +# VORTEX_RESOLVED_RUSTC_VERBOSE, VORTEX_RESOLVED_RUSTC_RELEASE, and +# VORTEX_RESOLVED_RUSTC_HOST are written to PARENT_SCOPE. Probes emit status; +# execution, parse, minimum-version, or host-coherence failures are fatal. +function(_vortex_find_rust_tools workspace_root) + _vortex_reject_semicolon("VORTEX_CARGO_EXECUTABLE" "${VORTEX_CARGO_EXECUTABLE}") + _vortex_reject_semicolon("VORTEX_RUSTC_EXECUTABLE" "${VORTEX_RUSTC_EXECUTABLE}") + + _vortex_find_rust_program(_cargo_candidate cargo "${VORTEX_CARGO_EXECUTABLE}") + _vortex_find_rust_program(_rustc_candidate rustc "${VORTEX_RUSTC_EXECUTABLE}") + if(NOT VORTEX_CARGO_EXECUTABLE) + set(VORTEX_CARGO_EXECUTABLE "${_cargo_candidate}" CACHE FILEPATH + "Path to the Cargo executable" FORCE) + endif() + if(NOT VORTEX_RUSTC_EXECUTABLE) + set(VORTEX_RUSTC_EXECUTABLE "${_rustc_candidate}" CACHE FILEPATH + "Path to the rustc executable" FORCE) + endif() + + _vortex_resolve_rustup_proxy("${_cargo_candidate}" cargo "${workspace_root}" _cargo _cargo_was_proxy) + _vortex_resolve_rustup_proxy("${_rustc_candidate}" rustc "${workspace_root}" _rustc _rustc_was_proxy) + # Two rustup-managed tools must come from one bin directory; otherwise Cargo + # could silently invoke a compiler from a different selected toolchain. + if(_cargo_was_proxy AND _rustc_was_proxy) + get_filename_component(_cargo_bin_directory "${_cargo}" DIRECTORY) + get_filename_component(_rustc_bin_directory "${_rustc}" DIRECTORY) + if(NOT _cargo_bin_directory STREQUAL _rustc_bin_directory) + message(FATAL_ERROR + "rustup resolved Cargo and rustc from different toolchains: " + "${_cargo_bin_directory} and ${_rustc_bin_directory}") + endif() + endif() + + # Query both concrete binaries: minimum versions establish required behavior, + # while matching host triples reject mixed non-rustup installations as well. + execute_process( + COMMAND "${_cargo}" -vV + WORKING_DIRECTORY "${workspace_root}" + OUTPUT_VARIABLE _cargo_verbose + ERROR_VARIABLE _cargo_error + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _cargo_result) + if(NOT _cargo_result EQUAL 0) + message(FATAL_ERROR "Failed to run Cargo at ${_cargo}: ${_cargo_error}") + endif() + + execute_process( + COMMAND "${_rustc}" -vV + WORKING_DIRECTORY "${workspace_root}" + OUTPUT_VARIABLE _rustc_verbose + ERROR_VARIABLE _rustc_error + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _rustc_result) + if(NOT _rustc_result EQUAL 0) + message(FATAL_ERROR "Failed to run rustc at ${_rustc}: ${_rustc_error}") + endif() + + string(REGEX MATCH "^cargo ([0-9]+\\.[0-9]+\\.[0-9]+)" _cargo_version_match "${_cargo_verbose}") + set(_cargo_semver "${CMAKE_MATCH_1}") + if(_cargo_semver STREQUAL "" OR _cargo_semver VERSION_LESS "1.95.0") + message(FATAL_ERROR "Vortex requires Cargo 1.95.0 or newer; found:\n${_cargo_verbose}") + endif() + + string(REGEX MATCH "host: ([^\r\n]+)" _cargo_host_match "${_cargo_verbose}") + set(_cargo_host "${CMAKE_MATCH_1}") + string(REGEX MATCH "host: ([^\r\n]+)" _rustc_host_match "${_rustc_verbose}") + set(_rustc_host "${CMAKE_MATCH_1}") + string(REGEX MATCH "release: ([^\r\n]+)" _release_match "${_rustc_verbose}") + set(_rustc_release "${CMAKE_MATCH_1}") + if(_cargo_host STREQUAL "" OR _rustc_host STREQUAL "" OR _rustc_release STREQUAL "") + message(FATAL_ERROR "Could not parse Cargo/rustc verbose version output from ${_cargo} and ${_rustc}") + endif() + if(NOT _cargo_host STREQUAL _rustc_host) + message(FATAL_ERROR + "Cargo host ${_cargo_host} does not match rustc host " + "${_rustc_host}; " + "select a compatible VORTEX_CARGO_EXECUTABLE and " + "VORTEX_RUSTC_EXECUTABLE pair") + endif() + string(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" _rustc_semver "${_rustc_release}") + if(_rustc_semver VERSION_LESS "1.95.0") + message(FATAL_ERROR "Vortex requires rustc 1.95.0 or newer; found ${_rustc_release}") + endif() + + string(REGEX MATCH "^[^\r\n]+" _cargo_version_line "${_cargo_verbose}") + message(STATUS "Vortex Cargo: ${_cargo_version_line} (${_cargo})") + message(STATUS "Vortex rustc: ${_rustc_release}, host ${_rustc_host} (${_rustc})") + + set(VORTEX_RESOLVED_CARGO_EXECUTABLE "${_cargo}" PARENT_SCOPE) + set(VORTEX_RESOLVED_CARGO_VERBOSE "${_cargo_verbose}" PARENT_SCOPE) + set(VORTEX_RESOLVED_RUSTC_EXECUTABLE "${_rustc}" PARENT_SCOPE) + set(VORTEX_RESOLVED_RUSTC_VERBOSE "${_rustc_verbose}" PARENT_SCOPE) + set(VORTEX_RESOLVED_RUSTC_RELEASE "${_rustc_release}" PARENT_SCOPE) + set(VORTEX_RESOLVED_RUSTC_HOST "${_rustc_host}" PARENT_SCOPE) +endfunction() + +# Determine the effective Apple deployment target. The variable named by +# `output` receives the explicit CMake value, a compiler-derived default, or an +# empty string off Apple in PARENT_SCOPE. Compiler execution/parsing failures +# are fatal and status messages are emitted for the selected value. +function(_vortex_resolve_apple_deployment_target output) + if(NOT APPLE) + set(${output} "" PARENT_SCOPE) + return() + endif() + + if(CMAKE_OSX_DEPLOYMENT_TARGET) + message(STATUS "Vortex macOS deployment target: ${CMAKE_OSX_DEPLOYMENT_TARGET}") + set(${output} "${CMAKE_OSX_DEPLOYMENT_TARGET}" PARENT_SCOPE) + return() + endif() + + # Probe the compiler's predefined minimum-version macro with CMake's effective + # target and sysroot. This captures the driver's real default instead of + # guessing it from the host OS or Xcode version. + _vortex_optional_value(CMAKE_C_COMPILER_ARG1 _compiler_arg1_value) + _vortex_optional_value(CMAKE_C_COMPILER_TARGET _compiler_target) + set(_compiler_command "${CMAKE_C_COMPILER}") + if(_compiler_arg1_value) + separate_arguments(_compiler_arg1 NATIVE_COMMAND "${_compiler_arg1_value}") + list(APPEND _compiler_command ${_compiler_arg1}) + endif() + if(_compiler_target AND CMAKE_C_COMPILER_ID MATCHES "^(AppleClang|Clang)$") + list(APPEND _compiler_command "--target=${_compiler_target}") + endif() + if(CMAKE_OSX_SYSROOT) + list(APPEND _compiler_command "-isysroot" "${CMAKE_OSX_SYSROOT}") + endif() + list(APPEND _compiler_command -dM -E -x c /dev/null) + execute_process( + COMMAND ${_compiler_command} + OUTPUT_VARIABLE _compiler_defines + ERROR_VARIABLE _compiler_defines_error + RESULT_VARIABLE _compiler_defines_result) + if(NOT _compiler_defines_result EQUAL 0) + message(FATAL_ERROR + "Could not determine Apple deployment target from " + "${CMAKE_C_COMPILER}: ${_compiler_defines_error}. " + "Set CMAKE_OSX_DEPLOYMENT_TARGET explicitly.") + endif() + + string(REGEX MATCH + "__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__[ \t]+([0-9]+)" + _deployment_match "${_compiler_defines}") + set(_deployment_code "${CMAKE_MATCH_1}") + if(_deployment_code STREQUAL "") + message(FATAL_ERROR + "The Apple compiler did not report its default macOS " + "deployment target. Set CMAKE_OSX_DEPLOYMENT_TARGET explicitly.") + endif() + + math(EXPR _deployment_major "${_deployment_code} / 10000") + math(EXPR _deployment_minor "(${_deployment_code} / 100) % 100") + math(EXPR _deployment_patch "${_deployment_code} % 100") + if(_deployment_patch EQUAL 0) + set(_deployment_target "${_deployment_major}.${_deployment_minor}") + else() + set(_deployment_target "${_deployment_major}.${_deployment_minor}.${_deployment_patch}") + endif() + message(STATUS "Vortex macOS deployment target: ${_deployment_target}") + set(${output} "${_deployment_target}" PARENT_SCOPE) +endfunction() + +# Resolve CMAKE_OSX_SYSROOT and its SDK version. The variables named by +# `root_output` and `version_output` receive values in PARENT_SCOPE, or empty +# strings off Apple/without a sysroot. xcrun lookup or invalid SDK metadata is +# fatal; successful resolution emits a status message. +function(_vortex_resolve_apple_sdkroot root_output version_output) + if(NOT APPLE OR NOT CMAKE_OSX_SYSROOT) + set(${root_output} "" PARENT_SCOPE) + set(${version_output} "" PARENT_SCOPE) + return() + endif() + + # CMake accepts either a concrete SDK directory or a name such as `macosx`. + # Preserve and canonicalize an absolute selection; let xcrun resolve a named + # SDK. xcrun remains authoritative for the selected SDK's version in both + # cases. + find_program(_xcrun NAMES xcrun REQUIRED NO_CACHE) + if(IS_ABSOLUTE "${CMAKE_OSX_SYSROOT}") + if(NOT IS_DIRECTORY "${CMAKE_OSX_SYSROOT}") + message(FATAL_ERROR "CMAKE_OSX_SYSROOT is not a directory: ${CMAKE_OSX_SYSROOT}") + endif() + file(REAL_PATH "${CMAKE_OSX_SYSROOT}" _apple_sdkroot) + else() + execute_process( + COMMAND "${_xcrun}" --sdk "${CMAKE_OSX_SYSROOT}" --show-sdk-path + OUTPUT_VARIABLE _apple_sdkroot + ERROR_VARIABLE _xcrun_error + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _xcrun_result) + if(NOT _xcrun_result EQUAL 0 OR NOT IS_DIRECTORY "${_apple_sdkroot}") + message(FATAL_ERROR "Could not resolve CMAKE_OSX_SYSROOT=${CMAKE_OSX_SYSROOT} with xcrun: ${_xcrun_error}") + endif() + file(REAL_PATH "${_apple_sdkroot}" _apple_sdkroot) + endif() + + execute_process( + COMMAND "${_xcrun}" --sdk "${CMAKE_OSX_SYSROOT}" --show-sdk-version + OUTPUT_VARIABLE _apple_sdk_version + ERROR_VARIABLE _xcrun_version_error + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _xcrun_version_result) + if(NOT _xcrun_version_result EQUAL 0 OR _apple_sdk_version STREQUAL "") + message(FATAL_ERROR + "Could not determine the version of Apple SDK " + "${CMAKE_OSX_SYSROOT}: ${_xcrun_version_error}") + endif() + + _vortex_reject_semicolon("Resolved Apple SDK root" "${_apple_sdkroot}") + _vortex_reject_semicolon("Resolved Apple SDK version" "${_apple_sdk_version}") + message(STATUS "Vortex Apple SDK: ${_apple_sdk_version} (${_apple_sdkroot})") + set(${root_output} "${_apple_sdkroot}" PARENT_SCOPE) + set(${version_output} "${_apple_sdk_version}" PARENT_SCOPE) +endfunction() + +# Validate one native compiler driver against `architecture` and `rust_target`. +# `compiler`, optional `arg1`/`configured_target`, `compiler_id`, and diagnostic +# `label` describe the CMake compiler invocation. There are no outputs; an +# unusable driver or architecture/platform/environment mismatch is fatal. +function(_vortex_validate_native_compiler + compiler arg1 configured_target compiler_id architecture rust_target label) + set(_compiler_command "${compiler}") + if(arg1) + separate_arguments(_compiler_arg1 NATIVE_COMMAND "${arg1}") + list(APPEND _compiler_command ${_compiler_arg1}) + endif() + if(configured_target AND compiler_id MATCHES "^(AppleClang|Clang)$") + list(APPEND _compiler_command "--target=${configured_target}") + endif() + # `-dumpmachine` checks the driver's effective default tuple, which compiler + # identity and CMAKE_*_COMPILER_TARGET declarations alone do not guarantee. + list(APPEND _compiler_command -dumpmachine) + execute_process( + COMMAND ${_compiler_command} + OUTPUT_VARIABLE _compiler_target + ERROR_VARIABLE _compiler_target_error + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _compiler_target_result) + if(NOT _compiler_target_result EQUAL 0 OR _compiler_target STREQUAL "") + message(FATAL_ERROR + "Could not determine the target of ${label} compiler ${compiler}: " + "${_compiler_target_error}") + endif() + + _vortex_normalize_arch("${_compiler_target}" _compiler_arch) + if(NOT _compiler_arch STREQUAL architecture) + message(FATAL_ERROR + "${label} compiler target ${_compiler_target} does not match " + "Vortex Rust target ${rust_target}") + endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND + (NOT _compiler_target MATCHES "linux" OR + _compiler_target MATCHES "musl")) + message(FATAL_ERROR + "${label} compiler target ${_compiler_target} is not compatible " + "with GNU Rust target ${rust_target}") + elseif(APPLE AND NOT _compiler_target MATCHES "(apple|darwin)") + message(FATAL_ERROR + "${label} compiler target ${_compiler_target} is not compatible " + "with Rust target ${rust_target}") + endif() + if(configured_target) + _vortex_normalize_arch("${configured_target}" _configured_arch) + if(NOT _configured_arch STREQUAL architecture) + message(FATAL_ERROR + "Configured ${label} compiler target ${configured_target} does " + "not match Vortex Rust target ${rust_target}") + endif() + endif() +endfunction() + +# Select and validate one supported native Rust target. `rustc` and +# `workspace_root` drive Rust probes; CMake platform/compiler state and +# VORTEX_RESOLVED_RUSTC_HOST constrain the result. The variable named by +# `output` receives the triple in PARENT_SCOPE. Cross, universal, unsupported, +# missing-std, compiler-probe, and ABI disagreements are fatal; probes may emit +# status output. +function(_vortex_resolve_native_target rustc workspace_root output) + # Native C dependencies built under Cargo must share the CMake host ABI. + # Reject cross builds explicitly rather than implying support from a plausible + # triple. + if(CMAKE_CROSSCOMPILING) + message(FATAL_ERROR + "Vortex's initial CMake integration supports native builds only; " + "CMAKE_CROSSCOMPILING is true") + endif() + + # Apple can override the generic processor selection, but one Rust static + # archive cannot represent a multi-architecture universal binary. + set(_processor "${CMAKE_SYSTEM_PROCESSOR}") + if(APPLE AND CMAKE_OSX_ARCHITECTURES) + list(LENGTH CMAKE_OSX_ARCHITECTURES _architecture_count) + if(NOT _architecture_count EQUAL 1) + message(FATAL_ERROR + "Vortex does not support Apple universal binaries; set " + "exactly one CMAKE_OSX_ARCHITECTURES value") + endif() + list(GET CMAKE_OSX_ARCHITECTURES 0 _processor) + endif() + _vortex_normalize_arch("${_processor}" _architecture) + + # Keep this mapping closed: every accepted triple has a separately allowlisted + # native static-link manifest rather than an inferred architecture spelling. + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + if(_architecture STREQUAL "x86_64") + set(_expected_target "x86_64-unknown-linux-gnu") + elseif(_architecture STREQUAL "aarch64") + set(_expected_target "aarch64-unknown-linux-gnu") + else() + message(FATAL_ERROR + "Vortex supports native Linux x86_64 and aarch64 only; CMake selected ${_processor}") + endif() + + elseif(APPLE) + if(_architecture STREQUAL "x86_64") + set(_expected_target "x86_64-apple-darwin") + elseif(_architecture STREQUAL "aarch64") + set(_expected_target "aarch64-apple-darwin") + else() + message(FATAL_ERROR + "Vortex standalone macOS builds support x86_64 and arm64 only; " + "CMake selected ${_processor}") + endif() + message(STATUS + "Vortex macOS support is for standalone development only; " + "it is not a supported cuDF integration target") + else() + message(FATAL_ERROR + "Vortex's initial CMake integration supports native Linux and " + "standalone macOS; CMake selected ${CMAKE_SYSTEM_NAME}") + endif() + + set(_rust_target "${_expected_target}") + if(NOT VORTEX_RESOLVED_RUSTC_HOST STREQUAL _rust_target) + message(FATAL_ERROR + "Vortex supports native Rust builds only: rustc host is " + "${VORTEX_RESOLVED_RUSTC_HOST}, but CMake requires " + "${_rust_target}") + endif() + + # Confirm that the selected toolchain contains the target standard library. + execute_process( + COMMAND "${rustc}" --print target-libdir --target "${_rust_target}" + WORKING_DIRECTORY "${workspace_root}" + OUTPUT_VARIABLE _target_libdir + ERROR_VARIABLE _target_libdir_error + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _target_libdir_result) + if(NOT _target_libdir_result EQUAL 0 OR NOT IS_DIRECTORY "${_target_libdir}") + message(FATAL_ERROR + "The Rust standard library for ${_rust_target} is unavailable: " + "${_target_libdir_error}. Install or provide that target before " + "configuring Vortex.") + endif() + file(GLOB _target_std_rlibs "${_target_libdir}/libstd-*.rlib") + if(NOT _target_std_rlibs) + message(FATAL_ERROR + "The Rust target directory ${_target_libdir} does not contain " + "libstd for ${_rust_target}. Install or provide that target before " + "configuring Vortex.") + endif() + + # Cargo build scripts may invoke either native driver. Validate their + # effective target tuples against the architecture selected above. + _vortex_optional_value(CMAKE_C_COMPILER_ARG1 _c_compiler_arg1) + _vortex_optional_value(CMAKE_C_COMPILER_TARGET _c_compiler_target) + _vortex_validate_native_compiler( + "${CMAKE_C_COMPILER}" + "${_c_compiler_arg1}" + "${_c_compiler_target}" + "${CMAKE_C_COMPILER_ID}" + "${_architecture}" + "${_rust_target}" + "C") + _vortex_optional_value(CMAKE_CXX_COMPILER _cxx_compiler) + if(_cxx_compiler) + _vortex_optional_value(CMAKE_CXX_COMPILER_ARG1 _cxx_compiler_arg1) + _vortex_optional_value(CMAKE_CXX_COMPILER_TARGET _cxx_compiler_target) + _vortex_validate_native_compiler( + "${_cxx_compiler}" + "${_cxx_compiler_arg1}" + "${_cxx_compiler_target}" + "${CMAKE_CXX_COMPILER_ID}" + "${_architecture}" + "${_rust_target}" + "C++") + endif() + + set(${output} "${_rust_target}" PARENT_SCOPE) +endfunction() diff --git a/lang/cpp/cmake/VortexStaticLink.cmake b/lang/cpp/cmake/VortexStaticLink.cmake new file mode 100644 index 00000000000..70a1259b7f2 --- /dev/null +++ b/lang/cpp/cmake/VortexStaticLink.cmake @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright the Vortex contributors + +# Native link requirements for the source-built Rust static archive. Rust +# archives leave platform-library references for the final linker, so the CMake +# target must publish the validated manifest explicitly. Export policy for an +# enclosing shared library remains the parent's responsibility. + +include_guard(GLOBAL) + +# Map a supported Rust target to its native link manifest. The platform and +# error outputs are written in PARENT_SCOPE; unknown targets return an error +# rather than guessing from the host. +function(_vortex_static_link_platform rust_target platform_output error_output) + if(rust_target STREQUAL "x86_64-unknown-linux-gnu" OR + rust_target STREQUAL "aarch64-unknown-linux-gnu") + set(_platform "linux") + elseif(rust_target STREQUAL "x86_64-apple-darwin" OR + rust_target STREQUAL "aarch64-apple-darwin") + set(_platform "macos") + else() + set(${platform_output} "" PARENT_SCOPE) + string(CONCAT _platform_error + "Vortex has no validated native static-link manifest for Rust target " + "${rust_target}") + set(${error_output} "${_platform_error}" PARENT_SCOPE) + return() + endif() + + set(${platform_output} "${_platform}" PARENT_SCOPE) + set(${error_output} "" PARENT_SCOPE) +endfunction() + +# Attach the native requirements for `rust_target` to the newly created static +# library `target`. Missing targets, unsupported triples, or unavailable system +# libraries are fatal. +function(_vortex_configure_static_link target rust_target) + if(NOT TARGET "${target}") + message(FATAL_ERROR "Expected CMake target does not exist: ${target}") + endif() + + _vortex_static_link_platform("${rust_target}" _platform _platform_error) + if(_platform_error) + message(FATAL_ERROR "${_platform_error}") + endif() + + if(_platform STREQUAL "linux") + set(THREADS_PREFER_PTHREAD_FLAG TRUE) + if(NOT TARGET Threads::Threads) + find_package(Threads REQUIRED) + endif() + + set(_native_libraries gcc_s util rt Threads::Threads m) + if(CMAKE_DL_LIBS) + list(APPEND _native_libraries "${CMAKE_DL_LIBS}") + endif() + list(APPEND _native_libraries c) + target_link_libraries("${target}" INTERFACE ${_native_libraries}) + else() + # Apple drivers add libSystem implicitly, and the C++ driver also adds + # libc++. A C final link still needs libc++, so add it only for C links. + find_library(_vortex_core_foundation CoreFoundation REQUIRED NO_CACHE) + target_link_libraries("${target}" INTERFACE + "$<$:c++>" + iconv + "${_vortex_core_foundation}") + endif() +endfunction() diff --git a/lang/cpp/examples/CMakeLists.txt b/lang/cpp/examples/CMakeLists.txt index 2733b745b96..f4ad8eb2fa2 100644 --- a/lang/cpp/examples/CMakeLists.txt +++ b/lang/cpp/examples/CMakeLists.txt @@ -2,6 +2,6 @@ # SPDX-FileCopyrightText: Copyright the Vortex contributors foreach(name reader writer dtype scan scan_to_arrow) - add_executable(${name} ${name}.cpp) - target_link_libraries(${name} PRIVATE vortex_cxx_shared nanoarrow_shared) + add_executable(${name} ${name}.cpp) + target_link_libraries(${name} PRIVATE Vortex::cpp_static nanoarrow_shared) endforeach() diff --git a/lang/cpp/gcov-report.sh b/lang/cpp/gcov-report.sh index 6c1365df57a..3c834c58b2b 100755 --- a/lang/cpp/gcov-report.sh +++ b/lang/cpp/gcov-report.sh @@ -4,11 +4,12 @@ # SPDX-FileCopyrightText: Copyright the Vortex contributors set -eu -cmake -Bbuild -DBUILD_TESTS=1 -DCMAKE_CXX_FLAGS='-fprofile-arcs -ftest-coverage' +cmake -Bbuild -DVORTEX_BUILD_TESTING=ON \ + -DCMAKE_CXX_FLAGS='-fprofile-arcs -ftest-coverage' cmake --build build -j ctest --test-dir build --output-on-failure -geninfo build/CMakeFiles/vortex_cxx_shared.dir/ \ +geninfo build/CMakeFiles/vortex_cxx.dir/ \ build/tests/CMakeFiles/vortex_cxx_test.dir/ \ --rc geninfo_unexecuted_blocks=1 \ --exclude /usr --exclude build/_deps --exclude tests \ diff --git a/lang/cpp/include/vortex/common.hpp b/lang/cpp/include/vortex/common.hpp index cc8559e96c6..51f76190f0a 100644 --- a/lang/cpp/include/vortex/common.hpp +++ b/lang/cpp/include/vortex/common.hpp @@ -17,7 +17,6 @@ struct float16_t { constexpr friend bool operator==(float16_t, float16_t) = default; // NOLINTNEXTLINE constexpr operator float() const { - float result; const uint32_t sign = (bits >> 15) & 1; const uint32_t exponent = (bits >> 10) & 0x1F; const uint32_t mantissa = bits & 0x3FF; diff --git a/lang/cpp/tests/CMakeLists.txt b/lang/cpp/tests/CMakeLists.txt index 16a6e0290b9..cbb13a53b51 100644 --- a/lang/cpp/tests/CMakeLists.txt +++ b/lang/cpp/tests/CMakeLists.txt @@ -16,7 +16,7 @@ file(GLOB TEST_FILES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") add_executable(vortex_cxx_test ${TEST_FILES}) target_include_directories(vortex_cxx_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(vortex_cxx_test PRIVATE - vortex_cxx_shared + Vortex::cpp_static Catch2::Catch2WithMain magic_enum::magic_enum nanoarrow_shared) From 3ad58e1bb97430d94040a8a53f9369fdf30b8e2f Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Thu, 3 Sep 2026 17:43:37 +0100 Subject: [PATCH 02/24] Simplify the CMake Cargo integration Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- lang/cpp/CMakeLists.txt | 23 +- lang/cpp/README.md | 117 ++-- lang/cpp/cmake/BuildVortexCargo.cmake | 268 ++++----- lang/cpp/cmake/VortexCargo.cmake | 680 +++++++++-------------- lang/cpp/cmake/VortexHelpers.cmake | 69 +-- lang/cpp/cmake/VortexOptions.cmake | 38 -- lang/cpp/cmake/VortexRustToolchain.cmake | 452 +++++---------- lang/cpp/cmake/VortexStaticLink.cmake | 70 +-- lang/cpp/gcov-report.sh | 6 +- lang/cpp/tests/CMakeLists.txt | 4 +- 10 files changed, 674 insertions(+), 1053 deletions(-) delete mode 100644 lang/cpp/cmake/VortexOptions.cmake diff --git a/lang/cpp/CMakeLists.txt b/lang/cpp/CMakeLists.txt index 002f380e261..1eaa3e73896 100644 --- a/lang/cpp/CMakeLists.txt +++ b/lang/cpp/CMakeLists.txt @@ -32,8 +32,11 @@ endif() set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_vortex_workspace_root}/rust-toolchain.toml") -include("${_vortex_cmake_dir}/VortexOptions.cmake") -include("${_vortex_cmake_dir}/VortexCargo.cmake") +option(VORTEX_BUILD_TESTING "Build Vortex C++ tests" OFF) +option(VORTEX_BUILD_EXAMPLES "Build Vortex C++ examples" OFF) +option(VORTEX_ENABLE_CUDA "Build the CUDA-enabled Vortex FFI" OFF) +set(VORTEX_SANITIZER "" CACHE STRING "Instrument Vortex and its consumers with asan or tsan") +set_property(CACHE VORTEX_SANITIZER PROPERTY STRINGS "" asan tsan) # Standalone builds treat warnings as errors; embedded builds do not impose that # policy on a parent. @@ -47,7 +50,7 @@ option(VORTEX_WARNINGS_AS_ERRORS "${_vortex_warnings_as_errors_default}") # Build the private Rust FFI dependency before defining the public C++ target. -_vortex_add_ffi_static() +include("${_vortex_cmake_dir}/VortexCargo.cmake") set(_vortex_cpp_sources src/array.cpp @@ -66,16 +69,9 @@ set_target_properties(vortex_cxx PROPERTIES target_compile_features(vortex_cxx PUBLIC cxx_std_20) # Warning policy is private to Vortex-owned sources so embedding this target # never injects warning flags into consumers. -if(MSVC) - target_compile_options(vortex_cxx PRIVATE /W4) - if(VORTEX_WARNINGS_AS_ERRORS) - target_compile_options(vortex_cxx PRIVATE /WX) - endif() -else() - target_compile_options(vortex_cxx PRIVATE -Wall -Wextra -Wpedantic) - if(VORTEX_WARNINGS_AS_ERRORS) - target_compile_options(vortex_cxx PRIVATE -Werror) - endif() +target_compile_options(vortex_cxx PRIVATE -Wall -Wextra -Wpedantic) +if(VORTEX_WARNINGS_AS_ERRORS) + target_compile_options(vortex_cxx PRIVATE -Werror) endif() target_include_directories(vortex_cxx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(vortex_cxx PUBLIC vortex_ffi_static) @@ -104,6 +100,7 @@ if(VORTEX_BUILD_TESTING) GIT_TAG v3.8.1) FetchContent_MakeAvailable(Catch) include(Catch) + target_compile_features(Catch2 PRIVATE cxx_std_17) target_compile_definitions(Catch2 PRIVATE CATCH_CONFIG_NO_POSIX_SIGNALS) if(_vortex_cpp_standalone) diff --git a/lang/cpp/README.md b/lang/cpp/README.md index 70012926060..47d6aa0965f 100644 --- a/lang/cpp/README.md +++ b/lang/cpp/README.md @@ -5,8 +5,8 @@ Vortex provides a C++20 API for reading and writing Vortex files. See the ## Quick start -Build from the repository root with CMake 3.28 or newer, Ninja, native C/C++ compilers, and the -workspace Rust toolchain: +Build from the repository root with CMake 3.28 or newer, Ninja, native C/C++ compilers, and +Cargo and rustc available to CMake's program search: ```sh cmake -S lang/cpp -B build/cpp -G Ninja -DCMAKE_BUILD_TYPE=Release @@ -24,8 +24,10 @@ add_subdirectory(path/to/vortex/lang/cpp vortex-cpp) target_link_libraries(my_target PRIVATE Vortex::cpp_static) ``` -`lang/cpp/CMakeLists.txt` is the only supported entry point. It provides one public target: -`Vortex::cpp_static`, a static PIC C++20 library with all required transitive dependencies. +`lang/cpp/CMakeLists.txt` is the only supported entry point. Its only supported consumer target is +`Vortex::cpp_static`, an alias of the `vortex_cxx` build target. It transitively links the separate +Rust FFI archive and required native libraries; consume the target rather than copying +`libvortex_cxx.a` alone. The integration is source-only: it does not install Vortex or provide `find_package(Vortex)`. It enables C and C++ when needed, but keeps compile and link policy target-scoped and does not replace @@ -36,7 +38,8 @@ the parent's build type, language standards, compiler flags, linker flags, or `B Vortex can be linked privately into a shared library such as `libcudf`. Keep Vortex calls in private C++ implementation files and preserve the parent's symbol-export policy. The parent must prevent `vx_*` and other implementation symbols from becoming part of its public ABI, for example with an -ELF version script and `--exclude-libs,ALL` or a macOS exported-symbol allowlist. +ELF version script and `--exclude-libs,ALL` or a macOS exported-symbol allowlist. CUDA-enabled builds +also have runtime-library deployment requirements described below. ## Supported configurations @@ -44,56 +47,69 @@ The build supports: - native GNU/Linux on x86_64 and aarch64; - native macOS on x86_64 and arm64 for standalone development; -- an empty `CMAKE_BUILD_TYPE` (Rust development profile), `Debug`, `Release`, and `RelWithDebInfo`; and +- a single-config generator, with `CMAKE_BUILD_TYPE` set to `Debug`, `Release`, or + `RelWithDebInfo`; and - Cargo and rustc 1.95.0 or newer, with the selected target's standard library installed. -Cargo, rustc, and the CMake-selected C/C++ compilers must target the same native platform and -architecture. The workspace toolchain is used by default. +CMake discovers Cargo and rustc with `find_program`. Rustup proxies are resolved from the Vortex +workspace, so they honor its `rust-toolchain.toml` and `RUSTUP_TOOLCHAIN`; concrete non-rustup +binaries are used directly. Cargo and rustc must report the same host. The selected C and C++ +compilers must support `-dumpmachine` and target the same native platform and architecture. Cross-compilation, Apple universal binaries, Windows, musl, multi-config generators, and shared -Vortex targets are not supported. macOS is not a supported cuDF integration target. +Vortex targets are not supported. Ninja is recommended. macOS is not a supported cuDF integration +target. ## Configuration -Set options before `add_subdirectory`, or pass them with `-D` for a standalone build. +Set these public Vortex-specific options before `add_subdirectory`, or pass them with `-D` for a +standalone build: -### Build options - -- `VORTEX_BUILD_TESTING=ON` builds the C++23 tests. Embedded builds require the parent to enable - CTest. Default: `OFF`. +- `VORTEX_BUILD_TESTING=ON` builds the `vortex_cxx_test` C++23 test target. Embedded builds require + the parent to enable CTest. Default: `OFF`. - `VORTEX_BUILD_EXAMPLES=ON` builds the examples. Default: `OFF`. -- `VORTEX_WARNINGS_AS_ERRORS` applies only to Vortex-owned C++ sources. Default: `ON` standalone and - `OFF` when embedded. -- `VORTEX_SANITIZER=asan|tsan` enables sanitizer instrumentation. Use an explicit Debug build and a - nightly Rust toolchain with `rust-src`. Flags propagate to targets that link Vortex, and `mimalloc` - cannot be used with sanitizers. - -### Cargo options - -- `VORTEX_CARGO_EXECUTABLE` and `VORTEX_RUSTC_EXECUTABLE` override tool discovery. -- `VORTEX_CARGO_TARGET_DIR` selects the Cargo cache root. -- `VORTEX_CARGO_JOBS` sets Cargo's job limit. -- `VORTEX_CARGO_OFFLINE=ON` passes `--offline` to Cargo; the Cargo cache must already be complete. -- `VORTEX_CARGO_FEATURES=mimalloc` enables the only supported optional FFI feature. -- `VORTEX_RUSTFLAGS` adds rustc arguments. `target-cpu=native` is rejected because parent artifacts - may run on a different CPU. - -CMake resolves and validates the toolchain during configure. The selected Cargo and rustc binaries -remain fixed until the project is reconfigured, and CMake forwards its -native toolchain settings to -Cargo build scripts. - -Cargo artifacts are isolated by a fingerprint of the toolchain and build settings. CMake clean -removes the staged Vortex archive but preserves the Cargo cache. Ambient Rust flag variables are -ignored; use `VORTEX_RUSTFLAGS` for intentional additions. - -Stable builds use the checked-in `vortex-ffi/cinclude/vortex.h`. A non-sanitizer nightly build may -regenerate that header in the source checkout and run `clang-format`; sanitizer builds skip header -generation. - -Cargo may download locked Rust dependencies during the build. Tests and examples may download their -CMake `FetchContent` dependencies during configure; `VORTEX_CARGO_OFFLINE` does not affect those -downloads. +- `VORTEX_ENABLE_CUDA=ON` selects the Linux-only `vortex-cuda-ffi` archive, adds `vortex_cuda.h` to + the existing `Vortex::cpp_static` target, and requires `find_package(CUDAToolkit)`. Set + `CUDAToolkit_ROOT` when CMake cannot find the toolkit. Default: `OFF`. +- `VORTEX_WARNINGS_AS_ERRORS` promotes warnings only while compiling `vortex_cxx`; it does not affect + tests, examples, consumers, Rust, or CUDA compilation. Default: `ON` standalone and `OFF` when + embedded. +- `VORTEX_SANITIZER=asan|tsan` requires `Debug`, nightly Rust, and `rust-src`. ASan also enables + undefined-behavior and leak checks for native code and address/leak instrumentation for Rust; + TSan enables thread instrumentation. Flags propagate to targets linking Vortex, but CUDA device + code, the CUB helper, and nvCOMP are not sanitizer-instrumented. Default: empty. + +The selected Cargo and rustc binaries are fixed until CMake is reconfigured. Cargo runs with the +lockfile, the selected native target and profile, and the selected FFI package's default features +disabled. Optional features such as `mimalloc` are not enabled. The integration supplies its complete +Rust flag sequence, overriding Rust flags from the environment and Cargo configuration. + +Cargo's target cache is stored under `/cargo-target`. The Cargo target runs whenever +Vortex is built, while Cargo decides whether recompilation is needed. The standard CMake clean target +removes both CMake outputs and this Cargo cache: + +```sh +cmake --build build/cpp --target clean +``` + +Public headers are consumed from the source checkout. `vortex.h` and `vortex_cuda.h` are checked in. +A non-sanitizer nightly build may regenerate `vortex.h` with cbindgen and attempt to format it with +`clang-format`; stable and sanitizer builds leave it unchanged. CUDA builds also generate kernel +sources and PTX in the source checkout, so nightly and CUDA builds require it to be writable. + +Cargo may download locked dependencies during the build. CUDA builds additionally require libclang +for bindgen and may download the pinned CUDA 12 nvCOMP SDK directly from NVIDIA. Tests and examples +may download Nanoarrow, Catch2, and magic_enum during CMake configure. + +### CUDA deployment + +CUDA builds continue to use `Vortex::cpp_static`; no separate CUDA CMake target is created. Current +CUDA build scripts invoke NVCC with `-arch=native`, so `CMAKE_CUDA_ARCHITECTURES` has no effect. +Generated PTX is embedded in the Rust archive, but CMake does not stage `libvortex_cub.so` or the +downloaded `libnvcomp.so`. The CUB helper must remain at its original Cargo build path or be placed +next to the host executable; nvCOMP is loaded from its original Cargo build path. CUDA operations +also require a compatible NVIDIA driver and accessible GPU. Consequently, CUDA-enabled output is +not currently a self-contained relocatable deployment artifact. ## Development @@ -116,7 +132,8 @@ cmake -S lang/cpp -B build/cpp-examples -G Ninja \ cmake --build build/cpp-examples --parallel ``` -The executables are written to `build/cpp-examples/examples/`. +The `reader`, `writer`, `dtype`, `scan`, and `scan_to_arrow` executables are written to +`build/cpp-examples/examples/`. ### Sanitizers @@ -134,10 +151,12 @@ ctest --test-dir build/cpp-asan --output-on-failure ### Coverage -Run the coverage helper from `lang/cpp`. It requires LCOV's `geninfo`, plus `genhtml` for HTML output: +Run one of the following from `lang/cpp`. The helper reports C++ coverage only and requires a +compatible gcov/LCOV toolchain, plus `genhtml` for HTML output: ```sh cd lang/cpp -./gcov-report.sh +./gcov-report.sh # Writes coverage.info +# Or: ./gcov-report.sh html # Also writes coverage/ ``` diff --git a/lang/cpp/cmake/BuildVortexCargo.cmake b/lang/cpp/cmake/BuildVortexCargo.cmake index 79395d6334f..ae1ad684d50 100644 --- a/lang/cpp/cmake/BuildVortexCargo.cmake +++ b/lang/cpp/cmake/BuildVortexCargo.cmake @@ -3,155 +3,161 @@ # Build-time `cmake -P` driver for the custom target defined by # VortexCargo.cmake. Configuration arrives through -D variables; this script -# defines no CMake targets. It recreates the validated Cargo environment and -# stages the resulting archive at the stable path consumed by CMake. Missing -# inputs, Cargo failure, or a missing/staging-failed archive are fatal. +# recreates the validated Cargo environment, builds the selected FFI crate, and +# stages its static archive at the stable path consumed by CMake. cmake_minimum_required(VERSION 3.28) -# These non-empty inputs must cross from configure time as -D definitions. Fail -# before invoking Cargo rather than falling back to ambient build settings. -foreach(_required IN ITEMS - VORTEX_CARGO_EXECUTABLE - VORTEX_RUSTC_EXECUTABLE - VORTEX_RUST_TARGET - VORTEX_WORKSPACE_ROOT - VORTEX_FFI_MANIFEST - VORTEX_CARGO_CONFIG_FILE - VORTEX_CARGO_TARGET_DIR - VORTEX_CARGO_PROFILE - VORTEX_CARGO_FFI_ARCHIVE - VORTEX_CMAKE_FFI_ARCHIVE - VORTEX_TARGET_ENV_KEY_UPPER - VORTEX_TARGET_ENV_KEY_LOWER - VORTEX_RUSTFLAGS_FILE - VORTEX_CFLAGS_FILE - VORTEX_CXXFLAGS_FILE) - if(NOT DEFINED ${_required} OR "${${_required}}" STREQUAL "") - message(FATAL_ERROR "BuildVortexCargo.cmake requires ${_required}") - endif() -endforeach() +# Require every configure-time input consumed by this build script. +function(_vortex_require_build_inputs) + foreach(_required IN ITEMS + VORTEX_CARGO_EXECUTABLE + VORTEX_RUSTC_EXECUTABLE + VORTEX_RUST_TARGET + VORTEX_CARGO_TARGET_DIR + VORTEX_CARGO_PROFILE + VORTEX_FFI_PACKAGE + VORTEX_CARGO_FFI_ARCHIVE + VORTEX_CMAKE_FFI_ARCHIVE + VORTEX_CARGO_SUPPORT_DIR + VORTEX_C_COMPILER + VORTEX_CXX_COMPILER + VORTEX_AR + VORTEX_RANLIB) + if(NOT DEFINED ${_required} OR "${${_required}}" STREQUAL "") + message(FATAL_ERROR "BuildVortexCargo.cmake requires ${_required}") + endif() + endforeach() +endfunction() -file(MAKE_DIRECTORY "${VORTEX_CARGO_TARGET_DIR}") +# Derive Cargo's uppercase and the Rust `cc` crate's lowercase environment keys +# for the selected target. +function(_vortex_cargo_target_env_keys upper_output lower_output) + string(REPLACE "-" "_" _target_key "${VORTEX_RUST_TARGET}") + string(TOUPPER "${_target_key}" _target_key_upper) + string(TOLOWER "${_target_key}" _target_key_lower) + set(${upper_output} "${_target_key_upper}" PARENT_SCOPE) + set(${lower_output} "${_target_key_lower}" PARENT_SCOPE) +endfunction() -# Build commands are CMake lists so every option and value remains a distinct -# argv element. The calling custom target uses VERBATIM for its outer cmake -P -# invocation; execute_process consumes this inner list without a shell string. -set(_cargo_command "${VORTEX_CARGO_EXECUTABLE}" --config "${VORTEX_CARGO_CONFIG_FILE}") -if(VORTEX_CARGO_OFFLINE) - list(APPEND _cargo_command --offline) -endif() -list(APPEND _cargo_command - rustc - --locked - --package vortex-ffi - --lib - --crate-type=staticlib - --manifest-path "${VORTEX_FFI_MANIFEST}" - --target "${VORTEX_RUST_TARGET}" - --target-dir "${VORTEX_CARGO_TARGET_DIR}" - --profile "${VORTEX_CARGO_PROFILE}") -if(DEFINED VORTEX_CARGO_JOBS AND NOT VORTEX_CARGO_JOBS STREQUAL "") - list(APPEND _cargo_command --jobs "${VORTEX_CARGO_JOBS}") -endif() -if(VORTEX_CARGO_NO_DEFAULT_FEATURES) - list(APPEND _cargo_command --no-default-features) -endif() -if(DEFINED VORTEX_CARGO_FEATURES AND NOT VORTEX_CARGO_FEATURES STREQUAL "") - list(APPEND _cargo_command --features "${VORTEX_CARGO_FEATURES}") -endif() -if(VORTEX_CARGO_BUILD_STD) - list(APPEND _cargo_command -Zbuild-std) -endif() +# Assemble the Cargo command that builds the selected FFI package as a static +# library using the workspace's Cargo profile policy. +function(_vortex_make_cargo_command output) + set(_command + "${VORTEX_CARGO_EXECUTABLE}" + rustc + --locked + --package "${VORTEX_FFI_PACKAGE}" + --lib + --crate-type=staticlib + --no-default-features + --target "${VORTEX_RUST_TARGET}" + --target-dir "${VORTEX_CARGO_TARGET_DIR}" + --profile "${VORTEX_CARGO_PROFILE}") + if(VORTEX_CARGO_BUILD_STD) + list(APPEND _command -Zbuild-std) + endif() + set(${output} "${_command}" PARENT_SCOPE) +endfunction() -# Flag payloads use files because shell-quoted native flags and ASCII-31 encoded -# Rust flags cannot safely traverse another -D and CMake-list expansion. -foreach(_flags_file IN ITEMS VORTEX_RUSTFLAGS_FILE VORTEX_CFLAGS_FILE VORTEX_CXXFLAGS_FILE) - if(NOT EXISTS "${${_flags_file}}") - message(FATAL_ERROR "BuildVortexCargo.cmake cannot read ${_flags_file}: ${${_flags_file}}") +# Prepend the selected Rust and CUDA tool directories to the ambient PATH. +function(_vortex_build_tool_path output) + get_filename_component(_rust_bin_dir "${VORTEX_RUSTC_EXECUTABLE}" DIRECTORY) + set(_tool_bin_dirs "${_rust_bin_dir}") + if(VORTEX_NVCC_EXECUTABLE) + get_filename_component(_nvcc_bin_dir "${VORTEX_NVCC_EXECUTABLE}" DIRECTORY) + list(APPEND _tool_bin_dirs "${_nvcc_bin_dir}") endif() -endforeach() -file(READ "${VORTEX_RUSTFLAGS_FILE}" _encoded_rustflags) -file(READ "${VORTEX_CFLAGS_FILE}" _native_c_flags) -file(READ "${VORTEX_CXXFLAGS_FILE}" _native_cxx_flags) + list(JOIN _tool_bin_dirs ":" _path) -# Cargo and its subprocesses must use the concrete rustc selected at configure -# time, not a rustup proxy or another PATH entry. Put its bin directory first -# and set both RUSTC and CARGO_BUILD_RUSTC below. -get_filename_component(_rust_toolchain_bin_dir "${VORTEX_RUSTC_EXECUTABLE}" DIRECTORY) -if(DEFINED ENV{PATH} AND NOT "$ENV{PATH}" STREQUAL "") - if("$ENV{PATH}" MATCHES ";") - message(FATAL_ERROR "The CMake-owned Cargo build does not support semicolons in PATH") + if(DEFINED ENV{PATH} AND NOT "$ENV{PATH}" STREQUAL "") + if("$ENV{PATH}" MATCHES ";") + message(FATAL_ERROR + "The CMake-owned Cargo build does not support semicolons in PATH") + endif() + string(APPEND _path ":$ENV{PATH}") endif() - set(_cargo_path "${_rust_toolchain_bin_dir}:$ENV{PATH}") -else() - set(_cargo_path "${_rust_toolchain_bin_dir}") -endif() + set(${output} "${_path}" PARENT_SCOPE) +endfunction() -# Target-qualified linker, CC/CXX, AR, RANLIB, and flag variables force Cargo -# build scripts and native dependencies through CMake's selected toolchain while -# leaving unrelated host-target settings untouched. -set(_environment - "PATH=${_cargo_path}" - "RUSTC=${VORTEX_RUSTC_EXECUTABLE}" - "CARGO_BUILD_RUSTC=${VORTEX_RUSTC_EXECUTABLE}" - "CC_SHELL_ESCAPED_FLAGS=1" - "CARGO_TARGET_${VORTEX_TARGET_ENV_KEY_UPPER}_LINKER=${VORTEX_C_LINKER}" - "CC_${VORTEX_TARGET_ENV_KEY_LOWER}=${VORTEX_C_COMPILER}" - "CXX_${VORTEX_TARGET_ENV_KEY_LOWER}=${VORTEX_CXX_COMPILER}") -if(DEFINED VORTEX_AR AND NOT VORTEX_AR STREQUAL "") - list(APPEND _environment "AR_${VORTEX_TARGET_ENV_KEY_LOWER}=${VORTEX_AR}") -endif() -if(DEFINED VORTEX_RANLIB AND NOT VORTEX_RANLIB STREQUAL "") - list(APPEND _environment "RANLIB_${VORTEX_TARGET_ENV_KEY_LOWER}=${VORTEX_RANLIB}") -endif() -if(_native_c_flags) - list(APPEND _environment "CFLAGS_${VORTEX_TARGET_ENV_KEY_LOWER}=${_native_c_flags}") -endif() -if(_native_cxx_flags) - list(APPEND _environment "CXXFLAGS_${VORTEX_TARGET_ENV_KEY_LOWER}=${_native_cxx_flags}") -endif() -if(DEFINED VORTEX_APPLE_DEPLOYMENT_TARGET AND NOT VORTEX_APPLE_DEPLOYMENT_TARGET STREQUAL "") - list(APPEND _environment "MACOSX_DEPLOYMENT_TARGET=${VORTEX_APPLE_DEPLOYMENT_TARGET}") -endif() -if(DEFINED VORTEX_APPLE_SDKROOT AND NOT VORTEX_APPLE_SDKROOT STREQUAL "") - list(APPEND _environment "SDKROOT=${VORTEX_APPLE_SDKROOT}") -endif() +# Assemble the target-specific Cargo environment from the selected tools and +# generated Rust, C, and C++ flag files. On macOS, make the toolchain's LLVM +# library available to llvm-tools binaries whose packaged rpath is insufficient. +function(_vortex_make_cargo_environment support_dir target_key_upper target_key_lower output) + file(READ "${support_dir}/rustflags" _rustflags) + file(READ "${support_dir}/cflags" _cflags) + file(READ "${support_dir}/cxxflags" _cxxflags) + _vortex_build_tool_path(_cargo_path) -# Use one validated, fingerprinted flag sequence at Cargo's highest-precedence -# environment layer. The explicit unsets remove ambient global and target Rust -# flags before installing that sequence, preventing hidden target-CPU or PIC -# changes from bypassing configure-time validation. -list(APPEND _environment "CARGO_ENCODED_RUSTFLAGS=${_encoded_rustflags}") -set(_command - "${CMAKE_COMMAND}" -E env - --unset=RUSTFLAGS - --unset=CARGO_ENCODED_RUSTFLAGS - "--unset=CARGO_TARGET_${VORTEX_TARGET_ENV_KEY_UPPER}_RUSTFLAGS" - ${_environment} - ${_cargo_command}) -# The caller's always-out-of-date custom target runs Cargo on every build. -# Cargo's own dependency cache decides whether compilation work is necessary. + set(_environment + "PATH=${_cargo_path}" + "RUSTC=${VORTEX_RUSTC_EXECUTABLE}" + "CC_SHELL_ESCAPED_FLAGS=1" + "CARGO_TARGET_${target_key_upper}_LINKER=${VORTEX_C_COMPILER}" + "CC_${target_key_lower}=${VORTEX_C_COMPILER}" + "CXX_${target_key_lower}=${VORTEX_CXX_COMPILER}" + "AR_${target_key_lower}=${VORTEX_AR}" + "RANLIB_${target_key_lower}=${VORTEX_RANLIB}" + "CFLAGS_${target_key_lower}=${_cflags}" + "CXXFLAGS_${target_key_lower}=${_cxxflags}" + "CARGO_ENCODED_RUSTFLAGS=${_rustflags}") + if(VORTEX_CUDA_ROOT) + list(APPEND _environment "CUDA_PATH=${VORTEX_CUDA_ROOT}") + endif() + if(VORTEX_APPLE_DEPLOYMENT_TARGET) + list(APPEND _environment + "MACOSX_DEPLOYMENT_TARGET=${VORTEX_APPLE_DEPLOYMENT_TARGET}") + endif() + if(VORTEX_APPLE_SDKROOT) + list(APPEND _environment "SDKROOT=${VORTEX_APPLE_SDKROOT}") + endif() + if(VORTEX_APPLE_DEPLOYMENT_TARGET) + get_filename_component(_rust_bin_dir "${VORTEX_RUSTC_EXECUTABLE}" DIRECTORY) + get_filename_component(_rust_toolchain_dir "${_rust_bin_dir}" DIRECTORY) + set(_rust_toolchain_lib_dir "${_rust_toolchain_dir}/lib") + if(EXISTS "${_rust_toolchain_lib_dir}/libLLVM.dylib") + set(_dyld_fallback_library_path "${_rust_toolchain_lib_dir}") + if(DEFINED ENV{DYLD_FALLBACK_LIBRARY_PATH} AND NOT "$ENV{DYLD_FALLBACK_LIBRARY_PATH}" STREQUAL "") + string(APPEND _dyld_fallback_library_path ":$ENV{DYLD_FALLBACK_LIBRARY_PATH}") + endif() + list(APPEND _environment "DYLD_FALLBACK_LIBRARY_PATH=${_dyld_fallback_library_path}") + endif() + endif() + set(${output} "${_environment}" PARENT_SCOPE) +endfunction() + +_vortex_require_build_inputs() +get_filename_component(_workspace_root "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) +_vortex_cargo_target_env_keys(_target_key_upper _target_key_lower) +_vortex_make_cargo_command(_cargo_command) +_vortex_make_cargo_environment( + "${VORTEX_CARGO_SUPPORT_DIR}" "${_target_key_upper}" "${_target_key_lower}" + _cargo_environment) + +# Remove ambient Rust flags before installing the CMake-owned sequence. Cargo +# remains responsible for dependency tracking and incremental freshness. execute_process( - COMMAND ${_command} - WORKING_DIRECTORY "${VORTEX_WORKSPACE_ROOT}" - COMMAND_ECHO STDOUT + COMMAND "${CMAKE_COMMAND}" -E env + "--unset=RUSTFLAGS" + "--unset=CARGO_ENCODED_RUSTFLAGS" + "--unset=CARGO_TARGET_${_target_key_upper}_RUSTFLAGS" + ${_cargo_environment} + ${_cargo_command} + WORKING_DIRECTORY "${_workspace_root}" RESULT_VARIABLE _cargo_result) if(NOT _cargo_result EQUAL 0) - message(FATAL_ERROR "Cargo failed while building vortex-ffi (${_cargo_result})") + message(FATAL_ERROR + "Cargo failed while building ${VORTEX_FFI_PACKAGE} (${_cargo_result})") endif() - if(NOT EXISTS "${VORTEX_CARGO_FFI_ARCHIVE}") message(FATAL_ERROR "Cargo completed successfully but did not produce the expected " "static archive: ${VORTEX_CARGO_FFI_ARCHIVE}") endif() -get_filename_component(_cmake_archive_directory "${VORTEX_CMAKE_FFI_ARCHIVE}" DIRECTORY) -file(MAKE_DIRECTORY "${_cmake_archive_directory}") -# Stage only changed bytes so repeated no-op Cargo runs do not advance the -# imported archive's timestamp and trigger avoidable downstream relinks. -file(COPY_FILE "${VORTEX_CARGO_FFI_ARCHIVE}" "${VORTEX_CMAKE_FFI_ARCHIVE}" ONLY_IF_DIFFERENT) -if(NOT EXISTS "${VORTEX_CMAKE_FFI_ARCHIVE}") - message(FATAL_ERROR "Failed to stage the vortex-ffi archive for CMake: ${VORTEX_CMAKE_FFI_ARCHIVE}") -endif() +# Copy only changed bytes so no-op Cargo runs do not trigger downstream relinks. +get_filename_component(_destination_dir "${VORTEX_CMAKE_FFI_ARCHIVE}" DIRECTORY) +file(MAKE_DIRECTORY "${_destination_dir}") +file(COPY_FILE + "${VORTEX_CARGO_FFI_ARCHIVE}" "${VORTEX_CMAKE_FFI_ARCHIVE}" + ONLY_IF_DIFFERENT) diff --git a/lang/cpp/cmake/VortexCargo.cmake b/lang/cpp/cmake/VortexCargo.cmake index a6ac34cc1c4..dfb1304e5ca 100644 --- a/lang/cpp/cmake/VortexCargo.cmake +++ b/lang/cpp/cmake/VortexCargo.cmake @@ -1,10 +1,10 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -# Configure-time integration that builds vortex-ffi with Cargo and exposes it as -# a CMake static-library target. Its lifecycle is validation -> toolchain -# resolution -> fingerprinting -> support files -> custom target -> imported -# target. +# Configure-time integration that builds the selected Vortex FFI crate with +# Cargo and exposes it as a CMake static-library target. The module resolves the +# build policy and toolchain, writes build support files, and defines the Cargo +# and imported-library targets. include_guard(GLOBAL) @@ -12,107 +12,94 @@ include("${CMAKE_CURRENT_LIST_DIR}/VortexHelpers.cmake") include("${CMAKE_CURRENT_LIST_DIR}/VortexRustToolchain.cmake") include("${CMAKE_CURRENT_LIST_DIR}/VortexStaticLink.cmake") -# Normalize the user-facing comma-separated feature scalar into the canonical -# value written to `output` in PARENT_SCOPE. Semicolons and features outside the -# allowlist are fatal because they cannot participate in a supported build. -function(_vortex_normalize_cargo_features input output) - _vortex_reject_semicolon("VORTEX_CARGO_FEATURES" "${input}") - string(REPLACE "," ";" _features "${input}") - set(_normalized_features) - foreach(_feature IN LISTS _features) - string(STRIP "${_feature}" _feature) - if(_feature STREQUAL "") - continue() - endif() - if(NOT _feature STREQUAL "mimalloc") - message(FATAL_ERROR - "Unsupported vortex-ffi Cargo feature '${_feature}'. The " - "validated feature sets are empty and mimalloc.") - endif() - list(APPEND _normalized_features "${_feature}") - endforeach() - list(REMOVE_DUPLICATES _normalized_features) - list(SORT _normalized_features) - string(JOIN "," _normalized_features ${_normalized_features}) - set(${output} "${_normalized_features}" PARENT_SCOPE) -endfunction() - -# Validate the tokenized rustc arguments in ARGN against portability policy. -# This function has no output; target-cpu=native and incomplete codegen options -# are fatal. Recognizing every supported -C/--codegen spelling prevents -# alternate syntax from bypassing the check. -function(_vortex_validate_rustflags) - set(_expect_codegen_option FALSE) - foreach(_rustflag IN LISTS ARGN) - set(_codegen_option "") - if(_expect_codegen_option) - set(_codegen_option "${_rustflag}") - set(_expect_codegen_option FALSE) - elseif(_rustflag STREQUAL "-C" OR _rustflag STREQUAL "--codegen") - set(_expect_codegen_option TRUE) - continue() - elseif(_rustflag MATCHES "^-C=(.+)$") - set(_codegen_option "${CMAKE_MATCH_1}") - elseif(_rustflag MATCHES "^-C(.+)$") - set(_codegen_option "${CMAKE_MATCH_1}") - elseif(_rustflag MATCHES "^--codegen=(.+)$") - set(_codegen_option "${CMAKE_MATCH_1}") - else() - continue() - endif() - - if(_codegen_option MATCHES "^target-cpu=(.+)$") - string(TOLOWER "${CMAKE_MATCH_1}" _target_cpu) - if(_target_cpu STREQUAL "native") - message(FATAL_ERROR - "Vortex builds do not support Rust target-cpu=native because " - "build-host CPU features are unsafe for distributed parent " - "artifacts") - endif() - endif() - endforeach() - - if(_expect_codegen_option) - message(FATAL_ERROR "VORTEX_RUSTFLAGS ends with an incomplete codegen option") - endif() -endfunction() - -# Build the repository's vortex-ffi crate as a private C++ dependency. -# VORTEX_* options supply build policy. Unsupported configuration or incoherent -# toolchain metadata is fatal. -function(_vortex_add_ffi_static) - if(ARGC GREATER 0) - message(FATAL_ERROR "_vortex_add_ffi_static does not accept arguments") - endif() - if(TARGET vortex_ffi_static) - message(FATAL_ERROR "The internal vortex_ffi_static target already exists") - endif() - _vortex_normalize_cargo_features("${VORTEX_CARGO_FEATURES}" _cargo_features) - - # One Cargo profile is selected per invocation, so multi-config generators - # cannot share this target without ambiguous artifact locations. +# Validate the single-config CMake build type and return its uppercase CMake +# configuration, Cargo profile, and Cargo artifact directory. Unsupported build +# types and multi-config generators are fatal. +function(_vortex_resolve_cargo_profile configuration_output profile_output artifact_directory_output) if(CMAKE_CONFIGURATION_TYPES) message(FATAL_ERROR "The initial Vortex CMake integration supports single-config " "generators only; use Ninja with CMAKE_BUILD_TYPE=Debug, Release, " "or RelWithDebInfo") endif() + string(TOUPPER "${CMAKE_BUILD_TYPE}" _configuration) - if(_configuration STREQUAL "") - message(STATUS - "Vortex: CMAKE_BUILD_TYPE is empty; using the Debug Cargo " - "profile without modifying the parent build type") - elseif(NOT _configuration STREQUAL "DEBUG" AND - NOT _configuration STREQUAL "RELEASE" AND - NOT _configuration STREQUAL "RELWITHDEBINFO") + if(_configuration STREQUAL "DEBUG") + set(_cargo_profile "dev") + set(_artifact_directory "debug") + elseif(_configuration STREQUAL "RELEASE") + set(_cargo_profile "release") + set(_artifact_directory "release") + elseif(_configuration STREQUAL "RELWITHDEBINFO") + set(_cargo_profile "release_debug") + set(_artifact_directory "release_debug") + else() message(FATAL_ERROR - "The ${CMAKE_BUILD_TYPE} CMake configuration has no configured " - "Cargo profile. Supported configurations are Debug, Release, and " - "RelWithDebInfo.") + "Vortex requires CMAKE_BUILD_TYPE=Debug, Release, or " + "RelWithDebInfo; got '${CMAKE_BUILD_TYPE}'") endif() - # Keep sanitizer builds on their validated nightly/Debug path and reject the - # allocator combination whose behavior cannot be guaranteed. + set(${configuration_output} "${_configuration}" PARENT_SCOPE) + set(${profile_output} "${_cargo_profile}" PARENT_SCOPE) + set(${artifact_directory_output} "${_artifact_directory}" PARENT_SCOPE) +endfunction() + +# Select the CPU or CUDA FFI package from a complete workspace checkout. Return +# its package and archive names, public header directories, optional nvcc path, +# and CUDA root; +# missing workspace files and CUDA on non-Linux platforms are fatal. +function(_vortex_resolve_ffi_package + workspace_root + package_output + archive_name_output + include_dirs_output + nvcc_output + cuda_root_output) + set(_package "vortex-ffi") + set(_archive_name "libvortex_ffi.a") + set(_manifest "${workspace_root}/vortex-ffi/Cargo.toml") + set(_include_dirs "${workspace_root}/vortex-ffi/cinclude") + set(_nvcc "") + set(_cuda_root "") + + if(VORTEX_ENABLE_CUDA) + if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + message(FATAL_ERROR "VORTEX_ENABLE_CUDA is supported on Linux only") + endif() + find_package(CUDAToolkit REQUIRED) + set(_package "vortex-cuda-ffi") + set(_archive_name "libvortex_cuda_ffi.a") + set(_manifest "${workspace_root}/vortex-cuda/ffi/Cargo.toml") + list(APPEND _include_dirs "${workspace_root}/vortex-cuda/ffi/cinclude") + set(_nvcc "${CUDAToolkit_NVCC_EXECUTABLE}") + set(_cuda_root "${CUDAToolkit_TARGET_DIR}") + endif() + + if(NOT EXISTS "${workspace_root}/Cargo.toml" OR + NOT EXISTS "${workspace_root}/Cargo.lock" OR + NOT EXISTS "${_manifest}") + message(FATAL_ERROR + "Vortex's CMake source build requires a complete workspace " + "checkout containing ${_package}") + endif() + + set(${package_output} "${_package}" PARENT_SCOPE) + set(${archive_name_output} "${_archive_name}" PARENT_SCOPE) + set(${include_dirs_output} "${_include_dirs}" PARENT_SCOPE) + set(${nvcc_output} "${_nvcc}" PARENT_SCOPE) + set(${cuda_root_output} "${_cuda_root}" PARENT_SCOPE) +endfunction() + +# Validate the sanitizer selection against the CMake configuration and resolved +# Rust release. Return the native compiler flag, additional rustc arguments, +# and whether Cargo must rebuild the standard library. Invalid names, +# non-Debug builds, and non-nightly Rust toolchains are fatal. +function(_vortex_resolve_sanitizer + configuration + rustc_release + native_flag_output + rustflags_output + build_std_output) string(TOLOWER "${VORTEX_SANITIZER}" _sanitizer) if(NOT _sanitizer STREQUAL "" AND NOT _sanitizer STREQUAL "asan" AND @@ -121,384 +108,269 @@ function(_vortex_add_ffi_static) "VORTEX_SANITIZER must be empty, asan, or tsan; got " "${VORTEX_SANITIZER}") endif() - if(NOT _sanitizer STREQUAL "" AND _cargo_features STREQUAL "mimalloc") - message(FATAL_ERROR - "VORTEX_CARGO_FEATURES=mimalloc is incompatible with " - "VORTEX_SANITIZER=${_sanitizer}; disable mimalloc for sanitizer " - "builds") - endif() - if(NOT _sanitizer STREQUAL "" AND - NOT _configuration STREQUAL "" AND - NOT _configuration STREQUAL "DEBUG") + if(NOT _sanitizer STREQUAL "" AND NOT configuration STREQUAL "DEBUG") message(FATAL_ERROR "Vortex sanitizer builds require CMAKE_BUILD_TYPE=Debug") endif() - - if(VORTEX_CARGO_JOBS AND NOT VORTEX_CARGO_JOBS MATCHES "^[1-9][0-9]*$") - message(FATAL_ERROR "VORTEX_CARGO_JOBS must be a positive integer") - endif() - - # Keep the CMake configuration, Cargo profile, and Cargo artifact directory - # in a single explicit mapping used by both the command and staged path. - if(_configuration STREQUAL "" OR _configuration STREQUAL "DEBUG") - set(_configuration_name "Debug") - set(_cargo_profile "dev") - set(_cargo_artifact_directory "debug") - elseif(_configuration STREQUAL "RELEASE") - set(_configuration_name "Release") - set(_cargo_profile "release") - set(_cargo_artifact_directory "release") - else() - set(_configuration_name "RelWithDebInfo") - set(_cargo_profile "release_debug") - set(_cargo_artifact_directory "release_debug") - endif() - - # Canonicalize workspace identity before resolving tools. Values that cross - # CMake list and COMMAND boundaries must remain scalar: a semicolon would - # split one path or shell-style flag string into multiple elements. - get_filename_component(_workspace_root "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../.." ABSOLUTE) - set(_ffi_source_dir "${_workspace_root}/vortex-ffi") - set(_ffi_manifest "${_ffi_source_dir}/Cargo.toml") - if(NOT EXISTS "${_workspace_root}/Cargo.toml" OR - NOT EXISTS "${_workspace_root}/Cargo.lock" OR - NOT EXISTS "${_ffi_manifest}") - message(FATAL_ERROR - "Vortex's CMake source build requires a complete workspace " - "checkout with Cargo.toml, Cargo.lock, and vortex-ffi/Cargo.toml") - endif() - file(REAL_PATH "${_workspace_root}" _workspace_real_path) - _vortex_reject_semicolon("Vortex workspace path" "${_workspace_root}") - _vortex_reject_semicolon("Vortex workspace real path" "${_workspace_real_path}") - _vortex_reject_semicolon("Vortex FFI source path" "${_ffi_source_dir}") - _vortex_reject_semicolon("Vortex FFI manifest path" "${_ffi_manifest}") - _vortex_reject_semicolon("Vortex binary path" "${CMAKE_CURRENT_BINARY_DIR}") - _vortex_reject_semicolon("VORTEX_CARGO_TARGET_DIR" "${VORTEX_CARGO_TARGET_DIR}") - _vortex_reject_semicolon("VORTEX_RUSTFLAGS" "${VORTEX_RUSTFLAGS}") - - # Resolve rustup proxies to concrete Cargo/rustc binaries, then verify the - # Rust target and native C/C++ compilers describe the same host ABI. - _vortex_find_rust_tools("${_workspace_root}") - if(NOT _sanitizer STREQUAL "" AND NOT VORTEX_RESOLVED_RUSTC_RELEASE MATCHES "nightly") + if(NOT _sanitizer STREQUAL "" AND NOT rustc_release MATCHES "nightly") message(FATAL_ERROR "VORTEX_SANITIZER=${_sanitizer} requires a nightly Rust " - "toolchain; found ${VORTEX_RESOLVED_RUSTC_RELEASE}") - endif() - _vortex_resolve_native_target("${VORTEX_RESOLVED_RUSTC_EXECUTABLE}" "${_workspace_root}" _rust_target) - _vortex_resolve_apple_deployment_target(_apple_deployment_target) - _vortex_resolve_apple_sdkroot(_apple_sdkroot _apple_sdk_version) - - string(TOUPPER "${_rust_target}" _target_env_upper) - string(REPLACE "-" "_" _target_env_upper "${_target_env_upper}") - string(TOLOWER "${_rust_target}" _target_env_lower) - string(REPLACE "-" "_" _target_env_lower "${_target_env_lower}") - - # Parse the opted-in flag string with shell rules so rustc receives the - # intended argv. Ambient Rust flags are excluded because unvalidated state - # must not evade portability checks or the build fingerprint. - set(_user_rustflags) - if(VORTEX_RUSTFLAGS) - separate_arguments(_user_rustflags UNIX_COMMAND "${VORTEX_RUSTFLAGS}") + "toolchain; found ${rustc_release}") endif() - set(_ambient_rustflags FALSE) - if(DEFINED ENV{CARGO_ENCODED_RUSTFLAGS}) - if(NOT "$ENV{CARGO_ENCODED_RUSTFLAGS}" STREQUAL "") - set(_ambient_rustflags TRUE) - endif() - endif() - if(DEFINED ENV{RUSTFLAGS}) - if(NOT "$ENV{RUSTFLAGS}" STREQUAL "") - set(_ambient_rustflags TRUE) - endif() - endif() - if(_ambient_rustflags) - message(STATUS - "Vortex ignores ambient Rust flags; use VORTEX_RUSTFLAGS so " - "the validated flags participate in the CMake fingerprint") - endif() - set(_cargo_build_std OFF) - set(_cargo_no_default_features ON) - set(_sanitizer_compile_flag "") + + set(_native_flag "") + set(_rust_sanitizer "") if(_sanitizer STREQUAL "asan") - set(_cargo_build_std ON) - set(_sanitizer_compile_flag "-fsanitize=address,undefined,leak") - list(APPEND _user_rustflags - -A warnings - -Cunsafe-allow-abi-mismatch=sanitizer - -C debuginfo=2 - -C opt-level=0 - -C strip=none - -Zexternal-clangrt - -Zsanitizer=address,leak) + set(_native_flag "-fsanitize=address,undefined,leak") + set(_rust_sanitizer "address,leak") elseif(_sanitizer STREQUAL "tsan") - set(_cargo_build_std ON) - set(_sanitizer_compile_flag "-fsanitize=thread") - list(APPEND _user_rustflags + set(_native_flag "-fsanitize=thread") + set(_rust_sanitizer "thread") + endif() + + set(_rustflags) + set(_build_std OFF) + if(_rust_sanitizer) + set(_build_std ON) + list(APPEND _rustflags -A warnings -Cunsafe-allow-abi-mismatch=sanitizer -C debuginfo=2 -C opt-level=0 - -C strip=none -Zexternal-clangrt - -Zsanitizer=thread) + "-Zsanitizer=${_rust_sanitizer}") endif() - _vortex_validate_rustflags(${_user_rustflags}) + set(${native_flag_output} "${_native_flag}" PARENT_SCOPE) + set(${rustflags_output} "${_rustflags}" PARENT_SCOPE) + set(${build_std_output} "${_build_std}" PARENT_SCOPE) +endfunction() - # Preserve CMake's separate compile and link sysroots. C/C++ compilations from - # Cargo build scripts consume the former, while rustc forwards the latter at - # link. +# Resolve the compile and link sysroots that Cargo-built native code and rustc +# must inherit from CMake. Explicit compile/link sysroots take precedence over +# the shared CMake sysroot and the resolved Apple SDK. +function(_vortex_resolve_cargo_sysroots apple_sdkroot compile_output link_output) if(CMAKE_SYSROOT_COMPILE) set(_compile_sysroot "${CMAKE_SYSROOT_COMPILE}") elseif(CMAKE_SYSROOT) set(_compile_sysroot "${CMAKE_SYSROOT}") elseif(APPLE) - set(_compile_sysroot "${_apple_sdkroot}") + set(_compile_sysroot "${apple_sdkroot}") else() set(_compile_sysroot "") endif() + if(CMAKE_SYSROOT_LINK) set(_link_sysroot "${CMAKE_SYSROOT_LINK}") elseif(CMAKE_SYSROOT) set(_link_sysroot "${CMAKE_SYSROOT}") elseif(APPLE) - set(_link_sysroot "${_apple_sdkroot}") + set(_link_sysroot "${apple_sdkroot}") else() set(_link_sysroot "") endif() - # Cargo build scripts must use the exact CMake-selected native toolchain. - # Compiler wrappers generated below retain any CMAKE_*_COMPILER_ARG1 prefix. - _vortex_optional_value(CMAKE_C_COMPILER_ARG1 _c_compiler_arg1) - _vortex_optional_value(CMAKE_C_COMPILER_TARGET _c_compiler_target) - _vortex_optional_value(CMAKE_CXX_COMPILER _cxx_compiler) - _vortex_optional_value(CMAKE_CXX_COMPILER_ARG1 _cxx_compiler_arg1) - _vortex_optional_value(CMAKE_CXX_COMPILER_ID _cxx_compiler_id) - _vortex_optional_value(CMAKE_CXX_COMPILER_VERSION _cxx_compiler_version) - _vortex_optional_value(CMAKE_CXX_COMPILER_TARGET _cxx_compiler_target) - _vortex_optional_value(CMAKE_AR _archiver) - _vortex_optional_value(CMAKE_RANLIB _ranlib) - - if(_c_compiler_target AND CMAKE_C_COMPILER_ID MATCHES "^(AppleClang|Clang)$") - set(_native_c_compiler_target "${_c_compiler_target}") + set(${compile_output} "${_compile_sysroot}" PARENT_SCOPE) + set(${link_output} "${_link_sysroot}" PARENT_SCOPE) +endfunction() + +# Reconstruct CMake's effective C and C++ flags for Cargo build scripts, then +# append target, sysroot, deployment-target, sanitizer, and PIC requirements. +# Return the effective C compiler target and shell-encoded C/C++ flag strings. +function(_vortex_encode_native_flags + configuration + compile_sysroot + apple_deployment_target + sanitizer_flag + c_target_output + cflags_output + cxxflags_output) + if(CMAKE_C_COMPILER_TARGET AND CMAKE_C_COMPILER_ID MATCHES "^(AppleClang|Clang)$") + set(_c_target "${CMAKE_C_COMPILER_TARGET}") else() - set(_native_c_compiler_target "") + set(_c_target "") endif() - if(_cxx_compiler_target AND _cxx_compiler_id MATCHES "^(AppleClang|Clang)$") - set(_native_cxx_compiler_target "${_cxx_compiler_target}") + if(CMAKE_CXX_COMPILER_TARGET AND CMAKE_CXX_COMPILER_ID MATCHES "^(AppleClang|Clang)$") + set(_cxx_target "${CMAKE_CXX_COMPILER_TARGET}") else() - set(_native_cxx_compiler_target "") + set(_cxx_target "") endif() - _vortex_reject_semicolon("CMAKE_C_COMPILER" "${CMAKE_C_COMPILER}") - _vortex_reject_semicolon("CMAKE_C_COMPILER_ARG1" "${_c_compiler_arg1}") - _vortex_reject_semicolon("CMAKE_CXX_COMPILER" "${_cxx_compiler}") - _vortex_reject_semicolon("CMAKE_CXX_COMPILER_ARG1" "${_cxx_compiler_arg1}") - _vortex_reject_semicolon("CMAKE_AR" "${_archiver}") - _vortex_reject_semicolon("CMAKE_RANLIB" "${_ranlib}") - _vortex_reject_semicolon("compile sysroot" "${_compile_sysroot}") - _vortex_reject_semicolon("link sysroot" "${_link_sysroot}") - - # Reconstruct effective configuration-specific native flags as argv, then add - # target, sysroot, deployment, sanitizer, and PIC requirements consistently - # for native dependencies compiled from Cargo build scripts. - _vortex_optional_value(CMAKE_C_FLAGS _cmake_c_flags) - _vortex_optional_value(CMAKE_CXX_FLAGS _cmake_cxx_flags) - if(NOT _configuration STREQUAL "") - _vortex_optional_value(CMAKE_C_FLAGS_${_configuration} _cmake_c_configuration_flags) - _vortex_optional_value(CMAKE_CXX_FLAGS_${_configuration} _cmake_cxx_configuration_flags) - string(APPEND _cmake_c_flags " ${_cmake_c_configuration_flags}") - string(APPEND _cmake_cxx_flags " ${_cmake_cxx_configuration_flags}") - endif() + set(_cmake_c_flags "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${configuration}}") + set(_cmake_cxx_flags "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${configuration}}") _vortex_reject_semicolon("effective CMAKE_C_FLAGS" "${_cmake_c_flags}") _vortex_reject_semicolon("effective CMAKE_CXX_FLAGS" "${_cmake_cxx_flags}") - separate_arguments(_native_c_flags UNIX_COMMAND "${_cmake_c_flags}") - separate_arguments(_native_cxx_flags UNIX_COMMAND "${_cmake_cxx_flags}") - if(_compile_sysroot) - list(APPEND _native_c_flags "--sysroot=${_compile_sysroot}") - list(APPEND _native_cxx_flags "--sysroot=${_compile_sysroot}") + separate_arguments(_cflags UNIX_COMMAND "${_cmake_c_flags}") + separate_arguments(_cxxflags UNIX_COMMAND "${_cmake_cxx_flags}") + + if(compile_sysroot) + list(APPEND _cflags "--sysroot=${compile_sysroot}") + list(APPEND _cxxflags "--sysroot=${compile_sysroot}") endif() - if(_native_c_compiler_target) - list(APPEND _native_c_flags "--target=${_native_c_compiler_target}") + if(_c_target) + list(APPEND _cflags "--target=${_c_target}") endif() - if(_native_cxx_compiler_target) - list(APPEND _native_cxx_flags "--target=${_native_cxx_compiler_target}") - elseif(_native_c_compiler_target AND NOT _cxx_compiler) - list(APPEND _native_cxx_flags "--target=${_native_c_compiler_target}") + if(_cxx_target) + list(APPEND _cxxflags "--target=${_cxx_target}") endif() - if(_apple_deployment_target) - list(APPEND _native_c_flags "-mmacosx-version-min=${_apple_deployment_target}") - list(APPEND _native_cxx_flags "-mmacosx-version-min=${_apple_deployment_target}") + if(apple_deployment_target) + list(APPEND _cflags "-mmacosx-version-min=${apple_deployment_target}") + list(APPEND _cxxflags "-mmacosx-version-min=${apple_deployment_target}") endif() - if(_sanitizer_compile_flag) - list(APPEND _native_c_flags "${_sanitizer_compile_flag}") - list(APPEND _native_cxx_flags "${_sanitizer_compile_flag}") + if(sanitizer_flag) + list(APPEND _cflags "${sanitizer_flag}") + list(APPEND _cxxflags "${sanitizer_flag}") endif() - # Native dependencies compiled by Cargo build scripts are part of the static - # archive and must remain suitable for embedding in a shared parent. - list(APPEND _native_c_flags -fPIC) - list(APPEND _native_cxx_flags -fPIC) - _vortex_encode_shell_arguments(_native_c_flags_shell ${_native_c_flags}) - _vortex_encode_shell_arguments(_native_cxx_flags_shell ${_native_cxx_flags}) - - # Cargo's encoded rustflags format uses ASCII unit separator (31) between - # arguments, preserving exact rustc argv without another shell/list parse. - set(_final_rustflags ${_user_rustflags}) - list(APPEND _final_rustflags + + # Native dependencies become part of the archive embedded in shared parents. + list(APPEND _cflags -fPIC) + list(APPEND _cxxflags -fPIC) + _vortex_encode_shell_arguments(_cflags_shell ${_cflags}) + _vortex_encode_shell_arguments(_cxxflags_shell ${_cxxflags}) + + set(${c_target_output} "${_c_target}" PARENT_SCOPE) + set(${cflags_output} "${_cflags_shell}" PARENT_SCOPE) + set(${cxxflags_output} "${_cxxflags_shell}" PARENT_SCOPE) +endfunction() + +# Add PIC, link-sysroot, and Clang-target requirements to optional sanitizer +# rustflags, then encode the exact rustc argv with Cargo's ASCII unit separator. +function(_vortex_encode_rustflags output link_sysroot c_compiler_target) + set(_rustflags ${ARGN}) + list(APPEND _rustflags -C force-frame-pointers=yes -C relocation-model=pic) - if(_link_sysroot) - list(APPEND _final_rustflags -C "link-arg=--sysroot=${_link_sysroot}") + if(link_sysroot) + list(APPEND _rustflags -C "link-arg=--sysroot=${link_sysroot}") endif() - if(_native_c_compiler_target) - list(APPEND _final_rustflags -C "link-arg=--target=${_native_c_compiler_target}") - endif() - string(ASCII 31 _rustflags_separator) - string(JOIN "${_rustflags_separator}" _encoded_rustflags ${_final_rustflags}) - - # Hash every resolved toolchain and ABI/codegen input that can affect the - # archive. A fingerprint-specific Cargo tree isolates incompatible caches; - # profile outputs are separated again by configuration below. - set(_fingerprint_input - "${_workspace_real_path}|" - "${VORTEX_RESOLVED_CARGO_EXECUTABLE}|${VORTEX_RESOLVED_CARGO_VERBOSE}|" - "${VORTEX_RESOLVED_RUSTC_EXECUTABLE}|${VORTEX_RESOLVED_RUSTC_VERBOSE}|" - "${_rust_target}|${_cargo_features}|${_cargo_no_default_features}|" - "${CMAKE_C_COMPILER}|${_c_compiler_arg1}|" - "${CMAKE_C_COMPILER_ID}|${CMAKE_C_COMPILER_VERSION}|" - "${_c_compiler_target}|" - "${_cxx_compiler}|${_cxx_compiler_arg1}|" - "${_cxx_compiler_id}|${_cxx_compiler_version}|" - "${_cxx_compiler_target}|${_archiver}|${_ranlib}|" - "${_compile_sysroot}|${_link_sysroot}|${_apple_sdkroot}|" - "${_apple_sdk_version}|" - "${_apple_deployment_target}|" - "${_sanitizer}|${_native_c_flags_shell}|" - "${_native_cxx_flags_shell}|${_encoded_rustflags}") - string(SHA256 _fingerprint "${_fingerprint_input}") - - if(VORTEX_CARGO_TARGET_DIR) - get_filename_component(_cargo_target_root - "${VORTEX_CARGO_TARGET_DIR}" ABSOLUTE - BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") - else() - set(_cargo_target_root "${CMAKE_CURRENT_BINARY_DIR}/cargo-target") - endif() - set(_cargo_target_base "${_cargo_target_root}/${_fingerprint}") - set(_cargo_support_dir "${_cargo_target_base}/cmake-support") - - # An explicit target root may be shared by concurrent configure processes. - # Hold a function-scoped lock while producing wrappers and support files so a - # build cannot observe a mixed or partially generated configuration. - file(MAKE_DIRECTORY "${_cargo_support_dir}") - file(LOCK "${_cargo_support_dir}/configure.lock" - GUARD FUNCTION - TIMEOUT 60 - RESULT_VARIABLE _cargo_support_lock_result) - if(NOT _cargo_support_lock_result EQUAL 0) - message(FATAL_ERROR - "Could not lock Vortex Cargo support directory " - "${_cargo_support_dir}: ${_cargo_support_lock_result}") + if(c_compiler_target) + list(APPEND _rustflags -C "link-arg=--target=${c_compiler_target}") endif() + string(ASCII 31 _separator) + string(JOIN "${_separator}" _encoded ${_rustflags}) + set(${output} "${_encoded}" PARENT_SCOPE) +endfunction() + +# Generate compiler wrappers and flag payload files under the CMake-local support +# directory. Return the compiler paths Cargo build scripts must use; +# unchanged support files retain their timestamps. +function(_vortex_prepare_cargo_support + support_dir + encoded_rustflags + cflags + cxxflags + c_compiler_output + cxx_compiler_output) + file(MAKE_DIRECTORY "${support_dir}") + _vortex_make_compiler_wrapper( - "${CMAKE_C_COMPILER}" "${_c_compiler_arg1}" - "${_cargo_support_dir}" cc _native_c_compiler) - if(_cxx_compiler) - _vortex_make_compiler_wrapper( - "${_cxx_compiler}" "${_cxx_compiler_arg1}" - "${_cargo_support_dir}" cxx _native_cxx_compiler) - else() - set(_native_cxx_compiler "${_native_c_compiler}") - endif() + "${CMAKE_C_COMPILER}" "${CMAKE_C_COMPILER_ARG1}" + "${support_dir}" cc _c_compiler) + _vortex_make_compiler_wrapper( + "${CMAKE_CXX_COMPILER}" "${CMAKE_CXX_COMPILER_ARG1}" + "${support_dir}" cxx _cxx_compiler) + _vortex_write_if_different("${support_dir}/rustflags" "${encoded_rustflags}") + _vortex_write_if_different("${support_dir}/cflags" "${cflags}") + _vortex_write_if_different("${support_dir}/cxxflags" "${cxxflags}") + + set(${c_compiler_output} "${_c_compiler}" PARENT_SCOPE) + set(${cxx_compiler_output} "${_cxx_compiler}" PARENT_SCOPE) +endfunction() - # Passing this generated file via Cargo --config makes its linker and profile - # policy explicit rather than dependent on directory discovery. The driver's - # target-specific environment has higher precedence for linker and rustflags. - set(_cargo_config "${_cargo_support_dir}/config.toml") - _vortex_toml_string("${_native_c_compiler}" _linker_toml) - string(CONCAT _cargo_config_contents - "[target.\"${_rust_target}\"]\n" - "linker = ${_linker_toml}\n" - "\n" - "[profile.dev]\n" - "strip = \"none\"\n" - "\n" - "[profile.release]\n" - "codegen-units = 1\n" - "lto = \"off\"\n" - "strip = \"none\"\n" - "\n" - "[profile.release_debug]\n" - "debug = \"full\"\n" - "strip = \"none\"\n") - _vortex_write_if_different("${_cargo_config}" "${_cargo_config_contents}") - - # Files carry shell-quoted native flags and ASCII-31 Rust flags safely across - # the configure/build boundary. Write-if-different avoids needless timestamp - # churn in the shared support directory. - set(_rustflags_file "${_cargo_support_dir}/rustflags") - set(_cflags_file "${_cargo_support_dir}/cflags") - set(_cxxflags_file "${_cargo_support_dir}/cxxflags") - _vortex_write_if_different("${_rustflags_file}" "${_encoded_rustflags}") - _vortex_write_if_different("${_cflags_file}" "${_native_c_flags_shell}") - _vortex_write_if_different("${_cxxflags_file}" "${_native_cxx_flags_shell}") +# Orchestrate one source-local Cargo build and expose its staged archive as the +# private dependency consumed by the public C++ target. +block(SCOPE_FOR VARIABLES) + _vortex_resolve_cargo_profile(_configuration _cargo_profile _cargo_artifact_directory) - set(_archive_name "libvortex_ffi.a") - set(_cargo_target_dir "${_cargo_target_base}/${_configuration_name}") - string(CONCAT _cargo_ffi_archive - "${_cargo_target_dir}/${_rust_target}/" - "${_cargo_artifact_directory}/${_archive_name}") - string(CONCAT _ffi_archive - "${CMAKE_CURRENT_BINARY_DIR}/vortex-artifacts/" - "${_configuration_name}/${_archive_name}") - - # This phony target intentionally has no OUTPUT, so Cargo checks freshness on - # every build. BYPRODUCTS describes the staged archive to generators; each -D - # assignment is one list element and VERBATIM protects the outer cmake -P - # driver's argument boundaries. - add_custom_target(vortex_ffi_cargo_build ALL + get_filename_component(_workspace_root "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) + _vortex_resolve_ffi_package( + "${_workspace_root}" + _ffi_package + _cargo_archive_name + _ffi_include_dirs + _nvcc_executable + _cuda_root) + + _vortex_find_rust_tools("${_workspace_root}") + _vortex_resolve_sanitizer( + "${_configuration}" + "${VORTEX_RESOLVED_RUSTC_RELEASE}" + _sanitizer_compile_flag + _sanitizer_rustflags + _cargo_build_std) + _vortex_resolve_native_target(_rust_target) + _vortex_resolve_apple_settings(_apple_sdkroot _apple_deployment_target) + _vortex_resolve_cargo_sysroots("${_apple_sdkroot}" _compile_sysroot _link_sysroot) + + if(NOT "$ENV{CARGO_ENCODED_RUSTFLAGS}" STREQUAL "" OR NOT "$ENV{RUSTFLAGS}" STREQUAL "") + message(STATUS "Vortex ignores ambient Rust flags in its Cargo build") + endif() + + _vortex_encode_native_flags( + "${_configuration}" + "${_compile_sysroot}" + "${_apple_deployment_target}" + "${_sanitizer_compile_flag}" + _native_c_compiler_target + _native_c_flags + _native_cxx_flags) + _vortex_encode_rustflags( + _encoded_rustflags + "${_link_sysroot}" + "${_native_c_compiler_target}" + ${_sanitizer_rustflags}) + + # Cargo owns incremental invalidation inside this CMake-build-local cache. + # Registering the directory as additional clean state gives the standard + # CMake clean target the same effect as `cargo clean --target-dir ...`. + set(_cargo_target_dir "${CMAKE_CURRENT_BINARY_DIR}/cargo-target") + set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES "${_cargo_target_dir}") + set(_cargo_support_dir "${CMAKE_CURRENT_BINARY_DIR}/cargo-support") + _vortex_prepare_cargo_support( + "${_cargo_support_dir}" + "${_encoded_rustflags}" + "${_native_c_flags}" + "${_native_cxx_flags}" + _native_c_compiler + _native_cxx_compiler) + set(_cargo_ffi_archive + "${_cargo_target_dir}/${_rust_target}/${_cargo_artifact_directory}/${_cargo_archive_name}") + set(_ffi_archive "${CMAKE_CURRENT_BINARY_DIR}/vortex-artifacts/libvortex_ffi.a") + + # The phony target lets Cargo own dependency tracking. Copy-if-different in + # the driver prevents fresh Cargo checks from forcing downstream relinks. + add_custom_target(vortex_ffi_cargo_build COMMAND "${CMAKE_COMMAND}" "-DVORTEX_CARGO_EXECUTABLE=${VORTEX_RESOLVED_CARGO_EXECUTABLE}" "-DVORTEX_RUSTC_EXECUTABLE=${VORTEX_RESOLVED_RUSTC_EXECUTABLE}" "-DVORTEX_RUST_TARGET=${_rust_target}" - "-DVORTEX_WORKSPACE_ROOT=${_workspace_root}" - "-DVORTEX_FFI_MANIFEST=${_ffi_manifest}" - "-DVORTEX_CARGO_CONFIG_FILE=${_cargo_config}" "-DVORTEX_CARGO_TARGET_DIR=${_cargo_target_dir}" "-DVORTEX_CARGO_PROFILE=${_cargo_profile}" - "-DVORTEX_CARGO_JOBS=${VORTEX_CARGO_JOBS}" - "-DVORTEX_CARGO_OFFLINE=${VORTEX_CARGO_OFFLINE}" - "-DVORTEX_CARGO_FEATURES=${_cargo_features}" - "-DVORTEX_CARGO_BUILD_STD=${_cargo_build_std}" - "-DVORTEX_CARGO_NO_DEFAULT_FEATURES=${_cargo_no_default_features}" + "-DVORTEX_FFI_PACKAGE=${_ffi_package}" "-DVORTEX_CARGO_FFI_ARCHIVE=${_cargo_ffi_archive}" + "-DVORTEX_CARGO_SUPPORT_DIR=${_cargo_support_dir}" + "-DVORTEX_NVCC_EXECUTABLE=${_nvcc_executable}" + "-DVORTEX_CUDA_ROOT=${_cuda_root}" + "-DVORTEX_CARGO_BUILD_STD=${_cargo_build_std}" "-DVORTEX_CMAKE_FFI_ARCHIVE=${_ffi_archive}" - "-DVORTEX_TARGET_ENV_KEY_UPPER=${_target_env_upper}" - "-DVORTEX_TARGET_ENV_KEY_LOWER=${_target_env_lower}" - "-DVORTEX_C_LINKER=${_native_c_compiler}" "-DVORTEX_C_COMPILER=${_native_c_compiler}" "-DVORTEX_CXX_COMPILER=${_native_cxx_compiler}" - "-DVORTEX_AR=${_archiver}" - "-DVORTEX_RANLIB=${_ranlib}" - "-DVORTEX_RUSTFLAGS_FILE=${_rustflags_file}" - "-DVORTEX_CFLAGS_FILE=${_cflags_file}" - "-DVORTEX_CXXFLAGS_FILE=${_cxxflags_file}" + "-DVORTEX_AR=${CMAKE_AR}" + "-DVORTEX_RANLIB=${CMAKE_RANLIB}" "-DVORTEX_APPLE_DEPLOYMENT_TARGET=${_apple_deployment_target}" "-DVORTEX_APPLE_SDKROOT=${_apple_sdkroot}" - -P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/BuildVortexCargo.cmake" + -P "${CMAKE_CURRENT_LIST_DIR}/BuildVortexCargo.cmake" BYPRODUCTS "${_ffi_archive}" - WORKING_DIRECTORY "${_workspace_root}" - COMMENT "Building the PIC vortex-ffi static archive with Cargo" + COMMENT "Building the PIC Vortex FFI static archive with Cargo" USES_TERMINAL VERBATIM) - # Keep the imported archive scoped to the C++ source directory. The public - # C++ target carries its include path, native libraries, and build dependency - # transitively without exposing a second supported consumer target. + # The imported target remains directory-scoped and is only carried to users + # through Vortex::cpp_static. add_library(vortex_ffi_static STATIC IMPORTED) set_target_properties(vortex_ffi_static PROPERTIES IMPORTED_LOCATION "${_ffi_archive}" - INTERFACE_INCLUDE_DIRECTORIES "${_ffi_source_dir}/cinclude") + INTERFACE_INCLUDE_DIRECTORIES "${_ffi_include_dirs}") add_dependencies(vortex_ffi_static vortex_ffi_cargo_build) _vortex_configure_static_link(vortex_ffi_static "${_rust_target}") if(_sanitizer_compile_flag) @@ -507,5 +379,5 @@ function(_vortex_add_ffi_static) endif() message(STATUS "Vortex Rust target: ${_rust_target}") - message(STATUS "Vortex Cargo target base: ${_cargo_target_base}") -endfunction() + message(STATUS "Vortex Cargo target directory: ${_cargo_target_dir}") +endblock() diff --git a/lang/cpp/cmake/VortexHelpers.cmake b/lang/cpp/cmake/VortexHelpers.cmake index 58ce3496843..b8a6be6f4cf 100644 --- a/lang/cpp/cmake/VortexHelpers.cmake +++ b/lang/cpp/cmake/VortexHelpers.cmake @@ -2,22 +2,12 @@ # SPDX-FileCopyrightText: Copyright the Vortex contributors # Internal quoting and file-generation primitives shared by the CMake modules. -# Value-producing helpers accept an output variable name and assign it with -# PARENT_SCOPE because CMake functions otherwise isolate their variables. +# Value-producing helpers assign named outputs with PARENT_SCOPE because CMake +# functions otherwise isolate their variables. include_guard(GLOBAL) -# Copy an optional variable to a named output, normalizing undefined to empty. -function(_vortex_optional_value variable output) - if(DEFINED ${variable}) - set(${output} "${${variable}}" PARENT_SCOPE) - else() - set(${output} "" PARENT_SCOPE) - endif() -endfunction() - -# Reject values that cannot remain scalar: CMake stores lists as semicolon- -# delimited strings, so downstream list operations would change argv boundaries. +# Fail if a scalar value contains CMake's semicolon list separator. function(_vortex_reject_semicolon label value) if("${value}" MATCHES ";") message(FATAL_ERROR @@ -26,70 +16,47 @@ function(_vortex_reject_semicolon label value) endif() endfunction() -# Encode a validated scalar as a quoted TOML basic string. These escapes belong -# to TOML and are intentionally distinct from the shell quoting below. -function(_vortex_toml_string input output) - string(REPLACE "\\" "\\\\" _value "${input}") - string(REPLACE "\"" "\\\"" _value "${_value}") - set(${output} "\"${_value}\"" PARENT_SCOPE) -endfunction() - -# Encode one POSIX shell word; embedded apostrophes close and reopen the quoted -# segment so the original argument remains a single word. -function(_vortex_shell_quote input output) - string(REPLACE "'" "'\"'\"'" _quoted "${input}") - set(${output} "'${_quoted}'" PARENT_SCOPE) -endfunction() - -# Preserve generated-file mtimes when content is unchanged, avoiding needless -# downstream CMake or Cargo rebuild triggers. +# Write content only when path is missing or has different contents. function(_vortex_write_if_different path content) - set(_write_file TRUE) if(EXISTS "${path}") file(READ "${path}" _existing_content) if("${_existing_content}" STREQUAL "${content}") - set(_write_file FALSE) + return() endif() endif() - if(_write_file) - file(WRITE "${path}" "${content}") - endif() + file(WRITE "${path}" "${content}") endfunction() -# Serialize a CMake argv list as a single POSIX-shell command fragment while -# preserving each list element as one shell word. +# Encode ARGN as POSIX shell words in one space-delimited string. function(_vortex_encode_shell_arguments output) set(_encoded "") foreach(_argument IN LISTS ARGN) - _vortex_shell_quote("${_argument}" _argument_quoted) + string(REPLACE "'" "'\"'\"'" _argument_quoted "${_argument}") if(_encoded) string(APPEND _encoded " ") endif() - string(APPEND _encoded "${_argument_quoted}") + string(APPEND _encoded "'${_argument_quoted}'") endforeach() set(${output} "${_encoded}" PARENT_SCOPE) endfunction() -# Return the compiler directly unless CMAKE__COMPILER_ARG1 contributes -# required prefix arguments. Cargo accepts a compiler executable path, so a -# generated wrapper preserves CMake's complete compiler command for CC/CXX. +# Return the compiler path directly, or create a POSIX wrapper that preserves +# `arg1`. The wrapper filename hashes the complete command so Cargo observes a +# changed CC/CXX value when CMake changes the otherwise-hidden prefix argument. function(_vortex_make_compiler_wrapper compiler arg1 directory name output) if(arg1 STREQUAL "") set(${output} "${compiler}" PARENT_SCOPE) return() endif() - set(_command "${compiler}") separate_arguments(_compiler_arg1 NATIVE_COMMAND "${arg1}") - list(APPEND _command ${_compiler_arg1}) - set(_script "#!/bin/sh\nexec") - foreach(_argument IN LISTS _command) - _vortex_shell_quote("${_argument}" _argument_quoted) - string(APPEND _script " ${_argument_quoted}") - endforeach() - string(APPEND _script " \"$@\"\n") + _vortex_encode_shell_arguments( + _compiler_command "${compiler}" ${_compiler_arg1}) + set(_script "#!/bin/sh\nexec ${_compiler_command} \"$@\"\n") + string(SHA256 _command_hash "${_script}") + string(SUBSTRING "${_command_hash}" 0 16 _command_hash) - set(_wrapper "${directory}/${name}") + set(_wrapper "${directory}/${name}-${_command_hash}") _vortex_write_if_different("${_wrapper}" "${_script}") file(CHMOD "${_wrapper}" PERMISSIONS diff --git a/lang/cpp/cmake/VortexOptions.cmake b/lang/cpp/cmake/VortexOptions.cmake deleted file mode 100644 index 300112ffece..00000000000 --- a/lang/cpp/cmake/VortexOptions.cmake +++ /dev/null @@ -1,38 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright the Vortex contributors - -# Public options for the source-only C++ and Cargo integration. Normal variables -# set by an embedding parent take precedence over the cache defaults below. - -include_guard(GLOBAL) - -option(VORTEX_BUILD_TESTING "Build Vortex C++ tests" OFF) -option(VORTEX_BUILD_EXAMPLES "Build Vortex C++ examples" OFF) -option(VORTEX_CARGO_OFFLINE "Require Cargo to run in offline mode" OFF) - -if(NOT DEFINED VORTEX_SANITIZER) - set(VORTEX_SANITIZER "" CACHE STRING "Instrument Vortex and its consumers with asan or tsan") -endif() -get_property(_sanitizer_is_cached CACHE VORTEX_SANITIZER PROPERTY TYPE SET) -if(_sanitizer_is_cached) - set_property(CACHE VORTEX_SANITIZER PROPERTY STRINGS "" asan tsan) -endif() - -if(NOT DEFINED VORTEX_CARGO_EXECUTABLE) - set(VORTEX_CARGO_EXECUTABLE "" CACHE FILEPATH "Path to the Cargo executable") -endif() -if(NOT DEFINED VORTEX_RUSTC_EXECUTABLE) - set(VORTEX_RUSTC_EXECUTABLE "" CACHE FILEPATH "Path to the rustc executable") -endif() -if(NOT DEFINED VORTEX_CARGO_TARGET_DIR) - set(VORTEX_CARGO_TARGET_DIR "" CACHE PATH "Root for fingerprinted Cargo target directories") -endif() -if(NOT DEFINED VORTEX_CARGO_JOBS) - set(VORTEX_CARGO_JOBS "" CACHE STRING "Maximum number of nested Cargo jobs") -endif() -if(NOT DEFINED VORTEX_CARGO_FEATURES) - set(VORTEX_CARGO_FEATURES "" CACHE STRING "Comma-separated vortex-ffi Cargo features") -endif() -if(NOT DEFINED VORTEX_RUSTFLAGS) - set(VORTEX_RUSTFLAGS "" CACHE STRING "Shell-style Rust flags for the CMake-owned vortex-ffi build") -endif() diff --git a/lang/cpp/cmake/VortexRustToolchain.cmake b/lang/cpp/cmake/VortexRustToolchain.cmake index 63e4b0bd22c..e8fa272c5f5 100644 --- a/lang/cpp/cmake/VortexRustToolchain.cmake +++ b/lang/cpp/cmake/VortexRustToolchain.cmake @@ -1,7 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -# Rust toolchain and native-ABI validation for Cargo-backed Vortex CMake builds. +# Rust toolchain and native-target validation for Cargo-backed Vortex CMake builds. # This module resolves concrete tools and one supported native Rust target # before build rules are created; it neither installs toolchains nor enables # cross builds. @@ -10,9 +10,8 @@ include_guard(GLOBAL) include("${CMAKE_CURRENT_LIST_DIR}/VortexHelpers.cmake") -# Normalize a processor or target-like value to Vortex's architecture spelling. -# Unknown values pass through lowercased so callers can report the original -# unsupported selection. +# Normalize supported architecture aliases and target prefixes to `x86_64` or +# `aarch64`; lowercase other values for later validation. function(_vortex_normalize_arch input output) string(TOLOWER "${input}" _arch) if(_arch MATCHES "^(x86_64|amd64)(-|$)") @@ -23,64 +22,71 @@ function(_vortex_normalize_arch input output) set(${output} "${_arch}" PARENT_SCOPE) endfunction() -# Resolve a Cargo-family executable candidate. `configured_value` is an absolute -# path or command name; an empty value selects `program_name`. Names search -# normal system paths before conventional Cargo homes. The variable named by -# `output` receives an absolute path in PARENT_SCOPE; a missing or non-file -# result is fatal. -function(_vortex_find_rust_program output program_name configured_value) - unset(_program) - if(configured_value) - if(IS_ABSOLUTE "${configured_value}") - set(_program "${configured_value}") - else() - find_program(_program NAMES "${configured_value}" NO_CACHE) - endif() - else() - find_program(_program NAMES "${program_name}" NO_CACHE) +# Assemble a compiler probe command, including CMAKE__COMPILER_ARG1 and +# Clang's explicit CMAKE__COMPILER_TARGET. +function(_vortex_compiler_command language output) + set(_compiler "${CMAKE_${language}_COMPILER}") + set(_command "${_compiler}") + if(CMAKE_${language}_COMPILER_ARG1) + separate_arguments(_compiler_arg1 NATIVE_COMMAND "${CMAKE_${language}_COMPILER_ARG1}") + list(APPEND _command ${_compiler_arg1}) + endif() + if(CMAKE_${language}_COMPILER_TARGET AND CMAKE_${language}_COMPILER_ID MATCHES "^(AppleClang|Clang)$") + list(APPEND _command "--target=${CMAKE_${language}_COMPILER_TARGET}") endif() + set(${output} "${_command}" PARENT_SCOPE) +endfunction() - # Explicit paths and normal command lookup take precedence. Cargo homes are a - # fallback for embedding environments whose PATH omits rustup's installation. - if(NOT _program) - set(_rust_program_hints) - if(DEFINED ENV{CARGO_HOME} AND NOT "$ENV{CARGO_HOME}" STREQUAL "") - _vortex_reject_semicolon("CARGO_HOME" "$ENV{CARGO_HOME}") - list(APPEND _rust_program_hints "$ENV{CARGO_HOME}/bin") - endif() - if(DEFINED ENV{HOME} AND NOT "$ENV{HOME}" STREQUAL "") - _vortex_reject_semicolon("HOME" "$ENV{HOME}") - list(APPEND _rust_program_hints "$ENV{HOME}/.cargo/bin") - endif() - if(configured_value) - set(_program_names "${configured_value}") - else() - set(_program_names "${program_name}") - endif() - find_program(_program - NAMES ${_program_names} - HINTS ${_rust_program_hints} - NO_DEFAULT_PATH - NO_CACHE) +# Probe the selected compiler with `-dumpmachine` and require its architecture +# and platform family to match the Rust target. +function(_vortex_validate_native_compiler language rust_target) + _vortex_compiler_command("${language}" _compiler_command) + list(APPEND _compiler_command -dumpmachine) + execute_process( + COMMAND ${_compiler_command} + OUTPUT_VARIABLE _compiler_target + ERROR_VARIABLE _compiler_error + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _compiler_result) + if(NOT _compiler_result EQUAL 0 OR _compiler_target STREQUAL "") + message(FATAL_ERROR + "Could not determine the target of the CMake ${language} compiler: " + "${_compiler_error}") endif() - if(NOT _program OR NOT EXISTS "${_program}" OR IS_DIRECTORY "${_program}") - if(configured_value) - message(FATAL_ERROR "Configured ${program_name} executable does not exist: ${configured_value}") - else() - message(FATAL_ERROR "Could not find ${program_name} in PATH, CARGO_HOME/bin, or HOME/.cargo/bin") - endif() + _vortex_normalize_arch("${rust_target}" _rust_arch) + _vortex_normalize_arch("${_compiler_target}" _compiler_arch) + if(NOT _compiler_arch STREQUAL _rust_arch) + message(FATAL_ERROR + "CMake ${language} compiler target ${_compiler_target} does not " + "match Vortex Rust target ${rust_target}") + endif() + if(rust_target MATCHES "-unknown-linux-gnu$" AND + (NOT _compiler_target MATCHES "linux" OR _compiler_target MATCHES "musl")) + message(FATAL_ERROR + "CMake ${language} compiler target ${_compiler_target} is not " + "compatible with GNU Rust target ${rust_target}") + elseif(rust_target MATCHES "-apple-darwin$" AND NOT _compiler_target MATCHES "(apple|darwin)") + message(FATAL_ERROR + "CMake ${language} compiler target ${_compiler_target} is not " + "compatible with Rust target ${rust_target}") + endif() +endfunction() + +# Find a Rust tool with find_program() and return its absolute path. +function(_vortex_find_rust_program output program_name) + find_program(_program NAMES "${program_name}" NO_CACHE) + if(NOT _program) + message(FATAL_ERROR "Could not find ${program_name} in PATH") endif() get_filename_component(_program "${_program}" ABSOLUTE) _vortex_reject_semicolon("Resolved ${program_name} executable" "${_program}") set(${output} "${_program}" PARENT_SCOPE) endfunction() -# Turn `program` into the concrete `program_name` binary selected for -# `workspace_root`. The variables named by `output` and `proxy_output` receive -# the real path and a proxy boolean in PARENT_SCOPE. Probes or failed resolution -# are fatal; there is no recoverable result. -function(_vortex_resolve_rustup_proxy program program_name workspace_root output proxy_output) +# Resolve a Cargo or rustc candidate to the concrete executable selected for the +# workspace. +function(_vortex_resolve_rustup_proxy program program_name workspace_root output) # A rustup proxy normally dispatches from argv[0]. Forcing that identity to # `rustup` makes the proxy identify its manager, while a real tool still # reports itself, so detection does not depend on the candidate's filename. @@ -98,7 +104,6 @@ function(_vortex_resolve_rustup_proxy program program_name workspace_root output if(NOT _proxy_probe_output MATCHES "^rustup [0-9]+\\.[0-9]+") get_filename_component(_resolved_program "${program}" REALPATH) set(${output} "${_resolved_program}" PARENT_SCOPE) - set(${proxy_output} FALSE PARENT_SCOPE) return() endif() @@ -132,44 +137,17 @@ function(_vortex_resolve_rustup_proxy program program_name workspace_root output _vortex_reject_semicolon("Resolved rustup ${program_name} executable" "${_resolved_program}") message(STATUS "Vortex resolved rustup ${program_name} proxy ${program} to ${_resolved_program}") set(${output} "${_resolved_program}" PARENT_SCOPE) - set(${proxy_output} TRUE PARENT_SCOPE) endfunction() -# Resolve and validate the Cargo/rustc pair used for `workspace_root`. Optional -# inputs are VORTEX_CARGO_EXECUTABLE and VORTEX_RUSTC_EXECUTABLE; defaults are -# cached as user-visible FILEPATH values. VORTEX_RESOLVED_CARGO_EXECUTABLE, -# VORTEX_RESOLVED_CARGO_VERBOSE, VORTEX_RESOLVED_RUSTC_EXECUTABLE, -# VORTEX_RESOLVED_RUSTC_VERBOSE, VORTEX_RESOLVED_RUSTC_RELEASE, and -# VORTEX_RESOLVED_RUSTC_HOST are written to PARENT_SCOPE. Probes emit status; -# execution, parse, minimum-version, or host-coherence failures are fatal. +# Resolve Cargo and rustc for the workspace, require both to be at least version +# 1.95.0 with matching host triples, and return the tools, rustc release, and +# host triple to the caller. function(_vortex_find_rust_tools workspace_root) - _vortex_reject_semicolon("VORTEX_CARGO_EXECUTABLE" "${VORTEX_CARGO_EXECUTABLE}") - _vortex_reject_semicolon("VORTEX_RUSTC_EXECUTABLE" "${VORTEX_RUSTC_EXECUTABLE}") + _vortex_find_rust_program(_cargo_candidate cargo) + _vortex_find_rust_program(_rustc_candidate rustc) - _vortex_find_rust_program(_cargo_candidate cargo "${VORTEX_CARGO_EXECUTABLE}") - _vortex_find_rust_program(_rustc_candidate rustc "${VORTEX_RUSTC_EXECUTABLE}") - if(NOT VORTEX_CARGO_EXECUTABLE) - set(VORTEX_CARGO_EXECUTABLE "${_cargo_candidate}" CACHE FILEPATH - "Path to the Cargo executable" FORCE) - endif() - if(NOT VORTEX_RUSTC_EXECUTABLE) - set(VORTEX_RUSTC_EXECUTABLE "${_rustc_candidate}" CACHE FILEPATH - "Path to the rustc executable" FORCE) - endif() - - _vortex_resolve_rustup_proxy("${_cargo_candidate}" cargo "${workspace_root}" _cargo _cargo_was_proxy) - _vortex_resolve_rustup_proxy("${_rustc_candidate}" rustc "${workspace_root}" _rustc _rustc_was_proxy) - # Two rustup-managed tools must come from one bin directory; otherwise Cargo - # could silently invoke a compiler from a different selected toolchain. - if(_cargo_was_proxy AND _rustc_was_proxy) - get_filename_component(_cargo_bin_directory "${_cargo}" DIRECTORY) - get_filename_component(_rustc_bin_directory "${_rustc}" DIRECTORY) - if(NOT _cargo_bin_directory STREQUAL _rustc_bin_directory) - message(FATAL_ERROR - "rustup resolved Cargo and rustc from different toolchains: " - "${_cargo_bin_directory} and ${_rustc_bin_directory}") - endif() - endif() + _vortex_resolve_rustup_proxy("${_cargo_candidate}" cargo "${workspace_root}" _cargo) + _vortex_resolve_rustup_proxy("${_rustc_candidate}" rustc "${workspace_root}" _rustc) # Query both concrete binaries: minimum versions establish required behavior, # while matching host triples reject mixed non-rustup installations as well. @@ -213,9 +191,7 @@ function(_vortex_find_rust_tools workspace_root) if(NOT _cargo_host STREQUAL _rustc_host) message(FATAL_ERROR "Cargo host ${_cargo_host} does not match rustc host " - "${_rustc_host}; " - "select a compatible VORTEX_CARGO_EXECUTABLE and " - "VORTEX_RUSTC_EXECUTABLE pair") + "${_rustc_host}; ensure both resolve from the same toolchain") endif() string(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" _rustc_semver "${_rustc_release}") if(_rustc_semver VERSION_LESS "1.95.0") @@ -227,196 +203,93 @@ function(_vortex_find_rust_tools workspace_root) message(STATUS "Vortex rustc: ${_rustc_release}, host ${_rustc_host} (${_rustc})") set(VORTEX_RESOLVED_CARGO_EXECUTABLE "${_cargo}" PARENT_SCOPE) - set(VORTEX_RESOLVED_CARGO_VERBOSE "${_cargo_verbose}" PARENT_SCOPE) set(VORTEX_RESOLVED_RUSTC_EXECUTABLE "${_rustc}" PARENT_SCOPE) - set(VORTEX_RESOLVED_RUSTC_VERBOSE "${_rustc_verbose}" PARENT_SCOPE) set(VORTEX_RESOLVED_RUSTC_RELEASE "${_rustc_release}" PARENT_SCOPE) set(VORTEX_RESOLVED_RUSTC_HOST "${_rustc_host}" PARENT_SCOPE) endfunction() -# Determine the effective Apple deployment target. The variable named by -# `output` receives the explicit CMake value, a compiler-derived default, or an -# empty string off Apple in PARENT_SCOPE. Compiler execution/parsing failures -# are fatal and status messages are emitted for the selected value. -function(_vortex_resolve_apple_deployment_target output) +# Resolve configured Apple SDK details and the effective macOS deployment target; +# return empty values on non-Apple platforms. +function(_vortex_resolve_apple_settings root_output deployment_output) if(NOT APPLE) - set(${output} "" PARENT_SCOPE) - return() - endif() - - if(CMAKE_OSX_DEPLOYMENT_TARGET) - message(STATUS "Vortex macOS deployment target: ${CMAKE_OSX_DEPLOYMENT_TARGET}") - set(${output} "${CMAKE_OSX_DEPLOYMENT_TARGET}" PARENT_SCOPE) + set(${root_output} "" PARENT_SCOPE) + set(${deployment_output} "" PARENT_SCOPE) return() endif() - # Probe the compiler's predefined minimum-version macro with CMake's effective - # target and sysroot. This captures the driver's real default instead of - # guessing it from the host OS or Xcode version. - _vortex_optional_value(CMAKE_C_COMPILER_ARG1 _compiler_arg1_value) - _vortex_optional_value(CMAKE_C_COMPILER_TARGET _compiler_target) - set(_compiler_command "${CMAKE_C_COMPILER}") - if(_compiler_arg1_value) - separate_arguments(_compiler_arg1 NATIVE_COMMAND "${_compiler_arg1_value}") - list(APPEND _compiler_command ${_compiler_arg1}) - endif() - if(_compiler_target AND CMAKE_C_COMPILER_ID MATCHES "^(AppleClang|Clang)$") - list(APPEND _compiler_command "--target=${_compiler_target}") - endif() + set(_apple_sdkroot "") if(CMAKE_OSX_SYSROOT) - list(APPEND _compiler_command "-isysroot" "${CMAKE_OSX_SYSROOT}") - endif() - list(APPEND _compiler_command -dM -E -x c /dev/null) - execute_process( - COMMAND ${_compiler_command} - OUTPUT_VARIABLE _compiler_defines - ERROR_VARIABLE _compiler_defines_error - RESULT_VARIABLE _compiler_defines_result) - if(NOT _compiler_defines_result EQUAL 0) - message(FATAL_ERROR - "Could not determine Apple deployment target from " - "${CMAKE_C_COMPILER}: ${_compiler_defines_error}. " - "Set CMAKE_OSX_DEPLOYMENT_TARGET explicitly.") - endif() - - string(REGEX MATCH - "__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__[ \t]+([0-9]+)" - _deployment_match "${_compiler_defines}") - set(_deployment_code "${CMAKE_MATCH_1}") - if(_deployment_code STREQUAL "") - message(FATAL_ERROR - "The Apple compiler did not report its default macOS " - "deployment target. Set CMAKE_OSX_DEPLOYMENT_TARGET explicitly.") - endif() - - math(EXPR _deployment_major "${_deployment_code} / 10000") - math(EXPR _deployment_minor "(${_deployment_code} / 100) % 100") - math(EXPR _deployment_patch "${_deployment_code} % 100") - if(_deployment_patch EQUAL 0) - set(_deployment_target "${_deployment_major}.${_deployment_minor}") - else() - set(_deployment_target "${_deployment_major}.${_deployment_minor}.${_deployment_patch}") - endif() - message(STATUS "Vortex macOS deployment target: ${_deployment_target}") - set(${output} "${_deployment_target}" PARENT_SCOPE) -endfunction() + if(IS_ABSOLUTE "${CMAKE_OSX_SYSROOT}") + if(NOT IS_DIRECTORY "${CMAKE_OSX_SYSROOT}") + message(FATAL_ERROR "CMAKE_OSX_SYSROOT is not a directory: ${CMAKE_OSX_SYSROOT}") + endif() + file(REAL_PATH "${CMAKE_OSX_SYSROOT}" _apple_sdkroot) + else() + find_program(_xcrun NAMES xcrun REQUIRED NO_CACHE) + execute_process( + COMMAND "${_xcrun}" --sdk "${CMAKE_OSX_SYSROOT}" --show-sdk-path + OUTPUT_VARIABLE _apple_sdkroot + ERROR_VARIABLE _xcrun_error + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _xcrun_result) + if(NOT _xcrun_result EQUAL 0 OR NOT IS_DIRECTORY "${_apple_sdkroot}") + message(FATAL_ERROR + "Could not resolve CMAKE_OSX_SYSROOT=${CMAKE_OSX_SYSROOT} " + "with xcrun: ${_xcrun_error}") + endif() + file(REAL_PATH "${_apple_sdkroot}" _apple_sdkroot) + endif() -# Resolve CMAKE_OSX_SYSROOT and its SDK version. The variables named by -# `root_output` and `version_output` receive values in PARENT_SCOPE, or empty -# strings off Apple/without a sysroot. xcrun lookup or invalid SDK metadata is -# fatal; successful resolution emits a status message. -function(_vortex_resolve_apple_sdkroot root_output version_output) - if(NOT APPLE OR NOT CMAKE_OSX_SYSROOT) - set(${root_output} "" PARENT_SCOPE) - set(${version_output} "" PARENT_SCOPE) - return() + _vortex_reject_semicolon("Resolved Apple SDK root" "${_apple_sdkroot}") + message(STATUS "Vortex Apple SDK: ${_apple_sdkroot}") endif() - # CMake accepts either a concrete SDK directory or a name such as `macosx`. - # Preserve and canonicalize an absolute selection; let xcrun resolve a named - # SDK. xcrun remains authoritative for the selected SDK's version in both - # cases. - find_program(_xcrun NAMES xcrun REQUIRED NO_CACHE) - if(IS_ABSOLUTE "${CMAKE_OSX_SYSROOT}") - if(NOT IS_DIRECTORY "${CMAKE_OSX_SYSROOT}") - message(FATAL_ERROR "CMAKE_OSX_SYSROOT is not a directory: ${CMAKE_OSX_SYSROOT}") + set(_deployment_target "${CMAKE_OSX_DEPLOYMENT_TARGET}") + if(NOT _deployment_target) + # Match Rust and Cargo-built native code to the compiler's effective + # default instead of guessing from the host OS or Xcode version. + _vortex_compiler_command(C _compiler_command) + if(_apple_sdkroot) + list(APPEND _compiler_command -isysroot "${_apple_sdkroot}") endif() - file(REAL_PATH "${CMAKE_OSX_SYSROOT}" _apple_sdkroot) - else() + list(APPEND _compiler_command -dM -E -x c /dev/null) execute_process( - COMMAND "${_xcrun}" --sdk "${CMAKE_OSX_SYSROOT}" --show-sdk-path - OUTPUT_VARIABLE _apple_sdkroot - ERROR_VARIABLE _xcrun_error - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE _xcrun_result) - if(NOT _xcrun_result EQUAL 0 OR NOT IS_DIRECTORY "${_apple_sdkroot}") - message(FATAL_ERROR "Could not resolve CMAKE_OSX_SYSROOT=${CMAKE_OSX_SYSROOT} with xcrun: ${_xcrun_error}") + COMMAND ${_compiler_command} + OUTPUT_VARIABLE _compiler_defines + ERROR_VARIABLE _compiler_error + RESULT_VARIABLE _compiler_result) + if(NOT _compiler_result EQUAL 0) + message(FATAL_ERROR + "Could not determine the default macOS deployment target: " + "${_compiler_error}") endif() - file(REAL_PATH "${_apple_sdkroot}" _apple_sdkroot) - endif() - - execute_process( - COMMAND "${_xcrun}" --sdk "${CMAKE_OSX_SYSROOT}" --show-sdk-version - OUTPUT_VARIABLE _apple_sdk_version - ERROR_VARIABLE _xcrun_version_error - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE _xcrun_version_result) - if(NOT _xcrun_version_result EQUAL 0 OR _apple_sdk_version STREQUAL "") - message(FATAL_ERROR - "Could not determine the version of Apple SDK " - "${CMAKE_OSX_SYSROOT}: ${_xcrun_version_error}") - endif() - - _vortex_reject_semicolon("Resolved Apple SDK root" "${_apple_sdkroot}") - _vortex_reject_semicolon("Resolved Apple SDK version" "${_apple_sdk_version}") - message(STATUS "Vortex Apple SDK: ${_apple_sdk_version} (${_apple_sdkroot})") - set(${root_output} "${_apple_sdkroot}" PARENT_SCOPE) - set(${version_output} "${_apple_sdk_version}" PARENT_SCOPE) -endfunction() - -# Validate one native compiler driver against `architecture` and `rust_target`. -# `compiler`, optional `arg1`/`configured_target`, `compiler_id`, and diagnostic -# `label` describe the CMake compiler invocation. There are no outputs; an -# unusable driver or architecture/platform/environment mismatch is fatal. -function(_vortex_validate_native_compiler - compiler arg1 configured_target compiler_id architecture rust_target label) - set(_compiler_command "${compiler}") - if(arg1) - separate_arguments(_compiler_arg1 NATIVE_COMMAND "${arg1}") - list(APPEND _compiler_command ${_compiler_arg1}) - endif() - if(configured_target AND compiler_id MATCHES "^(AppleClang|Clang)$") - list(APPEND _compiler_command "--target=${configured_target}") - endif() - # `-dumpmachine` checks the driver's effective default tuple, which compiler - # identity and CMAKE_*_COMPILER_TARGET declarations alone do not guarantee. - list(APPEND _compiler_command -dumpmachine) - execute_process( - COMMAND ${_compiler_command} - OUTPUT_VARIABLE _compiler_target - ERROR_VARIABLE _compiler_target_error - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE _compiler_target_result) - if(NOT _compiler_target_result EQUAL 0 OR _compiler_target STREQUAL "") - message(FATAL_ERROR - "Could not determine the target of ${label} compiler ${compiler}: " - "${_compiler_target_error}") - endif() - - _vortex_normalize_arch("${_compiler_target}" _compiler_arch) - if(NOT _compiler_arch STREQUAL architecture) - message(FATAL_ERROR - "${label} compiler target ${_compiler_target} does not match " - "Vortex Rust target ${rust_target}") - endif() - if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND - (NOT _compiler_target MATCHES "linux" OR - _compiler_target MATCHES "musl")) - message(FATAL_ERROR - "${label} compiler target ${_compiler_target} is not compatible " - "with GNU Rust target ${rust_target}") - elseif(APPLE AND NOT _compiler_target MATCHES "(apple|darwin)") - message(FATAL_ERROR - "${label} compiler target ${_compiler_target} is not compatible " - "with Rust target ${rust_target}") - endif() - if(configured_target) - _vortex_normalize_arch("${configured_target}" _configured_arch) - if(NOT _configured_arch STREQUAL architecture) + string(REGEX MATCH + "__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__[ \t]+([0-9]+)" + _deployment_match "${_compiler_defines}") + set(_deployment_code "${CMAKE_MATCH_1}") + if(_deployment_code STREQUAL "") message(FATAL_ERROR - "Configured ${label} compiler target ${configured_target} does " - "not match Vortex Rust target ${rust_target}") + "The C compiler did not report a default macOS deployment " + "target; set CMAKE_OSX_DEPLOYMENT_TARGET") + endif() + math(EXPR _deployment_major "${_deployment_code} / 10000") + math(EXPR _deployment_minor "(${_deployment_code} / 100) % 100") + math(EXPR _deployment_patch "${_deployment_code} % 100") + set(_deployment_target "${_deployment_major}.${_deployment_minor}") + if(NOT _deployment_patch EQUAL 0) + string(APPEND _deployment_target ".${_deployment_patch}") endif() endif() + _vortex_reject_semicolon("macOS deployment target" "${_deployment_target}") + message(STATUS "Vortex macOS deployment target: ${_deployment_target}") + + set(${root_output} "${_apple_sdkroot}" PARENT_SCOPE) + set(${deployment_output} "${_deployment_target}" PARENT_SCOPE) endfunction() -# Select and validate one supported native Rust target. `rustc` and -# `workspace_root` drive Rust probes; CMake platform/compiler state and -# VORTEX_RESOLVED_RUSTC_HOST constrain the result. The variable named by -# `output` receives the triple in PARENT_SCOPE. Cross, universal, unsupported, -# missing-std, compiler-probe, and ABI disagreements are fatal; probes may emit -# status output. -function(_vortex_resolve_native_target rustc workspace_root output) +# Select a supported native Rust target for CMake's platform and architecture, +# then validate it against the rustc, C, and C++ targets. +function(_vortex_resolve_native_target output) # Native C dependencies built under Cargo must share the CMake host ABI. # Reject cross builds explicitly rather than implying support from a plausible # triple. @@ -441,12 +314,12 @@ function(_vortex_resolve_native_target rustc workspace_root output) _vortex_normalize_arch("${_processor}" _architecture) # Keep this mapping closed: every accepted triple has a separately allowlisted - # native static-link manifest rather than an inferred architecture spelling. + # native static-link manifest. if(CMAKE_SYSTEM_NAME STREQUAL "Linux") if(_architecture STREQUAL "x86_64") - set(_expected_target "x86_64-unknown-linux-gnu") + set(_rust_target "x86_64-unknown-linux-gnu") elseif(_architecture STREQUAL "aarch64") - set(_expected_target "aarch64-unknown-linux-gnu") + set(_rust_target "aarch64-unknown-linux-gnu") else() message(FATAL_ERROR "Vortex supports native Linux x86_64 and aarch64 only; CMake selected ${_processor}") @@ -454,9 +327,9 @@ function(_vortex_resolve_native_target rustc workspace_root output) elseif(APPLE) if(_architecture STREQUAL "x86_64") - set(_expected_target "x86_64-apple-darwin") + set(_rust_target "x86_64-apple-darwin") elseif(_architecture STREQUAL "aarch64") - set(_expected_target "aarch64-apple-darwin") + set(_rust_target "aarch64-apple-darwin") else() message(FATAL_ERROR "Vortex standalone macOS builds support x86_64 and arm64 only; " @@ -471,7 +344,6 @@ function(_vortex_resolve_native_target rustc workspace_root output) "standalone macOS; CMake selected ${CMAKE_SYSTEM_NAME}") endif() - set(_rust_target "${_expected_target}") if(NOT VORTEX_RESOLVED_RUSTC_HOST STREQUAL _rust_target) message(FATAL_ERROR "Vortex supports native Rust builds only: rustc host is " @@ -479,53 +351,7 @@ function(_vortex_resolve_native_target rustc workspace_root output) "${_rust_target}") endif() - # Confirm that the selected toolchain contains the target standard library. - execute_process( - COMMAND "${rustc}" --print target-libdir --target "${_rust_target}" - WORKING_DIRECTORY "${workspace_root}" - OUTPUT_VARIABLE _target_libdir - ERROR_VARIABLE _target_libdir_error - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE _target_libdir_result) - if(NOT _target_libdir_result EQUAL 0 OR NOT IS_DIRECTORY "${_target_libdir}") - message(FATAL_ERROR - "The Rust standard library for ${_rust_target} is unavailable: " - "${_target_libdir_error}. Install or provide that target before " - "configuring Vortex.") - endif() - file(GLOB _target_std_rlibs "${_target_libdir}/libstd-*.rlib") - if(NOT _target_std_rlibs) - message(FATAL_ERROR - "The Rust target directory ${_target_libdir} does not contain " - "libstd for ${_rust_target}. Install or provide that target before " - "configuring Vortex.") - endif() - - # Cargo build scripts may invoke either native driver. Validate their - # effective target tuples against the architecture selected above. - _vortex_optional_value(CMAKE_C_COMPILER_ARG1 _c_compiler_arg1) - _vortex_optional_value(CMAKE_C_COMPILER_TARGET _c_compiler_target) - _vortex_validate_native_compiler( - "${CMAKE_C_COMPILER}" - "${_c_compiler_arg1}" - "${_c_compiler_target}" - "${CMAKE_C_COMPILER_ID}" - "${_architecture}" - "${_rust_target}" - "C") - _vortex_optional_value(CMAKE_CXX_COMPILER _cxx_compiler) - if(_cxx_compiler) - _vortex_optional_value(CMAKE_CXX_COMPILER_ARG1 _cxx_compiler_arg1) - _vortex_optional_value(CMAKE_CXX_COMPILER_TARGET _cxx_compiler_target) - _vortex_validate_native_compiler( - "${_cxx_compiler}" - "${_cxx_compiler_arg1}" - "${_cxx_compiler_target}" - "${CMAKE_CXX_COMPILER_ID}" - "${_architecture}" - "${_rust_target}" - "C++") - endif() - + _vortex_validate_native_compiler(C "${_rust_target}") + _vortex_validate_native_compiler(CXX "${_rust_target}") set(${output} "${_rust_target}" PARENT_SCOPE) endfunction() diff --git a/lang/cpp/cmake/VortexStaticLink.cmake b/lang/cpp/cmake/VortexStaticLink.cmake index 70a1259b7f2..33c0ecd77db 100644 --- a/lang/cpp/cmake/VortexStaticLink.cmake +++ b/lang/cpp/cmake/VortexStaticLink.cmake @@ -8,61 +8,31 @@ include_guard(GLOBAL) -# Map a supported Rust target to its native link manifest. The platform and -# error outputs are written in PARENT_SCOPE; unknown targets return an error -# rather than guessing from the host. -function(_vortex_static_link_platform rust_target platform_output error_output) - if(rust_target STREQUAL "x86_64-unknown-linux-gnu" OR - rust_target STREQUAL "aarch64-unknown-linux-gnu") - set(_platform "linux") - elseif(rust_target STREQUAL "x86_64-apple-darwin" OR - rust_target STREQUAL "aarch64-apple-darwin") - set(_platform "macos") - else() - set(${platform_output} "" PARENT_SCOPE) - string(CONCAT _platform_error - "Vortex has no validated native static-link manifest for Rust target " - "${rust_target}") - set(${error_output} "${_platform_error}" PARENT_SCOPE) - return() - endif() - - set(${platform_output} "${_platform}" PARENT_SCOPE) - set(${error_output} "" PARENT_SCOPE) -endfunction() - -# Attach the native requirements for `rust_target` to the newly created static -# library `target`. Missing targets, unsupported triples, or unavailable system -# libraries are fatal. +# Attach the platform libraries required by the Rust static archive; fail if the +# Rust target has no supported link manifest. function(_vortex_configure_static_link target rust_target) - if(NOT TARGET "${target}") - message(FATAL_ERROR "Expected CMake target does not exist: ${target}") - endif() - - _vortex_static_link_platform("${rust_target}" _platform _platform_error) - if(_platform_error) - message(FATAL_ERROR "${_platform_error}") - endif() - - if(_platform STREQUAL "linux") - set(THREADS_PREFER_PTHREAD_FLAG TRUE) + if(rust_target MATCHES "^(x86_64|aarch64)-unknown-linux-gnu$") if(NOT TARGET Threads::Threads) + set(THREADS_PREFER_PTHREAD_FLAG TRUE) find_package(Threads REQUIRED) endif() - set(_native_libraries gcc_s util rt Threads::Threads m) - if(CMAKE_DL_LIBS) - list(APPEND _native_libraries "${CMAKE_DL_LIBS}") - endif() - list(APPEND _native_libraries c) - target_link_libraries("${target}" INTERFACE ${_native_libraries}) - else() - # Apple drivers add libSystem implicitly, and the C++ driver also adds - # libc++. A C final link still needs libc++, so add it only for C links. - find_library(_vortex_core_foundation CoreFoundation REQUIRED NO_CACHE) target_link_libraries("${target}" INTERFACE - "$<$:c++>" - iconv - "${_vortex_core_foundation}") + gcc_s + util + rt + Threads::Threads + m + ${CMAKE_DL_LIBS} + c) + elseif(rust_target MATCHES "^(x86_64|aarch64)-apple-darwin$") + # The public Vortex target is C++, whose driver adds libc++ and + # libSystem. Publish only additional framework/library requirements. + find_library(_vortex_core_foundation CoreFoundation REQUIRED NO_CACHE) + target_link_libraries("${target}" INTERFACE iconv "${_vortex_core_foundation}") + else() + message(FATAL_ERROR + "Vortex has no validated native static-link manifest for Rust target " + "${rust_target}") endif() endfunction() diff --git a/lang/cpp/gcov-report.sh b/lang/cpp/gcov-report.sh index 3c834c58b2b..5da22a8f742 100755 --- a/lang/cpp/gcov-report.sh +++ b/lang/cpp/gcov-report.sh @@ -4,9 +4,11 @@ # SPDX-FileCopyrightText: Copyright the Vortex contributors set -eu -cmake -Bbuild -DVORTEX_BUILD_TESTING=ON \ +cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DVORTEX_BUILD_TESTING=ON \ -DCMAKE_CXX_FLAGS='-fprofile-arcs -ftest-coverage' -cmake --build build -j +cmake --build build --parallel ctest --test-dir build --output-on-failure geninfo build/CMakeFiles/vortex_cxx.dir/ \ diff --git a/lang/cpp/tests/CMakeLists.txt b/lang/cpp/tests/CMakeLists.txt index cbb13a53b51..8d6f1501850 100644 --- a/lang/cpp/tests/CMakeLists.txt +++ b/lang/cpp/tests/CMakeLists.txt @@ -1,8 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -# need this for std::float16_t -set(CMAKE_CXX_STANDARD 23) FetchContent_Declare( magic_enum @@ -14,6 +12,8 @@ FetchContent_MakeAvailable(magic_enum) file(GLOB TEST_FILES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") add_executable(vortex_cxx_test ${TEST_FILES}) +# Required by the std::float16_t coverage. +target_compile_features(vortex_cxx_test PRIVATE cxx_std_23) target_include_directories(vortex_cxx_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(vortex_cxx_test PRIVATE Vortex::cpp_static From a0926dff955490a98c1e1b24f0b05d5a68f46a6b Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 11:22:03 +0100 Subject: [PATCH 03/24] Clarify the CMake module structure Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- lang/cpp/CMakeLists.txt | 5 +++-- .../{BuildVortexCargo.cmake => CargoBuild.cmake} | 8 +++----- .../cmake/{VortexCargo.cmake => Configure.cmake} | 16 +++++++--------- .../cmake/{VortexHelpers.cmake => Helpers.cmake} | 4 +--- ...exRustToolchain.cmake => RustToolchain.cmake} | 8 +++----- ...StaticLink.cmake => SystemDependencies.cmake} | 8 +++----- 6 files changed, 20 insertions(+), 29 deletions(-) rename lang/cpp/cmake/{BuildVortexCargo.cmake => CargoBuild.cmake} (94%) rename lang/cpp/cmake/{VortexCargo.cmake => Configure.cmake} (96%) rename lang/cpp/cmake/{VortexHelpers.cmake => Helpers.cmake} (92%) rename lang/cpp/cmake/{VortexRustToolchain.cmake => RustToolchain.cmake} (98%) rename lang/cpp/cmake/{VortexStaticLink.cmake => SystemDependencies.cmake} (77%) diff --git a/lang/cpp/CMakeLists.txt b/lang/cpp/CMakeLists.txt index 1eaa3e73896..1f06980e3e5 100644 --- a/lang/cpp/CMakeLists.txt +++ b/lang/cpp/CMakeLists.txt @@ -49,8 +49,9 @@ option(VORTEX_WARNINGS_AS_ERRORS "Treat warnings in Vortex-owned C++ sources as errors" "${_vortex_warnings_as_errors_default}") -# Build the private Rust FFI dependency before defining the public C++ target. -include("${_vortex_cmake_dir}/VortexCargo.cmake") +# Run the private configure-time integration that defines the Cargo build and +# imported Rust FFI target used by the public C++ library. +include("${_vortex_cmake_dir}/Configure.cmake") set(_vortex_cpp_sources src/array.cpp diff --git a/lang/cpp/cmake/BuildVortexCargo.cmake b/lang/cpp/cmake/CargoBuild.cmake similarity index 94% rename from lang/cpp/cmake/BuildVortexCargo.cmake rename to lang/cpp/cmake/CargoBuild.cmake index ae1ad684d50..7a5ce1ed780 100644 --- a/lang/cpp/cmake/BuildVortexCargo.cmake +++ b/lang/cpp/cmake/CargoBuild.cmake @@ -1,10 +1,8 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -# Build-time `cmake -P` driver for the custom target defined by -# VortexCargo.cmake. Configuration arrives through -D variables; this script -# recreates the validated Cargo environment, builds the selected FFI crate, and -# stages its static archive at the stable path consumed by CMake. +# Builds and stages the Rust FFI static library used by the Vortex C++ target. +# Configure.cmake invokes this internal script automatically during the build. cmake_minimum_required(VERSION 3.28) @@ -25,7 +23,7 @@ function(_vortex_require_build_inputs) VORTEX_AR VORTEX_RANLIB) if(NOT DEFINED ${_required} OR "${${_required}}" STREQUAL "") - message(FATAL_ERROR "BuildVortexCargo.cmake requires ${_required}") + message(FATAL_ERROR "CargoBuild.cmake requires ${_required}") endif() endforeach() endfunction() diff --git a/lang/cpp/cmake/VortexCargo.cmake b/lang/cpp/cmake/Configure.cmake similarity index 96% rename from lang/cpp/cmake/VortexCargo.cmake rename to lang/cpp/cmake/Configure.cmake index dfb1304e5ca..d7be89649fa 100644 --- a/lang/cpp/cmake/VortexCargo.cmake +++ b/lang/cpp/cmake/Configure.cmake @@ -1,16 +1,14 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -# Configure-time integration that builds the selected Vortex FFI crate with -# Cargo and exposes it as a CMake static-library target. The module resolves the -# build policy and toolchain, writes build support files, and defines the Cargo -# and imported-library targets. +# Configures the Rust FFI library used by the Vortex C++ target. This module +# defines the Cargo build and exposes its output to the CMake build. include_guard(GLOBAL) -include("${CMAKE_CURRENT_LIST_DIR}/VortexHelpers.cmake") -include("${CMAKE_CURRENT_LIST_DIR}/VortexRustToolchain.cmake") -include("${CMAKE_CURRENT_LIST_DIR}/VortexStaticLink.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/Helpers.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/RustToolchain.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/SystemDependencies.cmake") # Validate the single-config CMake build type and return its uppercase CMake # configuration, Cargo profile, and Cargo artifact directory. Unsupported build @@ -359,7 +357,7 @@ block(SCOPE_FOR VARIABLES) "-DVORTEX_RANLIB=${CMAKE_RANLIB}" "-DVORTEX_APPLE_DEPLOYMENT_TARGET=${_apple_deployment_target}" "-DVORTEX_APPLE_SDKROOT=${_apple_sdkroot}" - -P "${CMAKE_CURRENT_LIST_DIR}/BuildVortexCargo.cmake" + -P "${CMAKE_CURRENT_LIST_DIR}/CargoBuild.cmake" BYPRODUCTS "${_ffi_archive}" COMMENT "Building the PIC Vortex FFI static archive with Cargo" USES_TERMINAL @@ -372,7 +370,7 @@ block(SCOPE_FOR VARIABLES) IMPORTED_LOCATION "${_ffi_archive}" INTERFACE_INCLUDE_DIRECTORIES "${_ffi_include_dirs}") add_dependencies(vortex_ffi_static vortex_ffi_cargo_build) - _vortex_configure_static_link(vortex_ffi_static "${_rust_target}") + _vortex_attach_system_dependencies(vortex_ffi_static "${_rust_target}") if(_sanitizer_compile_flag) target_compile_options(vortex_ffi_static INTERFACE "${_sanitizer_compile_flag}") target_link_options(vortex_ffi_static INTERFACE "${_sanitizer_compile_flag}") diff --git a/lang/cpp/cmake/VortexHelpers.cmake b/lang/cpp/cmake/Helpers.cmake similarity index 92% rename from lang/cpp/cmake/VortexHelpers.cmake rename to lang/cpp/cmake/Helpers.cmake index b8a6be6f4cf..8eb00b0a09c 100644 --- a/lang/cpp/cmake/VortexHelpers.cmake +++ b/lang/cpp/cmake/Helpers.cmake @@ -1,9 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -# Internal quoting and file-generation primitives shared by the CMake modules. -# Value-producing helpers assign named outputs with PARENT_SCOPE because CMake -# functions otherwise isolate their variables. +# Provides shared internal utilities for the Vortex CMake integration. include_guard(GLOBAL) diff --git a/lang/cpp/cmake/VortexRustToolchain.cmake b/lang/cpp/cmake/RustToolchain.cmake similarity index 98% rename from lang/cpp/cmake/VortexRustToolchain.cmake rename to lang/cpp/cmake/RustToolchain.cmake index e8fa272c5f5..3c1c4e24cc1 100644 --- a/lang/cpp/cmake/VortexRustToolchain.cmake +++ b/lang/cpp/cmake/RustToolchain.cmake @@ -1,14 +1,12 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -# Rust toolchain and native-target validation for Cargo-backed Vortex CMake builds. -# This module resolves concrete tools and one supported native Rust target -# before build rules are created; it neither installs toolchains nor enables -# cross builds. +# Resolves and validates the Rust and native toolchains used by the Vortex C++ +# build. include_guard(GLOBAL) -include("${CMAKE_CURRENT_LIST_DIR}/VortexHelpers.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/Helpers.cmake") # Normalize supported architecture aliases and target prefixes to `x86_64` or # `aarch64`; lowercase other values for later validation. diff --git a/lang/cpp/cmake/VortexStaticLink.cmake b/lang/cpp/cmake/SystemDependencies.cmake similarity index 77% rename from lang/cpp/cmake/VortexStaticLink.cmake rename to lang/cpp/cmake/SystemDependencies.cmake index 33c0ecd77db..8e59b24ec8d 100644 --- a/lang/cpp/cmake/VortexStaticLink.cmake +++ b/lang/cpp/cmake/SystemDependencies.cmake @@ -1,16 +1,14 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -# Native link requirements for the source-built Rust static archive. Rust -# archives leave platform-library references for the final linker, so the CMake -# target must publish the validated manifest explicitly. Export policy for an -# enclosing shared library remains the parent's responsibility. +# Defines the system libraries required to link the Rust FFI archive into C++ +# targets. include_guard(GLOBAL) # Attach the platform libraries required by the Rust static archive; fail if the # Rust target has no supported link manifest. -function(_vortex_configure_static_link target rust_target) +function(_vortex_attach_system_dependencies target rust_target) if(rust_target MATCHES "^(x86_64|aarch64)-unknown-linux-gnu$") if(NOT TARGET Threads::Threads) set(THREADS_PREFER_PTHREAD_FLAG TRUE) From 510bf9545c88ae5fb6aa7801b145dc1522499324 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 11:50:09 +0100 Subject: [PATCH 04/24] Support configurable Cargo profiles in CMake Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- Cargo.toml | 4 +++ lang/cpp/CMakeLists.txt | 1 + lang/cpp/README.md | 8 ++++-- lang/cpp/cmake/CargoBuild.cmake | 6 +++-- lang/cpp/cmake/Configure.cmake | 43 +++++++++++++++++++++++++++------ 5 files changed, 50 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3c0368e190e..5765f61b5c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -419,6 +419,10 @@ lto = "off" debug = "full" inherits = "release" +[profile.release_size] +inherits = "release" +opt-level = "z" + [profile.bench] codegen-units = 16 debug = "full" diff --git a/lang/cpp/CMakeLists.txt b/lang/cpp/CMakeLists.txt index 1f06980e3e5..bb032f8e1f4 100644 --- a/lang/cpp/CMakeLists.txt +++ b/lang/cpp/CMakeLists.txt @@ -35,6 +35,7 @@ set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS option(VORTEX_BUILD_TESTING "Build Vortex C++ tests" OFF) option(VORTEX_BUILD_EXAMPLES "Build Vortex C++ examples" OFF) option(VORTEX_ENABLE_CUDA "Build the CUDA-enabled Vortex FFI" OFF) +set(VORTEX_CARGO_PROFILE "" CACHE STRING "Override the Cargo profile selected from CMAKE_BUILD_TYPE") set(VORTEX_SANITIZER "" CACHE STRING "Instrument Vortex and its consumers with asan or tsan") set_property(CACHE VORTEX_SANITIZER PROPERTY STRINGS "" asan tsan) diff --git a/lang/cpp/README.md b/lang/cpp/README.md index 47d6aa0965f..5d5a6dc3147 100644 --- a/lang/cpp/README.md +++ b/lang/cpp/README.md @@ -47,8 +47,9 @@ The build supports: - native GNU/Linux on x86_64 and aarch64; - native macOS on x86_64 and arm64 for standalone development; -- a single-config generator, with `CMAKE_BUILD_TYPE` set to `Debug`, `Release`, or - `RelWithDebInfo`; and +- a single-config generator; unless `VORTEX_CARGO_PROFILE` is set, `Debug`, `Release`, + `RelWithDebInfo`, and `MinSizeRel` map to Cargo `dev`, `release`, `release_debug`, and + `release_size`, respectively, while other build types warn and use `dev`; and - Cargo and rustc 1.95.0 or newer, with the selected target's standard library installed. CMake discovers Cargo and rustc with `find_program`. Rustup proxies are resolved from the Vortex @@ -71,6 +72,9 @@ standalone build: - `VORTEX_ENABLE_CUDA=ON` selects the Linux-only `vortex-cuda-ffi` archive, adds `vortex_cuda.h` to the existing `Vortex::cpp_static` target, and requires `find_package(CUDAToolkit)`. Set `CUDAToolkit_ROOT` when CMake cannot find the toolkit. Default: `OFF`. +- `VORTEX_CARGO_PROFILE` overrides the Cargo profile inferred from `CMAKE_BUILD_TYPE`. Custom + profiles use the same-named Cargo artifact directory; Cargo's `test` and `bench` profiles are not + supported. Default: empty. - `VORTEX_WARNINGS_AS_ERRORS` promotes warnings only while compiling `vortex_cxx`; it does not affect tests, examples, consumers, Rust, or CUDA compilation. Default: `ON` standalone and `OFF` when embedded. diff --git a/lang/cpp/cmake/CargoBuild.cmake b/lang/cpp/cmake/CargoBuild.cmake index 7a5ce1ed780..c5f5efdcd4f 100644 --- a/lang/cpp/cmake/CargoBuild.cmake +++ b/lang/cpp/cmake/CargoBuild.cmake @@ -28,8 +28,10 @@ function(_vortex_require_build_inputs) endforeach() endfunction() -# Derive Cargo's uppercase and the Rust `cc` crate's lowercase environment keys -# for the selected target. +# Normalize a target such as `aarch64-apple-darwin` into the suffixes used by +# target-specific environment variables: `AARCH64_APPLE_DARWIN` for Cargo's +# `CARGO_TARGET_AARCH64_APPLE_DARWIN_RUSTFLAGS`, and `aarch64_apple_darwin` for +# the `cc` crate's `CC_aarch64_apple_darwin`, `CFLAGS_aarch64_apple_darwin`, etc. function(_vortex_cargo_target_env_keys upper_output lower_output) string(REPLACE "-" "_" _target_key "${VORTEX_RUST_TARGET}") string(TOUPPER "${_target_key}" _target_key_upper) diff --git a/lang/cpp/cmake/Configure.cmake b/lang/cpp/cmake/Configure.cmake index d7be89649fa..a1e41525f37 100644 --- a/lang/cpp/cmake/Configure.cmake +++ b/lang/cpp/cmake/Configure.cmake @@ -10,19 +10,40 @@ include("${CMAKE_CURRENT_LIST_DIR}/Helpers.cmake") include("${CMAKE_CURRENT_LIST_DIR}/RustToolchain.cmake") include("${CMAKE_CURRENT_LIST_DIR}/SystemDependencies.cmake") -# Validate the single-config CMake build type and return its uppercase CMake -# configuration, Cargo profile, and Cargo artifact directory. Unsupported build -# types and multi-config generators are fatal. +# Use an explicit Cargo profile override or map the single-config CMake build +# type to a profile and artifact directory. Unknown build types warn and use +# Cargo's development profile; multi-config generators remain unsupported. function(_vortex_resolve_cargo_profile configuration_output profile_output artifact_directory_output) if(CMAKE_CONFIGURATION_TYPES) message(FATAL_ERROR "The initial Vortex CMake integration supports single-config " "generators only; use Ninja with CMAKE_BUILD_TYPE=Debug, Release, " - "or RelWithDebInfo") + "RelWithDebInfo, or MinSizeRel") endif() string(TOUPPER "${CMAKE_BUILD_TYPE}" _configuration) - if(_configuration STREQUAL "DEBUG") + if(VORTEX_CARGO_PROFILE) + _vortex_reject_semicolon("VORTEX_CARGO_PROFILE" "${VORTEX_CARGO_PROFILE}") + if(NOT VORTEX_CARGO_PROFILE MATCHES "^[A-Za-z0-9_-]+$") + message(FATAL_ERROR + "VORTEX_CARGO_PROFILE must contain only letters, numbers, " + "underscores, or hyphens; got '${VORTEX_CARGO_PROFILE}'") + endif() + if(VORTEX_CARGO_PROFILE STREQUAL "test" OR VORTEX_CARGO_PROFILE STREQUAL "bench") + message(FATAL_ERROR + "VORTEX_CARGO_PROFILE=${VORTEX_CARGO_PROFILE} is unsupported because " + "its artifact location is not stable") + endif() + + set(_cargo_profile "${VORTEX_CARGO_PROFILE}") + if(_cargo_profile STREQUAL "dev") + set(_artifact_directory "debug") + elseif(_cargo_profile STREQUAL "release") + set(_artifact_directory "release") + else() + set(_artifact_directory "${_cargo_profile}") + endif() + elseif(_configuration STREQUAL "DEBUG") set(_cargo_profile "dev") set(_artifact_directory "debug") elseif(_configuration STREQUAL "RELEASE") @@ -31,10 +52,16 @@ function(_vortex_resolve_cargo_profile configuration_output profile_output artif elseif(_configuration STREQUAL "RELWITHDEBINFO") set(_cargo_profile "release_debug") set(_artifact_directory "release_debug") + elseif(_configuration STREQUAL "MINSIZEREL") + set(_cargo_profile "release_size") + set(_artifact_directory "release_size") else() - message(FATAL_ERROR - "Vortex requires CMAKE_BUILD_TYPE=Debug, Release, or " - "RelWithDebInfo; got '${CMAKE_BUILD_TYPE}'") + message(WARNING + "Vortex has no Cargo profile mapping for " + "CMAKE_BUILD_TYPE='${CMAKE_BUILD_TYPE}'; using Cargo profile dev. " + "Set VORTEX_CARGO_PROFILE to override it") + set(_cargo_profile "dev") + set(_artifact_directory "debug") endif() set(${configuration_output} "${_configuration}" PARENT_SCOPE) From b36a839ca376286bef115ae5c5f27d19c8b90d0b Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 11:55:26 +0100 Subject: [PATCH 05/24] Simplify the Cargo build script Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- lang/cpp/cmake/CargoBuild.cmake | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/lang/cpp/cmake/CargoBuild.cmake b/lang/cpp/cmake/CargoBuild.cmake index c5f5efdcd4f..cd700e518aa 100644 --- a/lang/cpp/cmake/CargoBuild.cmake +++ b/lang/cpp/cmake/CargoBuild.cmake @@ -6,7 +6,8 @@ cmake_minimum_required(VERSION 3.28) -# Require every configure-time input consumed by this build script. +# Configure.cmake passes these required values as `-D` arguments when the +# `vortex_ffi_cargo_build` target launches this script with `cmake -P`. function(_vortex_require_build_inputs) foreach(_required IN ITEMS VORTEX_CARGO_EXECUTABLE @@ -28,10 +29,8 @@ function(_vortex_require_build_inputs) endforeach() endfunction() -# Normalize a target such as `aarch64-apple-darwin` into the suffixes used by -# target-specific environment variables: `AARCH64_APPLE_DARWIN` for Cargo's -# `CARGO_TARGET_AARCH64_APPLE_DARWIN_RUSTFLAGS`, and `aarch64_apple_darwin` for -# the `cc` crate's `CC_aarch64_apple_darwin`, `CFLAGS_aarch64_apple_darwin`, etc. +# Format the Rust target for Cargo (`AARCH64_APPLE_DARWIN`) and `cc` +# (`aarch64_apple_darwin`) environment variable names. function(_vortex_cargo_target_env_keys upper_output lower_output) string(REPLACE "-" "_" _target_key "${VORTEX_RUST_TARGET}") string(TOUPPER "${_target_key}" _target_key_upper) @@ -40,8 +39,8 @@ function(_vortex_cargo_target_env_keys upper_output lower_output) set(${lower_output} "${_target_key_lower}" PARENT_SCOPE) endfunction() -# Assemble the Cargo command that builds the selected FFI package as a static -# library using the workspace's Cargo profile policy. +# Assemble the Cargo command that builds the selected FFI package as +# a static library with the selected Cargo profile. function(_vortex_make_cargo_command output) set(_command "${VORTEX_CARGO_EXECUTABLE}" @@ -60,23 +59,21 @@ function(_vortex_make_cargo_command output) set(${output} "${_command}" PARENT_SCOPE) endfunction() -# Prepend the selected Rust and CUDA tool directories to the ambient PATH. +# Prepend the selected Rust and CUDA toolchain directories to the ambient PATH. function(_vortex_build_tool_path output) - get_filename_component(_rust_bin_dir "${VORTEX_RUSTC_EXECUTABLE}" DIRECTORY) - set(_tool_bin_dirs "${_rust_bin_dir}") + get_filename_component(_path "${VORTEX_RUSTC_EXECUTABLE}" DIRECTORY) + if(VORTEX_NVCC_EXECUTABLE) get_filename_component(_nvcc_bin_dir "${VORTEX_NVCC_EXECUTABLE}" DIRECTORY) - list(APPEND _tool_bin_dirs "${_nvcc_bin_dir}") + string(APPEND _path ":${_nvcc_bin_dir}") endif() - list(JOIN _tool_bin_dirs ":" _path) - if(DEFINED ENV{PATH} AND NOT "$ENV{PATH}" STREQUAL "") - if("$ENV{PATH}" MATCHES ";") - message(FATAL_ERROR - "The CMake-owned Cargo build does not support semicolons in PATH") - endif() + if("$ENV{PATH}" MATCHES ";") + message(FATAL_ERROR "The CMake-owned Cargo build does not support semicolons in PATH") + elseif(NOT "$ENV{PATH}" STREQUAL "") string(APPEND _path ":$ENV{PATH}") endif() + set(${output} "${_path}" PARENT_SCOPE) endfunction() From ecb48209fd2c8fb260de2e1ccdce9b2c7df369ac Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 12:05:06 +0100 Subject: [PATCH 06/24] Simplify the Cargo build environment Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- lang/cpp/cmake/CargoBuild.cmake | 55 ++++++++++++++++----------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/lang/cpp/cmake/CargoBuild.cmake b/lang/cpp/cmake/CargoBuild.cmake index cd700e518aa..7c8c25f5192 100644 --- a/lang/cpp/cmake/CargoBuild.cmake +++ b/lang/cpp/cmake/CargoBuild.cmake @@ -29,14 +29,11 @@ function(_vortex_require_build_inputs) endforeach() endfunction() -# Format the Rust target for Cargo (`AARCH64_APPLE_DARWIN`) and `cc` -# (`aarch64_apple_darwin`) environment variable names. -function(_vortex_cargo_target_env_keys upper_output lower_output) +# Format the Rust target for `cc` environment variable names. +function(_vortex_cc_target_env_key output) string(REPLACE "-" "_" _target_key "${VORTEX_RUST_TARGET}") - string(TOUPPER "${_target_key}" _target_key_upper) - string(TOLOWER "${_target_key}" _target_key_lower) - set(${upper_output} "${_target_key_upper}" PARENT_SCOPE) - set(${lower_output} "${_target_key_lower}" PARENT_SCOPE) + string(TOLOWER "${_target_key}" _target_key) + set(${output} "${_target_key}" PARENT_SCOPE) endfunction() # Assemble the Cargo command that builds the selected FFI package as @@ -80,7 +77,7 @@ endfunction() # Assemble the target-specific Cargo environment from the selected tools and # generated Rust, C, and C++ flag files. On macOS, make the toolchain's LLVM # library available to llvm-tools binaries whose packaged rpath is insufficient. -function(_vortex_make_cargo_environment support_dir target_key_upper target_key_lower output) +function(_vortex_make_cargo_environment support_dir target_key_lower output) file(READ "${support_dir}/rustflags" _rustflags) file(READ "${support_dir}/cflags" _cflags) file(READ "${support_dir}/cxxflags" _cxxflags) @@ -90,7 +87,6 @@ function(_vortex_make_cargo_environment support_dir target_key_upper target_key_ "PATH=${_cargo_path}" "RUSTC=${VORTEX_RUSTC_EXECUTABLE}" "CC_SHELL_ESCAPED_FLAGS=1" - "CARGO_TARGET_${target_key_upper}_LINKER=${VORTEX_C_COMPILER}" "CC_${target_key_lower}=${VORTEX_C_COMPILER}" "CXX_${target_key_lower}=${VORTEX_CXX_COMPILER}" "AR_${target_key_lower}=${VORTEX_AR}" @@ -98,46 +94,45 @@ function(_vortex_make_cargo_environment support_dir target_key_upper target_key_ "CFLAGS_${target_key_lower}=${_cflags}" "CXXFLAGS_${target_key_lower}=${_cxxflags}" "CARGO_ENCODED_RUSTFLAGS=${_rustflags}") + if(VORTEX_CUDA_ROOT) list(APPEND _environment "CUDA_PATH=${VORTEX_CUDA_ROOT}") endif() + if(VORTEX_APPLE_DEPLOYMENT_TARGET) list(APPEND _environment "MACOSX_DEPLOYMENT_TARGET=${VORTEX_APPLE_DEPLOYMENT_TARGET}") - endif() - if(VORTEX_APPLE_SDKROOT) - list(APPEND _environment "SDKROOT=${VORTEX_APPLE_SDKROOT}") - endif() - if(VORTEX_APPLE_DEPLOYMENT_TARGET) + get_filename_component(_rust_bin_dir "${VORTEX_RUSTC_EXECUTABLE}" DIRECTORY) get_filename_component(_rust_toolchain_dir "${_rust_bin_dir}" DIRECTORY) set(_rust_toolchain_lib_dir "${_rust_toolchain_dir}/lib") + if(EXISTS "${_rust_toolchain_lib_dir}/libLLVM.dylib") - set(_dyld_fallback_library_path "${_rust_toolchain_lib_dir}") - if(DEFINED ENV{DYLD_FALLBACK_LIBRARY_PATH} AND NOT "$ENV{DYLD_FALLBACK_LIBRARY_PATH}" STREQUAL "") - string(APPEND _dyld_fallback_library_path ":$ENV{DYLD_FALLBACK_LIBRARY_PATH}") + set(_dyld_path "${_rust_toolchain_lib_dir}") + if(NOT "$ENV{DYLD_FALLBACK_LIBRARY_PATH}" STREQUAL "") + string(APPEND _dyld_path ":$ENV{DYLD_FALLBACK_LIBRARY_PATH}") endif() - list(APPEND _environment "DYLD_FALLBACK_LIBRARY_PATH=${_dyld_fallback_library_path}") + list(APPEND _environment "DYLD_FALLBACK_LIBRARY_PATH=${_dyld_path}") endif() endif() + + if(VORTEX_APPLE_SDKROOT) + list(APPEND _environment "SDKROOT=${VORTEX_APPLE_SDKROOT}") + endif() + set(${output} "${_environment}" PARENT_SCOPE) endfunction() _vortex_require_build_inputs() get_filename_component(_workspace_root "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) -_vortex_cargo_target_env_keys(_target_key_upper _target_key_lower) +_vortex_cc_target_env_key(_target_key) _vortex_make_cargo_command(_cargo_command) -_vortex_make_cargo_environment( - "${VORTEX_CARGO_SUPPORT_DIR}" "${_target_key_upper}" "${_target_key_lower}" - _cargo_environment) +_vortex_make_cargo_environment("${VORTEX_CARGO_SUPPORT_DIR}" "${_target_key}" _cargo_environment) -# Remove ambient Rust flags before installing the CMake-owned sequence. Cargo -# remains responsible for dependency tracking and incremental freshness. +# Run Cargo with the CMake-selected tools and flags. Cargo remains responsible +# for dependency tracking and incremental freshness. execute_process( COMMAND "${CMAKE_COMMAND}" -E env - "--unset=RUSTFLAGS" - "--unset=CARGO_ENCODED_RUSTFLAGS" - "--unset=CARGO_TARGET_${_target_key_upper}_RUSTFLAGS" ${_cargo_environment} ${_cargo_command} WORKING_DIRECTORY "${_workspace_root}" @@ -146,13 +141,17 @@ if(NOT _cargo_result EQUAL 0) message(FATAL_ERROR "Cargo failed while building ${VORTEX_FFI_PACKAGE} (${_cargo_result})") endif() + +# Catch profile, target, or crate-output changes that invalidate the archive +# path predicted by Configure.cmake. if(NOT EXISTS "${VORTEX_CARGO_FFI_ARCHIVE}") message(FATAL_ERROR "Cargo completed successfully but did not produce the expected " "static archive: ${VORTEX_CARGO_FFI_ARCHIVE}") endif() -# Copy only changed bytes so no-op Cargo runs do not trigger downstream relinks. +# Stage the Cargo archive at CMake's stable artifact path. Preserve its +# timestamp when the contents are unchanged to avoid unnecessary relinks. get_filename_component(_destination_dir "${VORTEX_CMAKE_FFI_ARCHIVE}" DIRECTORY) file(MAKE_DIRECTORY "${_destination_dir}") file(COPY_FILE From 24d113c9b145f73295598e8e3f10408e83552ac3 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 13:20:58 +0100 Subject: [PATCH 07/24] Refine CMake profile and sanitizer handling Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- .github/workflows/ci.yml | 3 ++ lang/cpp/CMakeLists.txt | 3 ++ lang/cpp/README.md | 15 +++++-- lang/cpp/cmake/Configure.cmake | 80 +++++++++++++++++++--------------- 4 files changed, 61 insertions(+), 40 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e26f90007e..a639a7efa3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -596,7 +596,10 @@ jobs: run: | cmake -S lang/cpp -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ -DVORTEX_SANITIZER=asan \ + -DVORTEX_SANITIZE_RUST_STD=ON \ -DVORTEX_BUILD_TESTING=ON \ -DVORTEX_BUILD_EXAMPLES=ON cmake --build build --parallel diff --git a/lang/cpp/CMakeLists.txt b/lang/cpp/CMakeLists.txt index bb032f8e1f4..218c39c6f36 100644 --- a/lang/cpp/CMakeLists.txt +++ b/lang/cpp/CMakeLists.txt @@ -38,6 +38,9 @@ option(VORTEX_ENABLE_CUDA "Build the CUDA-enabled Vortex FFI" OFF) set(VORTEX_CARGO_PROFILE "" CACHE STRING "Override the Cargo profile selected from CMAKE_BUILD_TYPE") set(VORTEX_SANITIZER "" CACHE STRING "Instrument Vortex and its consumers with asan or tsan") set_property(CACHE VORTEX_SANITIZER PROPERTY STRINGS "" asan tsan) +option(VORTEX_SANITIZE_RUST_STD + "Rebuild Rust's standard library with sanitizer instrumentation" + OFF) # Standalone builds treat warnings as errors; embedded builds do not impose that # policy on a parent. diff --git a/lang/cpp/README.md b/lang/cpp/README.md index 5d5a6dc3147..5f7e08f4bef 100644 --- a/lang/cpp/README.md +++ b/lang/cpp/README.md @@ -78,10 +78,12 @@ standalone build: - `VORTEX_WARNINGS_AS_ERRORS` promotes warnings only while compiling `vortex_cxx`; it does not affect tests, examples, consumers, Rust, or CUDA compilation. Default: `ON` standalone and `OFF` when embedded. -- `VORTEX_SANITIZER=asan|tsan` requires `Debug`, nightly Rust, and `rust-src`. ASan also enables - undefined-behavior and leak checks for native code and address/leak instrumentation for Rust; +- `VORTEX_SANITIZER=asan|tsan` requires `Debug`, Clang or AppleClang, and nightly Rust. ASan also + enables undefined-behavior and leak checks for native code and address/leak instrumentation for Rust; TSan enables thread instrumentation. Flags propagate to targets linking Vortex, but CUDA device code, the CUB helper, and nvCOMP are not sanitizer-instrumented. Default: empty. +- `VORTEX_SANITIZE_RUST_STD=ON` rebuilds Rust's standard library with the selected sanitizer and + requires the nightly `rust-src` component. Default: `OFF`. The selected Cargo and rustc binaries are fixed until CMake is reconfigured. Cargo runs with the lockfile, the selected native target and profile, and the selected FFI package's default features @@ -141,18 +143,23 @@ The `reader`, `writer`, `dtype`, `scan`, and `scan_to_arrow` executables are wri ### Sanitizers -Install nightly Rust with `rust-src`, then select it explicitly: +Install nightly Rust, then select it explicitly. CMake must use Clang or AppleClang: ```sh -rustup toolchain install nightly --component rust-src +rustup toolchain install nightly RUSTUP_TOOLCHAIN=nightly cmake -S lang/cpp -B build/cpp-asan -G Ninja \ -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ -DVORTEX_SANITIZER=asan \ -DVORTEX_BUILD_TESTING=ON cmake --build build/cpp-asan --parallel ctest --test-dir build/cpp-asan --output-on-failure ``` +To also instrument Rust's standard library, install `rust-src` and configure with +`-DVORTEX_SANITIZE_RUST_STD=ON`. + ### Coverage Run one of the following from `lang/cpp`. The helper reports C++ coverage only and requires a diff --git a/lang/cpp/cmake/Configure.cmake b/lang/cpp/cmake/Configure.cmake index a1e41525f37..caade2e05b3 100644 --- a/lang/cpp/cmake/Configure.cmake +++ b/lang/cpp/cmake/Configure.cmake @@ -21,7 +21,10 @@ function(_vortex_resolve_cargo_profile configuration_output profile_output artif "RelWithDebInfo, or MinSizeRel") endif() - string(TOUPPER "${CMAKE_BUILD_TYPE}" _configuration) + # Normalize user input for comparisons and CMAKE__FLAGS_ lookups. + string(TOUPPER "${CMAKE_BUILD_TYPE}" _build_type) + + # An explicit user override takes precedence over the CMake build-type mapping. if(VORTEX_CARGO_PROFILE) _vortex_reject_semicolon("VORTEX_CARGO_PROFILE" "${VORTEX_CARGO_PROFILE}") if(NOT VORTEX_CARGO_PROFILE MATCHES "^[A-Za-z0-9_-]+$") @@ -36,43 +39,38 @@ function(_vortex_resolve_cargo_profile configuration_output profile_output artif endif() set(_cargo_profile "${VORTEX_CARGO_PROFILE}") - if(_cargo_profile STREQUAL "dev") - set(_artifact_directory "debug") - elseif(_cargo_profile STREQUAL "release") - set(_artifact_directory "release") - else() - set(_artifact_directory "${_cargo_profile}") - endif() - elseif(_configuration STREQUAL "DEBUG") + elseif(_build_type STREQUAL "DEBUG") set(_cargo_profile "dev") - set(_artifact_directory "debug") - elseif(_configuration STREQUAL "RELEASE") + elseif(_build_type STREQUAL "RELEASE") set(_cargo_profile "release") - set(_artifact_directory "release") - elseif(_configuration STREQUAL "RELWITHDEBINFO") + elseif(_build_type STREQUAL "RELWITHDEBINFO") set(_cargo_profile "release_debug") - set(_artifact_directory "release_debug") - elseif(_configuration STREQUAL "MINSIZEREL") + elseif(_build_type STREQUAL "MINSIZEREL") set(_cargo_profile "release_size") - set(_artifact_directory "release_size") else() message(WARNING "Vortex has no Cargo profile mapping for " "CMAKE_BUILD_TYPE='${CMAKE_BUILD_TYPE}'; using Cargo profile dev. " "Set VORTEX_CARGO_PROFILE to override it") set(_cargo_profile "dev") + endif() + + if(_cargo_profile STREQUAL "dev") set(_artifact_directory "debug") + elseif(_cargo_profile STREQUAL "release") + set(_artifact_directory "release") + else() + set(_artifact_directory "${_cargo_profile}") endif() - set(${configuration_output} "${_configuration}" PARENT_SCOPE) + set(${configuration_output} "${_build_type}" PARENT_SCOPE) set(${profile_output} "${_cargo_profile}" PARENT_SCOPE) set(${artifact_directory_output} "${_artifact_directory}" PARENT_SCOPE) endfunction() -# Select the CPU or CUDA FFI package from a complete workspace checkout. Return -# its package and archive names, public header directories, optional nvcc path, -# and CUDA root; -# missing workspace files and CUDA on non-Linux platforms are fatal. +# Select the Vortex FFI package and archive for a CPU-only or CUDA-enabled +# build. Return its include directories and optional CUDA tools. Fail when the +# workspace is incomplete or CUDA is requested outside Linux. function(_vortex_resolve_ffi_package workspace_root package_output @@ -115,52 +113,62 @@ function(_vortex_resolve_ffi_package set(${cuda_root_output} "${_cuda_root}" PARENT_SCOPE) endfunction() -# Validate the sanitizer selection against the CMake configuration and resolved -# Rust release. Return the native compiler flag, additional rustc arguments, -# and whether Cargo must rebuild the standard library. Invalid names, -# non-Debug builds, and non-nightly Rust toolchains are fatal. +# Validate and configure the optional sanitizer for native and Rust code. +# Sanitizers require Clang, a Debug build, and nightly Rust; standard-library +# instrumentation is optional. function(_vortex_resolve_sanitizer configuration rustc_release native_flag_output rustflags_output build_std_output) - string(TOLOWER "${VORTEX_SANITIZER}" _sanitizer) - if(NOT _sanitizer STREQUAL "" AND - NOT _sanitizer STREQUAL "asan" AND - NOT _sanitizer STREQUAL "tsan") + string(TOUPPER "${VORTEX_SANITIZER}" VORTEX_SANITIZER) + if(NOT VORTEX_SANITIZER STREQUAL "" AND + NOT VORTEX_SANITIZER STREQUAL "ASAN" AND + NOT VORTEX_SANITIZER STREQUAL "TSAN") message(FATAL_ERROR "VORTEX_SANITIZER must be empty, asan, or tsan; got " "${VORTEX_SANITIZER}") endif() - if(NOT _sanitizer STREQUAL "" AND NOT configuration STREQUAL "DEBUG") + if(VORTEX_SANITIZE_RUST_STD AND VORTEX_SANITIZER STREQUAL "") + message(FATAL_ERROR + "VORTEX_SANITIZE_RUST_STD=ON requires VORTEX_SANITIZER=asan or tsan") + endif() + if(NOT VORTEX_SANITIZER STREQUAL "" AND + (NOT CMAKE_C_COMPILER_ID MATCHES "^(AppleClang|Clang)$" OR + NOT CMAKE_CXX_COMPILER_ID MATCHES "^(AppleClang|Clang)$")) + message(FATAL_ERROR + "Vortex sanitizer builds require Clang or AppleClang for C and C++; " + "found ${CMAKE_C_COMPILER_ID} and ${CMAKE_CXX_COMPILER_ID}") + endif() + if(NOT VORTEX_SANITIZER STREQUAL "" AND NOT configuration STREQUAL "DEBUG") message(FATAL_ERROR "Vortex sanitizer builds require CMAKE_BUILD_TYPE=Debug") endif() - if(NOT _sanitizer STREQUAL "" AND NOT rustc_release MATCHES "nightly") + if(NOT VORTEX_SANITIZER STREQUAL "" AND NOT rustc_release MATCHES "nightly") message(FATAL_ERROR - "VORTEX_SANITIZER=${_sanitizer} requires a nightly Rust " + "VORTEX_SANITIZER=${VORTEX_SANITIZER} requires a nightly Rust " "toolchain; found ${rustc_release}") endif() set(_native_flag "") set(_rust_sanitizer "") - if(_sanitizer STREQUAL "asan") + if(VORTEX_SANITIZER STREQUAL "ASAN") set(_native_flag "-fsanitize=address,undefined,leak") set(_rust_sanitizer "address,leak") - elseif(_sanitizer STREQUAL "tsan") + elseif(VORTEX_SANITIZER STREQUAL "TSAN") set(_native_flag "-fsanitize=thread") set(_rust_sanitizer "thread") endif() set(_rustflags) - set(_build_std OFF) + set(_build_std "${VORTEX_SANITIZE_RUST_STD}") if(_rust_sanitizer) - set(_build_std ON) list(APPEND _rustflags -A warnings -Cunsafe-allow-abi-mismatch=sanitizer -C debuginfo=2 -C opt-level=0 + # Use the sanitizer runtime linked by the final C++ target for both languages. -Zexternal-clangrt "-Zsanitizer=${_rust_sanitizer}") endif() From 8e9481d340a6b72a3d75b9811a335656d762a88c Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 13:24:38 +0100 Subject: [PATCH 08/24] Document CMake Cargo profile mappings Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- lang/cpp/cmake/Configure.cmake | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lang/cpp/cmake/Configure.cmake b/lang/cpp/cmake/Configure.cmake index caade2e05b3..e2571686471 100644 --- a/lang/cpp/cmake/Configure.cmake +++ b/lang/cpp/cmake/Configure.cmake @@ -24,8 +24,8 @@ function(_vortex_resolve_cargo_profile configuration_output profile_output artif # Normalize user input for comparisons and CMAKE__FLAGS_ lookups. string(TOUPPER "${CMAKE_BUILD_TYPE}" _build_type) - # An explicit user override takes precedence over the CMake build-type mapping. if(VORTEX_CARGO_PROFILE) + # An explicit user override takes precedence over the CMake build-type mapping. _vortex_reject_semicolon("VORTEX_CARGO_PROFILE" "${VORTEX_CARGO_PROFILE}") if(NOT VORTEX_CARGO_PROFILE MATCHES "^[A-Za-z0-9_-]+$") message(FATAL_ERROR @@ -40,14 +40,19 @@ function(_vortex_resolve_cargo_profile configuration_output profile_output artif set(_cargo_profile "${VORTEX_CARGO_PROFILE}") elseif(_build_type STREQUAL "DEBUG") + # Debug maps to Cargo's built-in development profile. set(_cargo_profile "dev") elseif(_build_type STREQUAL "RELEASE") + # Release maps to Cargo's built-in optimized profile. set(_cargo_profile "release") elseif(_build_type STREQUAL "RELWITHDEBINFO") + # RelWithDebInfo keeps release optimizations and full debug information. set(_cargo_profile "release_debug") elseif(_build_type STREQUAL "MINSIZEREL") + # MinSizeRel uses the release profile optimized for binary size. set(_cargo_profile "release_size") else() + # Unknown build types fall back to Cargo's development profile with a warning. message(WARNING "Vortex has no Cargo profile mapping for " "CMAKE_BUILD_TYPE='${CMAKE_BUILD_TYPE}'; using Cargo profile dev. " From 38f893b792b1011d2fd8855767cfb7d8d941b081 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 13:29:45 +0100 Subject: [PATCH 09/24] Clarify CMake FFI package requirements Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- lang/cpp/cmake/Configure.cmake | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lang/cpp/cmake/Configure.cmake b/lang/cpp/cmake/Configure.cmake index e2571686471..306a3f15bad 100644 --- a/lang/cpp/cmake/Configure.cmake +++ b/lang/cpp/cmake/Configure.cmake @@ -73,9 +73,10 @@ function(_vortex_resolve_cargo_profile configuration_output profile_output artif set(${artifact_directory_output} "${_artifact_directory}" PARENT_SCOPE) endfunction() -# Select the Vortex FFI package and archive for a CPU-only or CUDA-enabled -# build. Return its include directories and optional CUDA tools. Fail when the -# workspace is incomplete or CUDA is requested outside Linux. +# Select the Cargo package and static archive that provide Vortex FFI for a +# CPU-only or CUDA-enabled build. Return the include directories and optional +# CUDA tools. Fail if the workspace manifest, lockfile, or selected package +# manifest is missing, or CUDA is requested outside Linux. function(_vortex_resolve_ffi_package workspace_root package_output From 4e86b5c15881c78baf9833da6566b73c10615911 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 13:32:55 +0100 Subject: [PATCH 10/24] Document CMake CUDA output initialization Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- lang/cpp/cmake/Configure.cmake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lang/cpp/cmake/Configure.cmake b/lang/cpp/cmake/Configure.cmake index 306a3f15bad..9dbeda36c62 100644 --- a/lang/cpp/cmake/Configure.cmake +++ b/lang/cpp/cmake/Configure.cmake @@ -88,6 +88,8 @@ function(_vortex_resolve_ffi_package set(_archive_name "libvortex_ffi.a") set(_manifest "${workspace_root}/vortex-ffi/Cargo.toml") set(_include_dirs "${workspace_root}/vortex-ffi/cinclude") + + # Shadow parent-scope values so CPU-only builds return empty CUDA outputs. set(_nvcc "") set(_cuda_root "") @@ -100,6 +102,7 @@ function(_vortex_resolve_ffi_package set(_archive_name "libvortex_cuda_ffi.a") set(_manifest "${workspace_root}/vortex-cuda/ffi/Cargo.toml") list(APPEND _include_dirs "${workspace_root}/vortex-cuda/ffi/cinclude") + # CMake's FindCUDAToolkit module sets these after find_package succeeds. set(_nvcc "${CUDAToolkit_NVCC_EXECUTABLE}") set(_cuda_root "${CUDAToolkit_TARGET_DIR}") endif() From 08ac67630e38bc44dea0adae6f9decb7a899552d Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 13:47:31 +0100 Subject: [PATCH 11/24] Simplify CMake Cargo flag handling Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- lang/cpp/cmake/Configure.cmake | 102 ++++++++++++--------------------- 1 file changed, 37 insertions(+), 65 deletions(-) diff --git a/lang/cpp/cmake/Configure.cmake b/lang/cpp/cmake/Configure.cmake index 9dbeda36c62..2857b128a2a 100644 --- a/lang/cpp/cmake/Configure.cmake +++ b/lang/cpp/cmake/Configure.cmake @@ -132,6 +132,7 @@ function(_vortex_resolve_sanitizer rustflags_output build_std_output) string(TOUPPER "${VORTEX_SANITIZER}" VORTEX_SANITIZER) + # Reject unknown names before deriving native and Rust compiler flags. if(NOT VORTEX_SANITIZER STREQUAL "" AND NOT VORTEX_SANITIZER STREQUAL "ASAN" AND NOT VORTEX_SANITIZER STREQUAL "TSAN") @@ -139,10 +140,14 @@ function(_vortex_resolve_sanitizer "VORTEX_SANITIZER must be empty, asan, or tsan; got " "${VORTEX_SANITIZER}") endif() + + # Rebuilding std is meaningful only when sanitizer instrumentation is enabled. if(VORTEX_SANITIZE_RUST_STD AND VORTEX_SANITIZER STREQUAL "") message(FATAL_ERROR "VORTEX_SANITIZE_RUST_STD=ON requires VORTEX_SANITIZER=asan or tsan") endif() + + # Use Clang so Rust and native code share one compatible sanitizer runtime. if(NOT VORTEX_SANITIZER STREQUAL "" AND (NOT CMAKE_C_COMPILER_ID MATCHES "^(AppleClang|Clang)$" OR NOT CMAKE_CXX_COMPILER_ID MATCHES "^(AppleClang|Clang)$")) @@ -150,9 +155,13 @@ function(_vortex_resolve_sanitizer "Vortex sanitizer builds require Clang or AppleClang for C and C++; " "found ${CMAKE_C_COMPILER_ID} and ${CMAKE_CXX_COMPILER_ID}") endif() + + # Restrict sanitizer instrumentation to the supported Debug configuration. if(NOT VORTEX_SANITIZER STREQUAL "" AND NOT configuration STREQUAL "DEBUG") message(FATAL_ERROR "Vortex sanitizer builds require CMAKE_BUILD_TYPE=Debug") endif() + + # Rust sanitizer instrumentation depends on nightly-only compiler flags. if(NOT VORTEX_SANITIZER STREQUAL "" AND NOT rustc_release MATCHES "nightly") message(FATAL_ERROR "VORTEX_SANITIZER=${VORTEX_SANITIZER} requires a nightly Rust " @@ -169,7 +178,7 @@ function(_vortex_resolve_sanitizer set(_rust_sanitizer "thread") endif() - set(_rustflags) + set(_rustflags "") set(_build_std "${VORTEX_SANITIZE_RUST_STD}") if(_rust_sanitizer) list(APPEND _rustflags @@ -187,53 +196,29 @@ function(_vortex_resolve_sanitizer set(${build_std_output} "${_build_std}" PARENT_SCOPE) endfunction() -# Resolve the compile and link sysroots that Cargo-built native code and rustc -# must inherit from CMake. Explicit compile/link sysroots take precedence over -# the shared CMake sysroot and the resolved Apple SDK. -function(_vortex_resolve_cargo_sysroots apple_sdkroot compile_output link_output) - if(CMAKE_SYSROOT_COMPILE) - set(_compile_sysroot "${CMAKE_SYSROOT_COMPILE}") - elseif(CMAKE_SYSROOT) - set(_compile_sysroot "${CMAKE_SYSROOT}") - elseif(APPLE) - set(_compile_sysroot "${apple_sdkroot}") - else() - set(_compile_sysroot "") - endif() - - if(CMAKE_SYSROOT_LINK) - set(_link_sysroot "${CMAKE_SYSROOT_LINK}") - elseif(CMAKE_SYSROOT) - set(_link_sysroot "${CMAKE_SYSROOT}") - elseif(APPLE) - set(_link_sysroot "${apple_sdkroot}") - else() - set(_link_sysroot "") - endif() - - set(${compile_output} "${_compile_sysroot}" PARENT_SCOPE) - set(${link_output} "${_link_sysroot}" PARENT_SCOPE) -endfunction() - # Reconstruct CMake's effective C and C++ flags for Cargo build scripts, then -# append target, sysroot, deployment-target, sanitizer, and PIC requirements. -# Return the effective C compiler target and shell-encoded C/C++ flag strings. +# append target, deployment-target, sanitizer, and PIC requirements. Return the +# shell-encoded C/C++ flag strings. function(_vortex_encode_native_flags configuration - compile_sysroot apple_deployment_target sanitizer_flag - c_target_output cflags_output cxxflags_output) + if(CMAKE_C_COMPILER_TARGET AND CMAKE_C_COMPILER_ID MATCHES "^(AppleClang|Clang)$") + # Forward Clang's explicit C target to Cargo-built C dependencies. set(_c_target "${CMAKE_C_COMPILER_TARGET}") else() + # No explicit Clang C target needs forwarding. set(_c_target "") endif() + if(CMAKE_CXX_COMPILER_TARGET AND CMAKE_CXX_COMPILER_ID MATCHES "^(AppleClang|Clang)$") + # Forward Clang's explicit C++ target to Cargo-built C++ dependencies. set(_cxx_target "${CMAKE_CXX_COMPILER_TARGET}") else() + # No explicit Clang C++ target needs forwarding. set(_cxx_target "") endif() @@ -244,21 +229,24 @@ function(_vortex_encode_native_flags separate_arguments(_cflags UNIX_COMMAND "${_cmake_c_flags}") separate_arguments(_cxxflags UNIX_COMMAND "${_cmake_cxx_flags}") - if(compile_sysroot) - list(APPEND _cflags "--sysroot=${compile_sysroot}") - list(APPEND _cxxflags "--sysroot=${compile_sysroot}") - endif() if(_c_target) + # Preserve CMake's explicit Clang target for C dependencies. list(APPEND _cflags "--target=${_c_target}") endif() + if(_cxx_target) + # Preserve CMake's explicit Clang target for C++ dependencies. list(APPEND _cxxflags "--target=${_cxx_target}") endif() + if(apple_deployment_target) + # Match Cargo-built native code to CMake's minimum macOS version. list(APPEND _cflags "-mmacosx-version-min=${apple_deployment_target}") list(APPEND _cxxflags "-mmacosx-version-min=${apple_deployment_target}") endif() + if(sanitizer_flag) + # Instrument Cargo-built native code with the selected sanitizer. list(APPEND _cflags "${sanitizer_flag}") list(APPEND _cxxflags "${sanitizer_flag}") endif() @@ -269,33 +257,24 @@ function(_vortex_encode_native_flags _vortex_encode_shell_arguments(_cflags_shell ${_cflags}) _vortex_encode_shell_arguments(_cxxflags_shell ${_cxxflags}) - set(${c_target_output} "${_c_target}" PARENT_SCOPE) set(${cflags_output} "${_cflags_shell}" PARENT_SCOPE) set(${cxxflags_output} "${_cxxflags_shell}" PARENT_SCOPE) endfunction() -# Add PIC, link-sysroot, and Clang-target requirements to optional sanitizer -# rustflags, then encode the exact rustc argv with Cargo's ASCII unit separator. -function(_vortex_encode_rustflags output link_sysroot c_compiler_target) - set(_rustflags ${ARGN}) - list(APPEND _rustflags - -C force-frame-pointers=yes - -C relocation-model=pic) - if(link_sysroot) - list(APPEND _rustflags -C "link-arg=--sysroot=${link_sysroot}") - endif() - if(c_compiler_target) - list(APPEND _rustflags -C "link-arg=--target=${c_compiler_target}") - endif() - +# Add frame pointers and PIC to optional sanitizer rustflags, then +# encode the exact rustc argv with Cargo's ASCII unit separator. +function(_vortex_encode_rustflags output) + set(_rustflags "${ARGN}") + list(APPEND _rustflags -C force-frame-pointers=yes -C relocation-model=pic) + # Cargo separates CARGO_ENCODED_RUSTFLAGS arguments with ASCII unit separator. string(ASCII 31 _separator) string(JOIN "${_separator}" _encoded ${_rustflags}) set(${output} "${_encoded}" PARENT_SCOPE) endfunction() -# Generate compiler wrappers and flag payload files under the CMake-local support -# directory. Return the compiler paths Cargo build scripts must use; -# unchanged support files retain their timestamps. +# Write the Rust, C, and C++ flags consumed by CargoBuild.cmake. Create compiler +# wrappers when CMake configured prefix arguments, and return the compiler paths +# Cargo build scripts should use. Avoid rewriting unchanged support files. function(_vortex_prepare_cargo_support support_dir encoded_rustflags @@ -319,8 +298,8 @@ function(_vortex_prepare_cargo_support set(${cxx_compiler_output} "${_cxx_compiler}" PARENT_SCOPE) endfunction() -# Orchestrate one source-local Cargo build and expose its staged archive as the -# private dependency consumed by the public C++ target. +# Configure Cargo and expose the staged FFI archive as a private dependency of +# the public Vortex C++ target. block(SCOPE_FOR VARIABLES) _vortex_resolve_cargo_profile(_configuration _cargo_profile _cargo_artifact_directory) @@ -342,7 +321,6 @@ block(SCOPE_FOR VARIABLES) _cargo_build_std) _vortex_resolve_native_target(_rust_target) _vortex_resolve_apple_settings(_apple_sdkroot _apple_deployment_target) - _vortex_resolve_cargo_sysroots("${_apple_sdkroot}" _compile_sysroot _link_sysroot) if(NOT "$ENV{CARGO_ENCODED_RUSTFLAGS}" STREQUAL "" OR NOT "$ENV{RUSTFLAGS}" STREQUAL "") message(STATUS "Vortex ignores ambient Rust flags in its Cargo build") @@ -350,17 +328,11 @@ block(SCOPE_FOR VARIABLES) _vortex_encode_native_flags( "${_configuration}" - "${_compile_sysroot}" "${_apple_deployment_target}" "${_sanitizer_compile_flag}" - _native_c_compiler_target _native_c_flags _native_cxx_flags) - _vortex_encode_rustflags( - _encoded_rustflags - "${_link_sysroot}" - "${_native_c_compiler_target}" - ${_sanitizer_rustflags}) + _vortex_encode_rustflags(_encoded_rustflags ${_sanitizer_rustflags}) # Cargo owns incremental invalidation inside this CMake-build-local cache. # Registering the directory as additional clean state gives the standard From 4fd6a8b43214b3055926140b939d40037d858e03 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 14:50:01 +0100 Subject: [PATCH 12/24] Harden CMake Rust toolchain integration Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- .github/workflows/ci.yml | 3 +- docs/getting-started/cpp.rst | 7 +- lang/cpp/README.md | 14 +- lang/cpp/cmake/CargoBuild.cmake | 15 +- lang/cpp/cmake/Configure.cmake | 4 +- lang/cpp/cmake/RustToolchain.cmake | 301 ++++++++++-------- lang/cpp/cmake/SystemDependencies.cmake | 2 +- lang/cpp/examples/reader.cpp | 1 + lang/cpp/tests/CMakeLists.txt | 7 + lang/cpp/tests/cmake/RustToolchainCase.cmake | 103 ++++++ lang/cpp/tests/cmake/RustToolchainTests.cmake | 196 ++++++++++++ 11 files changed, 488 insertions(+), 165 deletions(-) create mode 100644 lang/cpp/tests/cmake/RustToolchainCase.cmake create mode 100644 lang/cpp/tests/cmake/RustToolchainTests.cmake diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a639a7efa3c..b5634543256 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -604,8 +604,7 @@ jobs: -DVORTEX_BUILD_EXAMPLES=ON cmake --build build --parallel - name: Run C++ tests - run: | - build/tests/vortex_cxx_test + run: ctest --test-dir build --output-on-failure - name: Run C++ examples run: | cd build/examples diff --git a/docs/getting-started/cpp.rst b/docs/getting-started/cpp.rst index b77eef75b11..364006e72ef 100644 --- a/docs/getting-started/cpp.rst +++ b/docs/getting-started/cpp.rst @@ -54,10 +54,9 @@ The CMake integration is source-only: it does not provide installation rules or ``find_package(Vortex)`` package. Downstream projects should vendor or fetch a pinned Vortex checkout and add ``lang/cpp`` directly. -macOS arm64 and x86_64 are modeled, but only Apple Silicon is currently validated; -macOS is not a cuDF integration target. GNU/Linux x86_64 and aarch64 are modeled, -while full GCC 14, Conda compiler-wrapper, glibc 2.28, and cuDF validation remains -deferred. +Native macOS arm64 is supported for standalone development, but macOS is not a +cuDF integration target. GNU/Linux x86_64 and aarch64 are modeled, while full +GCC 14, Conda compiler-wrapper, glibc 2.28, and cuDF validation remains deferred. See the `C++ bindings README `_ for all CMake diff --git a/lang/cpp/README.md b/lang/cpp/README.md index 5f7e08f4bef..a7741b41aeb 100644 --- a/lang/cpp/README.md +++ b/lang/cpp/README.md @@ -46,7 +46,7 @@ also have runtime-library deployment requirements described below. The build supports: - native GNU/Linux on x86_64 and aarch64; -- native macOS on x86_64 and arm64 for standalone development; +- native macOS on arm64 for standalone development; - a single-config generator; unless `VORTEX_CARGO_PROFILE` is set, `Debug`, `Release`, `RelWithDebInfo`, and `MinSizeRel` map to Cargo `dev`, `release`, `release_debug`, and `release_size`, respectively, while other build types warn and use `dev`; and @@ -54,8 +54,8 @@ The build supports: CMake discovers Cargo and rustc with `find_program`. Rustup proxies are resolved from the Vortex workspace, so they honor its `rust-toolchain.toml` and `RUSTUP_TOOLCHAIN`; concrete non-rustup -binaries are used directly. Cargo and rustc must report the same host. The selected C and C++ -compilers must support `-dumpmachine` and target the same native platform and architecture. +binaries are used directly. Rustc and the selected C and C++ compilers must target the same native +platform and architecture; the C and C++ compilers must support `-dumpmachine`. Cross-compilation, Apple universal binaries, Windows, musl, multi-config generators, and shared Vortex targets are not supported. Ninja is recommended. macOS is not a supported cuDF integration @@ -78,10 +78,10 @@ standalone build: - `VORTEX_WARNINGS_AS_ERRORS` promotes warnings only while compiling `vortex_cxx`; it does not affect tests, examples, consumers, Rust, or CUDA compilation. Default: `ON` standalone and `OFF` when embedded. -- `VORTEX_SANITIZER=asan|tsan` requires `Debug`, Clang or AppleClang, and nightly Rust. ASan also - enables undefined-behavior and leak checks for native code and address/leak instrumentation for Rust; - TSan enables thread instrumentation. Flags propagate to targets linking Vortex, but CUDA device - code, the CUB helper, and nvCOMP are not sanitizer-instrumented. Default: empty. +- `VORTEX_SANITIZER=asan|tsan` requires `Debug`, Clang or AppleClang, and nightly Rust. ASan enables + address and undefined-behavior checks for native code and address instrumentation for Rust; TSan + enables thread instrumentation. Flags propagate to targets linking Vortex, but CUDA device code, + the CUB helper, and nvCOMP are not sanitizer-instrumented. Default: empty. - `VORTEX_SANITIZE_RUST_STD=ON` rebuilds Rust's standard library with the selected sanitizer and requires the nightly `rust-src` component. Default: `OFF`. diff --git a/lang/cpp/cmake/CargoBuild.cmake b/lang/cpp/cmake/CargoBuild.cmake index 7c8c25f5192..3246cc0d329 100644 --- a/lang/cpp/cmake/CargoBuild.cmake +++ b/lang/cpp/cmake/CargoBuild.cmake @@ -75,8 +75,7 @@ function(_vortex_build_tool_path output) endfunction() # Assemble the target-specific Cargo environment from the selected tools and -# generated Rust, C, and C++ flag files. On macOS, make the toolchain's LLVM -# library available to llvm-tools binaries whose packaged rpath is insufficient. +# generated Rust, C, and C++ flag files. function(_vortex_make_cargo_environment support_dir target_key_lower output) file(READ "${support_dir}/rustflags" _rustflags) file(READ "${support_dir}/cflags" _cflags) @@ -102,18 +101,6 @@ function(_vortex_make_cargo_environment support_dir target_key_lower output) if(VORTEX_APPLE_DEPLOYMENT_TARGET) list(APPEND _environment "MACOSX_DEPLOYMENT_TARGET=${VORTEX_APPLE_DEPLOYMENT_TARGET}") - - get_filename_component(_rust_bin_dir "${VORTEX_RUSTC_EXECUTABLE}" DIRECTORY) - get_filename_component(_rust_toolchain_dir "${_rust_bin_dir}" DIRECTORY) - set(_rust_toolchain_lib_dir "${_rust_toolchain_dir}/lib") - - if(EXISTS "${_rust_toolchain_lib_dir}/libLLVM.dylib") - set(_dyld_path "${_rust_toolchain_lib_dir}") - if(NOT "$ENV{DYLD_FALLBACK_LIBRARY_PATH}" STREQUAL "") - string(APPEND _dyld_path ":$ENV{DYLD_FALLBACK_LIBRARY_PATH}") - endif() - list(APPEND _environment "DYLD_FALLBACK_LIBRARY_PATH=${_dyld_path}") - endif() endif() if(VORTEX_APPLE_SDKROOT) diff --git a/lang/cpp/cmake/Configure.cmake b/lang/cpp/cmake/Configure.cmake index 2857b128a2a..95688216c69 100644 --- a/lang/cpp/cmake/Configure.cmake +++ b/lang/cpp/cmake/Configure.cmake @@ -171,8 +171,8 @@ function(_vortex_resolve_sanitizer set(_native_flag "") set(_rust_sanitizer "") if(VORTEX_SANITIZER STREQUAL "ASAN") - set(_native_flag "-fsanitize=address,undefined,leak") - set(_rust_sanitizer "address,leak") + set(_native_flag "-fsanitize=address,undefined") + set(_rust_sanitizer "address") elseif(VORTEX_SANITIZER STREQUAL "TSAN") set(_native_flag "-fsanitize=thread") set(_rust_sanitizer "thread") diff --git a/lang/cpp/cmake/RustToolchain.cmake b/lang/cpp/cmake/RustToolchain.cmake index 3c1c4e24cc1..096f2200ebb 100644 --- a/lang/cpp/cmake/RustToolchain.cmake +++ b/lang/cpp/cmake/RustToolchain.cmake @@ -1,15 +1,15 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -# Resolves and validates the Rust and native toolchains used by the Vortex C++ -# build. +# Selects the Rust tools and verifies that Rust, C, and C++ use a supported +# native ABI. It also resolves the macOS SDK settings needed by Cargo builds. include_guard(GLOBAL) include("${CMAKE_CURRENT_LIST_DIR}/Helpers.cmake") -# Normalize supported architecture aliases and target prefixes to `x86_64` or -# `aarch64`; lowercase other values for later validation. +# Normalize supported architecture aliases and target prefixes to +# `x86_64` or `aarch64`; lowercase other values for later validation. function(_vortex_normalize_arch input output) string(TOLOWER "${input}" _arch) if(_arch MATCHES "^(x86_64|amd64)(-|$)") @@ -20,23 +20,27 @@ function(_vortex_normalize_arch input output) set(${output} "${_arch}" PARENT_SCOPE) endfunction() -# Assemble a compiler probe command, including CMAKE__COMPILER_ARG1 and -# Clang's explicit CMAKE__COMPILER_TARGET. +# Assemble the compiler and target-selection arguments needed by probes. function(_vortex_compiler_command language output) - set(_compiler "${CMAKE_${language}_COMPILER}") - set(_command "${_compiler}") + set(_command "${CMAKE_${language}_COMPILER}") if(CMAKE_${language}_COMPILER_ARG1) + # Preserve arguments CMake treats as part of the compiler command. separate_arguments(_compiler_arg1 NATIVE_COMMAND "${CMAKE_${language}_COMPILER_ARG1}") list(APPEND _command ${_compiler_arg1}) endif() - if(CMAKE_${language}_COMPILER_TARGET AND CMAKE_${language}_COMPILER_ID MATCHES "^(AppleClang|Clang)$") + if(CMAKE_${language}_COMPILER_TARGET AND + CMAKE_${language}_COMPILER_ID MATCHES "^(AppleClang|Clang)$") + # Make Clang report the target explicitly selected by CMake. list(APPEND _command "--target=${CMAKE_${language}_COMPILER_TARGET}") endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_OSX_ARCHITECTURES) + # Make AppleClang report CMake's selected arm64 architecture. + list(APPEND _command -arch "${CMAKE_OSX_ARCHITECTURES}") + endif() set(${output} "${_command}" PARENT_SCOPE) endfunction() -# Probe the selected compiler with `-dumpmachine` and require its architecture -# and platform family to match the Rust target. +# Require the selected compiler to use the Rust target's architecture and ABI. function(_vortex_validate_native_compiler language rust_target) _vortex_compiler_command("${language}" _compiler_command) list(APPEND _compiler_command -dumpmachine) @@ -52,103 +56,101 @@ function(_vortex_validate_native_compiler language rust_target) "${_compiler_error}") endif() + string(TOLOWER "${_compiler_target}" _compiler_target_lower) _vortex_normalize_arch("${rust_target}" _rust_arch) - _vortex_normalize_arch("${_compiler_target}" _compiler_arch) - if(NOT _compiler_arch STREQUAL _rust_arch) + _vortex_normalize_arch("${_compiler_target_lower}" _compiler_arch) + if(NOT "${_compiler_arch}" STREQUAL "${_rust_arch}") message(FATAL_ERROR "CMake ${language} compiler target ${_compiler_target} does not " "match Vortex Rust target ${rust_target}") endif() - if(rust_target MATCHES "-unknown-linux-gnu$" AND - (NOT _compiler_target MATCHES "linux" OR _compiler_target MATCHES "musl")) - message(FATAL_ERROR - "CMake ${language} compiler target ${_compiler_target} is not " - "compatible with GNU Rust target ${rust_target}") - elseif(rust_target MATCHES "-apple-darwin$" AND NOT _compiler_target MATCHES "(apple|darwin)") + + if(rust_target MATCHES "-unknown-linux-gnu$") + # GNU distro triples may omit `gnu`, as in `x86_64-redhat-linux`, while + # incompatible ABIs add a different suffix such as `musl` or `gnux32`. + if(NOT _compiler_target_lower MATCHES "-linux(-gnu)?$") + message(FATAL_ERROR + "CMake ${language} compiler target ${_compiler_target} does not " + "use the GNU Linux ABI required by Rust target ${rust_target}") + endif() + elseif(rust_target MATCHES "-apple-darwin$" AND + NOT _compiler_target_lower MATCHES + "-apple-(darwin|macosx)([0-9]+(\\.[0-9]+)*)?$") message(FATAL_ERROR - "CMake ${language} compiler target ${_compiler_target} is not " - "compatible with Rust target ${rust_target}") + "CMake ${language} compiler target ${_compiler_target} does not " + "use the macOS ABI required by Rust target ${rust_target}") endif() endfunction() -# Find a Rust tool with find_program() and return its absolute path. -function(_vortex_find_rust_program output program_name) - find_program(_program NAMES "${program_name}" NO_CACHE) +# Pin a host Rust tool, resolving the workspace selection behind a rustup proxy. +function(_vortex_resolve_rust_program workspace_root program_name output) + # find_program skips its search when this inherited scratch name is set. + set(_program "_program-NOTFOUND") + find_program(_program + NAMES "${program_name}" + NO_CMAKE_FIND_ROOT_PATH + NO_CACHE) if(NOT _program) - message(FATAL_ERROR "Could not find ${program_name} in PATH") + message(FATAL_ERROR + "Could not find ${program_name} in CMake's program search path") endif() - get_filename_component(_program "${_program}" ABSOLUTE) - _vortex_reject_semicolon("Resolved ${program_name} executable" "${_program}") - set(${output} "${_program}" PARENT_SCOPE) -endfunction() -# Resolve a Cargo or rustc candidate to the concrete executable selected for the -# workspace. -function(_vortex_resolve_rustup_proxy program program_name workspace_root output) - # A rustup proxy normally dispatches from argv[0]. Forcing that identity to - # `rustup` makes the proxy identify its manager, while a real tool still - # reports itself, so detection does not depend on the candidate's filename. + # Rustup proxies may be hardlinks. RUSTUP_FORCE_ARG0 is rustup's multicall + # override, so detection does not depend on a filename or symlink target. execute_process( - COMMAND "${CMAKE_COMMAND}" -E env RUSTUP_FORCE_ARG0=rustup "${program}" --version + COMMAND "${CMAKE_COMMAND}" -E env RUSTUP_FORCE_ARG0=rustup "${_program}" --version WORKING_DIRECTORY "${workspace_root}" OUTPUT_VARIABLE _proxy_probe_output ERROR_VARIABLE _proxy_probe_error OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE _proxy_probe_result) if(NOT _proxy_probe_result EQUAL 0) - message(FATAL_ERROR "Failed to inspect ${program_name} at ${program}: ${_proxy_probe_error}") + message(FATAL_ERROR + "Failed to inspect ${program_name} at ${_program} " + "(${_proxy_probe_result}): ${_proxy_probe_error}\n${_proxy_probe_output}") endif() - if(NOT _proxy_probe_output MATCHES "^rustup [0-9]+\\.[0-9]+") - get_filename_component(_resolved_program "${program}" REALPATH) - set(${output} "${_resolved_program}" PARENT_SCOPE) - return() + set(_resolved_program "${_program}") + set(_is_rustup_proxy OFF) + if(_proxy_probe_output MATCHES "^rustup [0-9]+\\.[0-9]+") + set(_is_rustup_proxy ON) + # Resolve from the workspace so rustup applies the active environment, + # directory, and toolchain-file selection before CMake pins the binary. + execute_process( + COMMAND "${CMAKE_COMMAND}" -E env + RUSTUP_FORCE_ARG0=rustup "${_program}" which "${program_name}" + WORKING_DIRECTORY "${workspace_root}" + OUTPUT_VARIABLE _resolved_program + ERROR_VARIABLE _rustup_error + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _rustup_result) + if(NOT _rustup_result EQUAL 0) + message(FATAL_ERROR + "rustup proxy ${_program} could not resolve active ${program_name} " + "for ${workspace_root} (${_rustup_result}): ${_rustup_error}\n" + "${_resolved_program}") + endif() endif() - get_filename_component(_proxy_directory "${program}" DIRECTORY) - find_program(_rustup - NAMES rustup - HINTS "${_proxy_directory}" - NO_DEFAULT_PATH - NO_CACHE) - if(NOT _rustup) - message(FATAL_ERROR "${program} is a rustup proxy, but rustup was not found next to it") - endif() - # rustup selection honors directory overrides and rust-toolchain files. Query - # from the workspace so the resolved binary matches the later Cargo - # invocation. - execute_process( - COMMAND "${_rustup}" which "${program_name}" - WORKING_DIRECTORY "${workspace_root}" - OUTPUT_VARIABLE _resolved_program - ERROR_VARIABLE _rustup_error - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE _rustup_result) - if(NOT _rustup_result EQUAL 0 OR - NOT EXISTS "${_resolved_program}" OR - IS_DIRECTORY "${_resolved_program}") + if(NOT EXISTS "${_resolved_program}" OR IS_DIRECTORY "${_resolved_program}") message(FATAL_ERROR - "rustup could not resolve the active ${program_name} toolchain " - "for ${workspace_root}: ${_rustup_error}") + "Resolved ${program_name} executable is not a file: ${_resolved_program}") endif() get_filename_component(_resolved_program "${_resolved_program}" REALPATH) - _vortex_reject_semicolon("Resolved rustup ${program_name} executable" "${_resolved_program}") - message(STATUS "Vortex resolved rustup ${program_name} proxy ${program} to ${_resolved_program}") + _vortex_reject_semicolon("Resolved ${program_name} executable" "${_resolved_program}") + if(_is_rustup_proxy) + message(STATUS + "Vortex resolved rustup ${program_name} proxy ${_program} to ${_resolved_program}") + endif() set(${output} "${_resolved_program}" PARENT_SCOPE) endfunction() -# Resolve Cargo and rustc for the workspace, require both to be at least version -# 1.95.0 with matching host triples, and return the tools, rustc release, and -# host triple to the caller. +# Resolve and validate the concrete Cargo and rustc binaries used by the build. function(_vortex_find_rust_tools workspace_root) - _vortex_find_rust_program(_cargo_candidate cargo) - _vortex_find_rust_program(_rustc_candidate rustc) - - _vortex_resolve_rustup_proxy("${_cargo_candidate}" cargo "${workspace_root}" _cargo) - _vortex_resolve_rustup_proxy("${_rustc_candidate}" rustc "${workspace_root}" _rustc) + set(_minimum_version "1.95.0") + _vortex_resolve_rust_program("${workspace_root}" cargo _cargo) + _vortex_resolve_rust_program("${workspace_root}" rustc _rustc) - # Query both concrete binaries: minimum versions establish required behavior, - # while matching host triples reject mixed non-rustup installations as well. execute_process( COMMAND "${_cargo}" -vV WORKING_DIRECTORY "${workspace_root}" @@ -173,27 +175,31 @@ function(_vortex_find_rust_tools workspace_root) string(REGEX MATCH "^cargo ([0-9]+\\.[0-9]+\\.[0-9]+)" _cargo_version_match "${_cargo_verbose}") set(_cargo_semver "${CMAKE_MATCH_1}") - if(_cargo_semver STREQUAL "" OR _cargo_semver VERSION_LESS "1.95.0") - message(FATAL_ERROR "Vortex requires Cargo 1.95.0 or newer; found:\n${_cargo_verbose}") + if(_cargo_semver STREQUAL "") + message(FATAL_ERROR + "Could not parse Cargo version output from ${_cargo}:\n${_cargo_verbose}") + elseif("${_cargo_semver}" VERSION_LESS "${_minimum_version}") + message(FATAL_ERROR + "Vortex requires Cargo ${_minimum_version} or newer; found:\n${_cargo_verbose}") endif() - string(REGEX MATCH "host: ([^\r\n]+)" _cargo_host_match "${_cargo_verbose}") - set(_cargo_host "${CMAKE_MATCH_1}") + # Cargo only orchestrates the build; the pinned rustc host defines its ABI. string(REGEX MATCH "host: ([^\r\n]+)" _rustc_host_match "${_rustc_verbose}") set(_rustc_host "${CMAKE_MATCH_1}") string(REGEX MATCH "release: ([^\r\n]+)" _release_match "${_rustc_verbose}") set(_rustc_release "${CMAKE_MATCH_1}") - if(_cargo_host STREQUAL "" OR _rustc_host STREQUAL "" OR _rustc_release STREQUAL "") - message(FATAL_ERROR "Could not parse Cargo/rustc verbose version output from ${_cargo} and ${_rustc}") - endif() - if(NOT _cargo_host STREQUAL _rustc_host) + if(_rustc_host STREQUAL "" OR _rustc_release STREQUAL "") message(FATAL_ERROR - "Cargo host ${_cargo_host} does not match rustc host " - "${_rustc_host}; ensure both resolve from the same toolchain") + "Could not parse rustc host and release from ${_rustc}:\n${_rustc_verbose}") endif() + string(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" _rustc_semver "${_rustc_release}") - if(_rustc_semver VERSION_LESS "1.95.0") - message(FATAL_ERROR "Vortex requires rustc 1.95.0 or newer; found ${_rustc_release}") + if(_rustc_semver STREQUAL "") + message(FATAL_ERROR + "Could not parse rustc release ${_rustc_release} from ${_rustc}:\n${_rustc_verbose}") + elseif("${_rustc_semver}" VERSION_LESS "${_minimum_version}") + message(FATAL_ERROR + "Vortex requires rustc ${_minimum_version} or newer; found ${_rustc_release}") endif() string(REGEX MATCH "^[^\r\n]+" _cargo_version_line "${_cargo_verbose}") @@ -206,11 +212,10 @@ function(_vortex_find_rust_tools workspace_root) set(VORTEX_RESOLVED_RUSTC_HOST "${_rustc_host}" PARENT_SCOPE) endfunction() -# Resolve configured Apple SDK details and the effective macOS deployment target; -# return empty values on non-Apple platforms. -function(_vortex_resolve_apple_settings root_output deployment_output) - if(NOT APPLE) - set(${root_output} "" PARENT_SCOPE) +# Resolve the macOS SDK and deployment target needed by Cargo-built native code. +function(_vortex_resolve_apple_settings sdkroot_output deployment_output) + if(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(${sdkroot_output} "" PARENT_SCOPE) set(${deployment_output} "" PARENT_SCOPE) return() endif() @@ -223,7 +228,13 @@ function(_vortex_resolve_apple_settings root_output deployment_output) endif() file(REAL_PATH "${CMAKE_OSX_SYSROOT}" _apple_sdkroot) else() - find_program(_xcrun NAMES xcrun REQUIRED NO_CACHE) + # Avoid an inherited scratch value suppressing the host-tool lookup. + set(_xcrun "_xcrun-NOTFOUND") + find_program(_xcrun + NAMES xcrun + REQUIRED + NO_CMAKE_FIND_ROOT_PATH + NO_CACHE) execute_process( COMMAND "${_xcrun}" --sdk "${CMAKE_OSX_SYSROOT}" --show-sdk-path OUTPUT_VARIABLE _apple_sdkroot @@ -238,19 +249,29 @@ function(_vortex_resolve_apple_settings root_output deployment_output) file(REAL_PATH "${_apple_sdkroot}" _apple_sdkroot) endif() - _vortex_reject_semicolon("Resolved Apple SDK root" "${_apple_sdkroot}") - message(STATUS "Vortex Apple SDK: ${_apple_sdkroot}") + # A Darwin system name also appears in some Apple device toolchains. + # Restrict the Rust darwin target to an actual macOS SDK. + get_filename_component(_apple_sdk_name "${_apple_sdkroot}" NAME) + if(NOT _apple_sdk_name MATCHES + "^MacOSX([0-9]+(\\.[0-9]+)*)?\\.sdk$") + message(FATAL_ERROR + "CMAKE_OSX_SYSROOT must resolve to a macOS SDK; found " + "${_apple_sdkroot}") + endif() + + _vortex_reject_semicolon("Resolved macOS SDK root" "${_apple_sdkroot}") + message(STATUS "Vortex macOS SDK: ${_apple_sdkroot}") endif() set(_deployment_target "${CMAKE_OSX_DEPLOYMENT_TARGET}") if(NOT _deployment_target) - # Match Rust and Cargo-built native code to the compiler's effective - # default instead of guessing from the host OS or Xcode version. - _vortex_compiler_command(C _compiler_command) + # Use the C++ compiler's effective default rather than inferring one from + # the host OS or Xcode version. + _vortex_compiler_command(CXX _compiler_command) if(_apple_sdkroot) list(APPEND _compiler_command -isysroot "${_apple_sdkroot}") endif() - list(APPEND _compiler_command -dM -E -x c /dev/null) + list(APPEND _compiler_command -dM -E -x c++ /dev/null) execute_process( COMMAND ${_compiler_command} OUTPUT_VARIABLE _compiler_defines @@ -267,12 +288,20 @@ function(_vortex_resolve_apple_settings root_output deployment_output) set(_deployment_code "${CMAKE_MATCH_1}") if(_deployment_code STREQUAL "") message(FATAL_ERROR - "The C compiler did not report a default macOS deployment " + "The C++ compiler did not report a default macOS deployment " "target; set CMAKE_OSX_DEPLOYMENT_TARGET") endif() - math(EXPR _deployment_major "${_deployment_code} / 10000") - math(EXPR _deployment_minor "(${_deployment_code} / 100) % 100") - math(EXPR _deployment_patch "${_deployment_code} % 100") + + # Apple encoded 10.9.0 as 1090; macOS 10.10 and later use MMmmpp. + if(_deployment_code LESS 10000) + math(EXPR _deployment_major "${_deployment_code} / 100") + math(EXPR _deployment_minor "(${_deployment_code} / 10) % 10") + math(EXPR _deployment_patch "${_deployment_code} % 10") + else() + math(EXPR _deployment_major "${_deployment_code} / 10000") + math(EXPR _deployment_minor "(${_deployment_code} / 100) % 100") + math(EXPR _deployment_patch "${_deployment_code} % 100") + endif() set(_deployment_target "${_deployment_major}.${_deployment_minor}") if(NOT _deployment_patch EQUAL 0) string(APPEND _deployment_target ".${_deployment_patch}") @@ -281,38 +310,42 @@ function(_vortex_resolve_apple_settings root_output deployment_output) _vortex_reject_semicolon("macOS deployment target" "${_deployment_target}") message(STATUS "Vortex macOS deployment target: ${_deployment_target}") - set(${root_output} "${_apple_sdkroot}" PARENT_SCOPE) + set(${sdkroot_output} "${_apple_sdkroot}" PARENT_SCOPE) set(${deployment_output} "${_deployment_target}" PARENT_SCOPE) endfunction() -# Select a supported native Rust target for CMake's platform and architecture, -# then validate it against the rustc, C, and C++ targets. +# Select a supported native Rust target and verify the Rust, C, and C++ ABIs. function(_vortex_resolve_native_target output) - # Native C dependencies built under Cargo must share the CMake host ABI. - # Reject cross builds explicitly rather than implying support from a plausible - # triple. + # The target mapping assumes every compiler produces code for the build host. if(CMAKE_CROSSCOMPILING) message(FATAL_ERROR - "Vortex's initial CMake integration supports native builds only; " + "Vortex's CMake integration supports native builds only; " "CMAKE_CROSSCOMPILING is true") endif() + # Cargo-built native code cannot inherit CMake's generic compile sysroot. + # CMAKE_SYSROOT_LINK remains valid because CMake owns the final native link. + if(NOT "${CMAKE_SYSROOT}" STREQUAL "" OR + NOT "${CMAKE_SYSROOT_COMPILE}" STREQUAL "") + message(FATAL_ERROR + "Vortex does not support CMAKE_SYSROOT or CMAKE_SYSROOT_COMPILE; " + "generic compile sysroots cannot be applied consistently to " + "Cargo-built code. On macOS, use CMAKE_OSX_SYSROOT instead") + endif() - # Apple can override the generic processor selection, but one Rust static - # archive cannot represent a multi-architecture universal binary. + # This integration builds one native Cargo archive and supports only arm64 + # on macOS, so reject x86_64 and multi-architecture lists. set(_processor "${CMAKE_SYSTEM_PROCESSOR}") - if(APPLE AND CMAKE_OSX_ARCHITECTURES) - list(LENGTH CMAKE_OSX_ARCHITECTURES _architecture_count) - if(NOT _architecture_count EQUAL 1) + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_OSX_ARCHITECTURES) + if(NOT "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "arm64") message(FATAL_ERROR - "Vortex does not support Apple universal binaries; set " - "exactly one CMAKE_OSX_ARCHITECTURES value") + "CMAKE_OSX_ARCHITECTURES must be exactly arm64; CMake selected " + "${CMAKE_OSX_ARCHITECTURES}") endif() - list(GET CMAKE_OSX_ARCHITECTURES 0 _processor) + set(_processor "arm64") endif() _vortex_normalize_arch("${_processor}" _architecture) - # Keep this mapping closed: every accepted triple has a separately allowlisted - # native static-link manifest. + # Every accepted target has an allowlisted native static-link manifest. if(CMAKE_SYSTEM_NAME STREQUAL "Linux") if(_architecture STREQUAL "x86_64") set(_rust_target "x86_64-unknown-linux-gnu") @@ -320,29 +353,27 @@ function(_vortex_resolve_native_target output) set(_rust_target "aarch64-unknown-linux-gnu") else() message(FATAL_ERROR - "Vortex supports native Linux x86_64 and aarch64 only; CMake selected ${_processor}") + "Vortex supports native Linux x86_64 and aarch64 only; " + "CMake selected ${_processor}") endif() - elseif(APPLE) - if(_architecture STREQUAL "x86_64") - set(_rust_target "x86_64-apple-darwin") - elseif(_architecture STREQUAL "aarch64") - set(_rust_target "aarch64-apple-darwin") - else() + elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + if(NOT _architecture STREQUAL "aarch64") message(FATAL_ERROR - "Vortex standalone macOS builds support x86_64 and arm64 only; " + "Vortex macOS development supports arm64 only; " "CMake selected ${_processor}") endif() + set(_rust_target "aarch64-apple-darwin") message(STATUS - "Vortex macOS support is for standalone development only; " - "it is not a supported cuDF integration target") + "Vortex macOS support is development-only and is not a supported " + "cuDF integration target") else() message(FATAL_ERROR - "Vortex's initial CMake integration supports native Linux and " - "standalone macOS; CMake selected ${CMAKE_SYSTEM_NAME}") + "Vortex's CMake integration supports native Linux and development-only " + "macOS; CMake selected ${CMAKE_SYSTEM_NAME}") endif() - if(NOT VORTEX_RESOLVED_RUSTC_HOST STREQUAL _rust_target) + if(NOT "${VORTEX_RESOLVED_RUSTC_HOST}" STREQUAL "${_rust_target}") message(FATAL_ERROR "Vortex supports native Rust builds only: rustc host is " "${VORTEX_RESOLVED_RUSTC_HOST}, but CMake requires " diff --git a/lang/cpp/cmake/SystemDependencies.cmake b/lang/cpp/cmake/SystemDependencies.cmake index 8e59b24ec8d..9e620e647e6 100644 --- a/lang/cpp/cmake/SystemDependencies.cmake +++ b/lang/cpp/cmake/SystemDependencies.cmake @@ -23,7 +23,7 @@ function(_vortex_attach_system_dependencies target rust_target) m ${CMAKE_DL_LIBS} c) - elseif(rust_target MATCHES "^(x86_64|aarch64)-apple-darwin$") + elseif(rust_target STREQUAL "aarch64-apple-darwin") # The public Vortex target is C++, whose driver adds libc++ and # libSystem. Publish only additional framework/library requirements. find_library(_vortex_core_foundation CoreFoundation REQUIRED NO_CACHE) diff --git a/lang/cpp/examples/reader.cpp b/lang/cpp/examples/reader.cpp index 5cf331dffc5..a447ec6917b 100644 --- a/lang/cpp/examples/reader.cpp +++ b/lang/cpp/examples/reader.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors +#include #include #include diff --git a/lang/cpp/tests/CMakeLists.txt b/lang/cpp/tests/CMakeLists.txt index 8d6f1501850..5d4bb9c3019 100644 --- a/lang/cpp/tests/CMakeLists.txt +++ b/lang/cpp/tests/CMakeLists.txt @@ -22,3 +22,10 @@ target_link_libraries(vortex_cxx_test PRIVATE nanoarrow_shared) catch_discover_tests(vortex_cxx_test) + +# Validate the private CMake/Rust integration without invoking Cargo. +add_test(NAME vortex_cmake_rust_toolchain + COMMAND "${CMAKE_COMMAND}" + "-DVORTEX_CPP_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/.." + "-DVORTEX_TEST_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/cmake" + -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/RustToolchainTests.cmake") diff --git a/lang/cpp/tests/cmake/RustToolchainCase.cmake b/lang/cpp/tests/cmake/RustToolchainCase.cmake new file mode 100644 index 00000000000..2381c8bdc6f --- /dev/null +++ b/lang/cpp/tests/cmake/RustToolchainCase.cmake @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright the Vortex contributors + +# Runs one RustToolchain.cmake scenario in an isolated CMake process. + +cmake_minimum_required(VERSION 3.28) + +foreach(_required IN ITEMS + TEST_BINARY_DIR + TEST_OPERATION + VORTEX_CPP_SOURCE_DIR) + if(NOT DEFINED ${_required} OR "${${_required}}" STREQUAL "") + message(FATAL_ERROR "RustToolchainCase.cmake requires ${_required}") + endif() +endforeach() + +include("${VORTEX_CPP_SOURCE_DIR}/cmake/RustToolchain.cmake") + +function(_make_fake_compiler output) + set(_compiler "${TEST_BINARY_DIR}/compiler") + file(WRITE "${_compiler}" + "#!/bin/sh\nprintf '%s\\n' '${TEST_COMPILER_OUTPUT}'\n") + file(CHMOD "${_compiler}" + PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) + set(${output} "${_compiler}" PARENT_SCOPE) +endfunction() + +if(TEST_OPERATION STREQUAL "VALIDATE_COMPILER") + _make_fake_compiler(_compiler) + set(CMAKE_SYSTEM_NAME "${TEST_SYSTEM_NAME}") + set(CMAKE_OSX_ARCHITECTURES "${TEST_OSX_ARCHITECTURES}") + set(CMAKE_C_COMPILER "${_compiler}") + _vortex_validate_native_compiler(C "${TEST_RUST_TARGET}") + +elseif(TEST_OPERATION STREQUAL "RESOLVE_NATIVE_TARGET") + _make_fake_compiler(_compiler) + set(CMAKE_CROSSCOMPILING OFF) + set(CMAKE_SYSTEM_NAME "${TEST_SYSTEM_NAME}") + set(CMAKE_SYSTEM_PROCESSOR "${TEST_PROCESSOR}") + set(CMAKE_OSX_ARCHITECTURES "${TEST_OSX_ARCHITECTURES}") + set(CMAKE_SYSROOT "${TEST_SYSROOT}") + set(CMAKE_SYSROOT_COMPILE "${TEST_SYSROOT_COMPILE}") + set(CMAKE_C_COMPILER "${_compiler}") + set(CMAKE_CXX_COMPILER "${_compiler}") + set(VORTEX_RESOLVED_RUSTC_HOST "${TEST_RUST_HOST}") + + _vortex_resolve_native_target(_rust_target) + if(NOT "${TEST_EXPECTED_TARGET}" STREQUAL "" AND + NOT "${_rust_target}" STREQUAL "${TEST_EXPECTED_TARGET}") + message(FATAL_ERROR + "Expected Rust target ${TEST_EXPECTED_TARGET}; got ${_rust_target}") + endif() + +elseif(TEST_OPERATION STREQUAL "RESOLVE_PROGRAM") + set(_tool_dir "${TEST_BINARY_DIR}/bin") + set(_tool "${_tool_dir}/cargo") + file(MAKE_DIRECTORY "${_tool_dir}") + file(WRITE "${_tool}" "#!/bin/sh\nprintf '%s\\n' 'cargo 99.0.0'\n") + file(CHMOD "${_tool}" + PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) + + # An inherited find_program result must not bypass the local lookup. + set(CMAKE_PROGRAM_PATH "${_tool_dir}") + set(_program "/bin/false") + _vortex_resolve_rust_program("${VORTEX_CPP_SOURCE_DIR}" cargo _resolved_tool) + file(REAL_PATH "${_tool}" _expected_tool) + if(NOT "${_resolved_tool}" STREQUAL "${_expected_tool}") + message(FATAL_ERROR + "Expected Cargo at ${_expected_tool}; got ${_resolved_tool}") + endif() + +elseif(TEST_OPERATION STREQUAL "RESOLVE_APPLE_SETTINGS") + set(CMAKE_SYSTEM_NAME "Darwin") + set(CMAKE_OSX_DEPLOYMENT_TARGET "${TEST_OSX_DEPLOYMENT_TARGET}") + if(TEST_SDK_KIND STREQUAL "macos") + set(CMAKE_OSX_SYSROOT "${TEST_BINARY_DIR}/MacOSX14.4.sdk") + file(MAKE_DIRECTORY "${CMAKE_OSX_SYSROOT}") + elseif(TEST_SDK_KIND STREQUAL "iphone") + set(CMAKE_OSX_SYSROOT "${TEST_BINARY_DIR}/iPhoneOS17.4.sdk") + file(MAKE_DIRECTORY "${CMAKE_OSX_SYSROOT}") + else() + set(CMAKE_OSX_SYSROOT "") + endif() + + _make_fake_compiler(_compiler) + set(CMAKE_CXX_COMPILER "${_compiler}") + _vortex_resolve_apple_settings(_sdkroot _deployment_target) + if(NOT "${TEST_EXPECTED_DEPLOYMENT}" STREQUAL "" AND + NOT "${_deployment_target}" STREQUAL "${TEST_EXPECTED_DEPLOYMENT}") + message(FATAL_ERROR + "Expected macOS deployment target ${TEST_EXPECTED_DEPLOYMENT}; " + "got ${_deployment_target}") + endif() + +else() + message(FATAL_ERROR "Unknown Rust toolchain test operation: ${TEST_OPERATION}") +endif() diff --git a/lang/cpp/tests/cmake/RustToolchainTests.cmake b/lang/cpp/tests/cmake/RustToolchainTests.cmake new file mode 100644 index 00000000000..d958542b3a0 --- /dev/null +++ b/lang/cpp/tests/cmake/RustToolchainTests.cmake @@ -0,0 +1,196 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright the Vortex contributors + +# Exercises Rust tool lookup and native ABI validation without building Cargo. + +cmake_minimum_required(VERSION 3.28) + +foreach(_required IN ITEMS + VORTEX_CPP_SOURCE_DIR + VORTEX_TEST_BINARY_DIR) + if(NOT DEFINED ${_required} OR "${${_required}}" STREQUAL "") + message(FATAL_ERROR "RustToolchainTests.cmake requires ${_required}") + endif() +endforeach() + +set(_case_script "${CMAKE_CURRENT_LIST_DIR}/RustToolchainCase.cmake") + +function(_run_case name) + set(_one_value_arguments + COMPILER_OUTPUT + EXPECTED_DEPLOYMENT + EXPECTED_ERROR + EXPECTED_TARGET + OPERATION + OSX_ARCHITECTURES + OSX_DEPLOYMENT_TARGET + PROCESSOR + RUST_HOST + RUST_TARGET + SDK_KIND + SYSTEM_NAME + SYSROOT + SYSROOT_COMPILE) + cmake_parse_arguments(PARSE_ARGV 1 CASE "" "${_one_value_arguments}" "") + if(CASE_UNPARSED_ARGUMENTS) + message(FATAL_ERROR + "Unexpected arguments for ${name}: ${CASE_UNPARSED_ARGUMENTS}") + endif() + if(NOT CASE_OPERATION) + message(FATAL_ERROR "Toolchain test ${name} requires OPERATION") + endif() + + set(_binary_dir "${VORTEX_TEST_BINARY_DIR}/${name}") + file(REMOVE_RECURSE "${_binary_dir}") + file(MAKE_DIRECTORY "${_binary_dir}") + set(_command + "${CMAKE_COMMAND}" + "-DTEST_BINARY_DIR=${_binary_dir}" + "-DTEST_COMPILER_OUTPUT=${CASE_COMPILER_OUTPUT}" + "-DTEST_EXPECTED_DEPLOYMENT=${CASE_EXPECTED_DEPLOYMENT}" + "-DTEST_EXPECTED_TARGET=${CASE_EXPECTED_TARGET}" + "-DTEST_OPERATION=${CASE_OPERATION}" + "-DTEST_OSX_ARCHITECTURES=${CASE_OSX_ARCHITECTURES}" + "-DTEST_OSX_DEPLOYMENT_TARGET=${CASE_OSX_DEPLOYMENT_TARGET}" + "-DTEST_PROCESSOR=${CASE_PROCESSOR}" + "-DTEST_RUST_HOST=${CASE_RUST_HOST}" + "-DTEST_RUST_TARGET=${CASE_RUST_TARGET}" + "-DTEST_SDK_KIND=${CASE_SDK_KIND}" + "-DTEST_SYSTEM_NAME=${CASE_SYSTEM_NAME}" + "-DTEST_SYSROOT=${CASE_SYSROOT}" + "-DTEST_SYSROOT_COMPILE=${CASE_SYSROOT_COMPILE}" + "-DVORTEX_CPP_SOURCE_DIR=${VORTEX_CPP_SOURCE_DIR}" + -P "${_case_script}") + execute_process( + COMMAND ${_command} + OUTPUT_VARIABLE _output + ERROR_VARIABLE _error + RESULT_VARIABLE _result) + string(CONCAT _log "${_output}" "${_error}") + + if(CASE_EXPECTED_ERROR) + if(_result EQUAL 0) + message(FATAL_ERROR + "Toolchain test ${name} unexpectedly succeeded:\n${_log}") + elseif(NOT _log MATCHES "${CASE_EXPECTED_ERROR}") + message(FATAL_ERROR + "Toolchain test ${name} failed for the wrong reason. Expected " + "'${CASE_EXPECTED_ERROR}' in:\n${_log}") + endif() + elseif(NOT _result EQUAL 0) + message(FATAL_ERROR "Toolchain test ${name} failed:\n${_log}") + endif() + + message(STATUS "Passed Rust toolchain case: ${name}") +endfunction() + +_run_case(linux_x86_64 + OPERATION VALIDATE_COMPILER + COMPILER_OUTPUT x86_64-linux-gnu + RUST_TARGET x86_64-unknown-linux-gnu) +_run_case(linux_aarch64 + OPERATION VALIDATE_COMPILER + COMPILER_OUTPUT aarch64-unknown-linux-gnu + RUST_TARGET aarch64-unknown-linux-gnu) +_run_case(linux_vendor + OPERATION VALIDATE_COMPILER + COMPILER_OUTPUT x86_64-redhat-linux + RUST_TARGET x86_64-unknown-linux-gnu) +_run_case(macos_darwin + OPERATION VALIDATE_COMPILER + COMPILER_OUTPUT arm64-apple-darwin25.6.0 + RUST_TARGET aarch64-apple-darwin) +_run_case(macos_macosx + OPERATION VALIDATE_COMPILER + COMPILER_OUTPUT arm64-apple-macosx14.0 + RUST_TARGET aarch64-apple-darwin) + +foreach(_target IN ITEMS + x86_64-linux-gnux32 + x86_64-unknown-linux-musl + x86_64-linux-android + x86_64-linux-uclibc) + _run_case(reject_${_target} + OPERATION VALIDATE_COMPILER + COMPILER_OUTPUT "${_target}" + RUST_TARGET x86_64-unknown-linux-gnu + EXPECTED_ERROR "Linux ABI") +endforeach() +_run_case(reject_ios + OPERATION VALIDATE_COMPILER + COMPILER_OUTPUT arm64-apple-ios17.0 + RUST_TARGET aarch64-apple-darwin + EXPECTED_ERROR "does not use the macOS") +_run_case(reject_catalyst + OPERATION VALIDATE_COMPILER + COMPILER_OUTPUT arm64-apple-ios13.1-macabi + RUST_TARGET aarch64-apple-darwin + EXPECTED_ERROR "does not use the macOS") +_run_case(reject_architecture_mismatch + OPERATION VALIDATE_COMPILER + COMPILER_OUTPUT aarch64-linux-gnu + RUST_TARGET x86_64-unknown-linux-gnu + EXPECTED_ERROR "does not match Vortex Rust target") + +_run_case(resolve_linux_target + OPERATION RESOLVE_NATIVE_TARGET + COMPILER_OUTPUT x86_64-linux-gnu + SYSTEM_NAME Linux + PROCESSOR amd64 + RUST_HOST x86_64-unknown-linux-gnu + EXPECTED_TARGET x86_64-unknown-linux-gnu) +_run_case(resolve_macos_target + OPERATION RESOLVE_NATIVE_TARGET + COMPILER_OUTPUT arm64-apple-darwin25.6.0 + SYSTEM_NAME Darwin + PROCESSOR arm64 + OSX_ARCHITECTURES arm64 + RUST_HOST aarch64-apple-darwin + EXPECTED_TARGET aarch64-apple-darwin) +_run_case(reject_explicit_macos_x86_64 + OPERATION RESOLVE_NATIVE_TARGET + SYSTEM_NAME Darwin + PROCESSOR x86_64 + OSX_ARCHITECTURES x86_64 + RUST_HOST x86_64-apple-darwin + EXPECTED_ERROR "must be exactly arm64") +_run_case(reject_implicit_macos_x86_64 + OPERATION RESOLVE_NATIVE_TARGET + SYSTEM_NAME Darwin + PROCESSOR x86_64 + RUST_HOST x86_64-apple-darwin + EXPECTED_ERROR "supports arm64 only") +_run_case(reject_compile_sysroot + OPERATION RESOLVE_NATIVE_TARGET + SYSTEM_NAME Linux + PROCESSOR x86_64 + RUST_HOST x86_64-unknown-linux-gnu + SYSROOT_COMPILE /tmp/vortex-sysroot + EXPECTED_ERROR "CMAKE_SYSROOT or CMAKE_SYSROOT_COMPILE") +_run_case(reject_non_macos_apple_platform + OPERATION RESOLVE_NATIVE_TARGET + SYSTEM_NAME iOS + PROCESSOR arm64 + RUST_HOST aarch64-apple-darwin + EXPECTED_ERROR "CMake selected iOS") + +_run_case(ignore_inherited_find_program_result + OPERATION RESOLVE_PROGRAM) +_run_case(resolve_macos_sdk + OPERATION RESOLVE_APPLE_SETTINGS + SDK_KIND macos + OSX_DEPLOYMENT_TARGET 14.0 + EXPECTED_DEPLOYMENT 14.0) +_run_case(reject_iphone_sdk + OPERATION RESOLVE_APPLE_SETTINGS + SDK_KIND iphone + OSX_DEPLOYMENT_TARGET 17.0 + EXPECTED_ERROR "must resolve to a macOS SDK") +_run_case(resolve_legacy_deployment_target + OPERATION RESOLVE_APPLE_SETTINGS + COMPILER_OUTPUT "#define __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ 1090" + EXPECTED_DEPLOYMENT 10.9) +_run_case(resolve_current_deployment_target + OPERATION RESOLVE_APPLE_SETTINGS + COMPILER_OUTPUT "#define __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ 101203" + EXPECTED_DEPLOYMENT 10.12.3) From 7156735b8234bcca52c717bcc9fc62adf6732cab Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 15:16:37 +0100 Subject: [PATCH 13/24] Simplify CMake Rust toolchain resolution Reduce RustToolchain.cmake to one function that finds Cargo and rustc, reads the rustc host and release, captures RUSTUP_TOOLCHAIN, and forwards the Apple SDK and deployment target. The rustc host selects the Rust target directly; SystemDependencies.cmake already rejects unsupported targets, and Cargo enforces the workspace rust-version. Replace the concrete-binary pinning by forwarding the configure-time RUSTUP_TOOLCHAIN to every Cargo build. Default the macOS deployment target to rustc's 11.0 minimum so the cc crate does not compile for the SDK version, which ld64 reports as newer than CMake's link target. Remove the compiler triple validation, platform cross-checks, SDK name resolution, and version minimums, along with the CMake test harness that only exercised them. Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- lang/cpp/README.md | 19 +- lang/cpp/cmake/CargoBuild.cmake | 8 + lang/cpp/cmake/Configure.cmake | 34 +- lang/cpp/cmake/RustToolchain.cmake | 405 ++---------------- lang/cpp/tests/CMakeLists.txt | 7 - lang/cpp/tests/cmake/RustToolchainCase.cmake | 103 ----- lang/cpp/tests/cmake/RustToolchainTests.cmake | 196 --------- 7 files changed, 75 insertions(+), 697 deletions(-) delete mode 100644 lang/cpp/tests/cmake/RustToolchainCase.cmake delete mode 100644 lang/cpp/tests/cmake/RustToolchainTests.cmake diff --git a/lang/cpp/README.md b/lang/cpp/README.md index a7741b41aeb..685c2d045a6 100644 --- a/lang/cpp/README.md +++ b/lang/cpp/README.md @@ -50,12 +50,13 @@ The build supports: - a single-config generator; unless `VORTEX_CARGO_PROFILE` is set, `Debug`, `Release`, `RelWithDebInfo`, and `MinSizeRel` map to Cargo `dev`, `release`, `release_debug`, and `release_size`, respectively, while other build types warn and use `dev`; and -- Cargo and rustc 1.95.0 or newer, with the selected target's standard library installed. +- Cargo and rustc 1.95 or newer, which Cargo enforces from the workspace `rust-version` during + the build. -CMake discovers Cargo and rustc with `find_program`. Rustup proxies are resolved from the Vortex -workspace, so they honor its `rust-toolchain.toml` and `RUSTUP_TOOLCHAIN`; concrete non-rustup -binaries are used directly. Rustc and the selected C and C++ compilers must target the same native -platform and architecture; the C and C++ compilers must support `-dumpmachine`. +CMake discovers Cargo and rustc with `find_program`; set `VORTEX_CARGO_EXECUTABLE` and +`VORTEX_RUSTC_EXECUTABLE` to override them. Rustup proxies run from the Vortex workspace, so they +honor its `rust-toolchain.toml`, and the `RUSTUP_TOOLCHAIN` value present at configure time applies +to every Cargo build until CMake is reconfigured. The rustc host selects the Rust target. Cross-compilation, Apple universal binaries, Windows, musl, multi-config generators, and shared Vortex targets are not supported. Ninja is recommended. macOS is not a supported cuDF integration @@ -85,10 +86,10 @@ standalone build: - `VORTEX_SANITIZE_RUST_STD=ON` rebuilds Rust's standard library with the selected sanitizer and requires the nightly `rust-src` component. Default: `OFF`. -The selected Cargo and rustc binaries are fixed until CMake is reconfigured. Cargo runs with the -lockfile, the selected native target and profile, and the selected FFI package's default features -disabled. Optional features such as `mimalloc` are not enabled. The integration supplies its complete -Rust flag sequence, overriding Rust flags from the environment and Cargo configuration. +Cargo runs with the lockfile, the selected native target and profile, and the selected FFI package's +default features disabled. Optional features such as `mimalloc` are not enabled. The integration +supplies its complete Rust flag sequence, overriding Rust flags from the environment and Cargo +configuration. Cargo's target cache is stored under `/cargo-target`. The Cargo target runs whenever Vortex is built, while Cargo decides whether recompilation is needed. The standard CMake clean target diff --git a/lang/cpp/cmake/CargoBuild.cmake b/lang/cpp/cmake/CargoBuild.cmake index 3246cc0d329..f9b8f7ef1b8 100644 --- a/lang/cpp/cmake/CargoBuild.cmake +++ b/lang/cpp/cmake/CargoBuild.cmake @@ -94,6 +94,14 @@ function(_vortex_make_cargo_environment support_dir target_key_lower output) "CXXFLAGS_${target_key_lower}=${_cxxflags}" "CARGO_ENCODED_RUSTFLAGS=${_rustflags}") + # Keep the toolchain selection seen at configure time, even when the + # ambient environment differs at build time. + if(VORTEX_RUSTUP_TOOLCHAIN) + list(APPEND _environment "RUSTUP_TOOLCHAIN=${VORTEX_RUSTUP_TOOLCHAIN}") + else() + list(APPEND _environment --unset=RUSTUP_TOOLCHAIN) + endif() + if(VORTEX_CUDA_ROOT) list(APPEND _environment "CUDA_PATH=${VORTEX_CUDA_ROOT}") endif() diff --git a/lang/cpp/cmake/Configure.cmake b/lang/cpp/cmake/Configure.cmake index 95688216c69..f7ae6e87e0b 100644 --- a/lang/cpp/cmake/Configure.cmake +++ b/lang/cpp/cmake/Configure.cmake @@ -312,15 +312,13 @@ block(SCOPE_FOR VARIABLES) _nvcc_executable _cuda_root) - _vortex_find_rust_tools("${_workspace_root}") + _vortex_resolve_rust_toolchain("${_workspace_root}") _vortex_resolve_sanitizer( "${_configuration}" - "${VORTEX_RESOLVED_RUSTC_RELEASE}" + "${VORTEX_RUSTC_RELEASE}" _sanitizer_compile_flag _sanitizer_rustflags _cargo_build_std) - _vortex_resolve_native_target(_rust_target) - _vortex_resolve_apple_settings(_apple_sdkroot _apple_deployment_target) if(NOT "$ENV{CARGO_ENCODED_RUSTFLAGS}" STREQUAL "" OR NOT "$ENV{RUSTFLAGS}" STREQUAL "") message(STATUS "Vortex ignores ambient Rust flags in its Cargo build") @@ -328,7 +326,7 @@ block(SCOPE_FOR VARIABLES) _vortex_encode_native_flags( "${_configuration}" - "${_apple_deployment_target}" + "${VORTEX_APPLE_DEPLOYMENT_TARGET}" "${_sanitizer_compile_flag}" _native_c_flags _native_cxx_flags) @@ -348,16 +346,26 @@ block(SCOPE_FOR VARIABLES) _native_c_compiler _native_cxx_compiler) set(_cargo_ffi_archive - "${_cargo_target_dir}/${_rust_target}/${_cargo_artifact_directory}/${_cargo_archive_name}") + "${_cargo_target_dir}/${VORTEX_RUST_TARGET}/${_cargo_artifact_directory}/${_cargo_archive_name}") set(_ffi_archive "${CMAKE_CURRENT_BINARY_DIR}/vortex-artifacts/libvortex_ffi.a") + # Each value below becomes one `-D` argument of the driver and later one + # environment entry, where a semicolon would split it. + foreach(_name IN ITEMS + VORTEX_CARGO_EXECUTABLE VORTEX_RUSTC_EXECUTABLE VORTEX_RUSTUP_TOOLCHAIN + VORTEX_APPLE_SDKROOT VORTEX_APPLE_DEPLOYMENT_TARGET + _native_c_compiler _native_cxx_compiler CMAKE_AR CMAKE_RANLIB) + _vortex_reject_semicolon("${_name}" "${${_name}}") + endforeach() + # The phony target lets Cargo own dependency tracking. Copy-if-different in # the driver prevents fresh Cargo checks from forcing downstream relinks. add_custom_target(vortex_ffi_cargo_build COMMAND "${CMAKE_COMMAND}" - "-DVORTEX_CARGO_EXECUTABLE=${VORTEX_RESOLVED_CARGO_EXECUTABLE}" - "-DVORTEX_RUSTC_EXECUTABLE=${VORTEX_RESOLVED_RUSTC_EXECUTABLE}" - "-DVORTEX_RUST_TARGET=${_rust_target}" + "-DVORTEX_CARGO_EXECUTABLE=${VORTEX_CARGO_EXECUTABLE}" + "-DVORTEX_RUSTC_EXECUTABLE=${VORTEX_RUSTC_EXECUTABLE}" + "-DVORTEX_RUSTUP_TOOLCHAIN=${VORTEX_RUSTUP_TOOLCHAIN}" + "-DVORTEX_RUST_TARGET=${VORTEX_RUST_TARGET}" "-DVORTEX_CARGO_TARGET_DIR=${_cargo_target_dir}" "-DVORTEX_CARGO_PROFILE=${_cargo_profile}" "-DVORTEX_FFI_PACKAGE=${_ffi_package}" @@ -371,8 +379,8 @@ block(SCOPE_FOR VARIABLES) "-DVORTEX_CXX_COMPILER=${_native_cxx_compiler}" "-DVORTEX_AR=${CMAKE_AR}" "-DVORTEX_RANLIB=${CMAKE_RANLIB}" - "-DVORTEX_APPLE_DEPLOYMENT_TARGET=${_apple_deployment_target}" - "-DVORTEX_APPLE_SDKROOT=${_apple_sdkroot}" + "-DVORTEX_APPLE_DEPLOYMENT_TARGET=${VORTEX_APPLE_DEPLOYMENT_TARGET}" + "-DVORTEX_APPLE_SDKROOT=${VORTEX_APPLE_SDKROOT}" -P "${CMAKE_CURRENT_LIST_DIR}/CargoBuild.cmake" BYPRODUCTS "${_ffi_archive}" COMMENT "Building the PIC Vortex FFI static archive with Cargo" @@ -386,12 +394,12 @@ block(SCOPE_FOR VARIABLES) IMPORTED_LOCATION "${_ffi_archive}" INTERFACE_INCLUDE_DIRECTORIES "${_ffi_include_dirs}") add_dependencies(vortex_ffi_static vortex_ffi_cargo_build) - _vortex_attach_system_dependencies(vortex_ffi_static "${_rust_target}") + _vortex_attach_system_dependencies(vortex_ffi_static "${VORTEX_RUST_TARGET}") if(_sanitizer_compile_flag) target_compile_options(vortex_ffi_static INTERFACE "${_sanitizer_compile_flag}") target_link_options(vortex_ffi_static INTERFACE "${_sanitizer_compile_flag}") endif() - message(STATUS "Vortex Rust target: ${_rust_target}") + message(STATUS "Vortex Rust target: ${VORTEX_RUST_TARGET}") message(STATUS "Vortex Cargo target directory: ${_cargo_target_dir}") endblock() diff --git a/lang/cpp/cmake/RustToolchain.cmake b/lang/cpp/cmake/RustToolchain.cmake index 096f2200ebb..edb3f2bb0c4 100644 --- a/lang/cpp/cmake/RustToolchain.cmake +++ b/lang/cpp/cmake/RustToolchain.cmake @@ -1,386 +1,53 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -# Selects the Rust tools and verifies that Rust, C, and C++ use a supported -# native ABI. It also resolves the macOS SDK settings needed by Cargo builds. +# Selects the Rust tools and the native Rust target for the Vortex C++ build. include_guard(GLOBAL) -include("${CMAKE_CURRENT_LIST_DIR}/Helpers.cmake") - -# Normalize supported architecture aliases and target prefixes to -# `x86_64` or `aarch64`; lowercase other values for later validation. -function(_vortex_normalize_arch input output) - string(TOLOWER "${input}" _arch) - if(_arch MATCHES "^(x86_64|amd64)(-|$)") - set(_arch "x86_64") - elseif(_arch MATCHES "^(aarch64|arm64)(-|$)") - set(_arch "aarch64") - endif() - set(${output} "${_arch}" PARENT_SCOPE) -endfunction() - -# Assemble the compiler and target-selection arguments needed by probes. -function(_vortex_compiler_command language output) - set(_command "${CMAKE_${language}_COMPILER}") - if(CMAKE_${language}_COMPILER_ARG1) - # Preserve arguments CMake treats as part of the compiler command. - separate_arguments(_compiler_arg1 NATIVE_COMMAND "${CMAKE_${language}_COMPILER_ARG1}") - list(APPEND _command ${_compiler_arg1}) - endif() - if(CMAKE_${language}_COMPILER_TARGET AND - CMAKE_${language}_COMPILER_ID MATCHES "^(AppleClang|Clang)$") - # Make Clang report the target explicitly selected by CMake. - list(APPEND _command "--target=${CMAKE_${language}_COMPILER_TARGET}") - endif() - if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_OSX_ARCHITECTURES) - # Make AppleClang report CMake's selected arm64 architecture. - list(APPEND _command -arch "${CMAKE_OSX_ARCHITECTURES}") - endif() - set(${output} "${_command}" PARENT_SCOPE) -endfunction() - -# Require the selected compiler to use the Rust target's architecture and ABI. -function(_vortex_validate_native_compiler language rust_target) - _vortex_compiler_command("${language}" _compiler_command) - list(APPEND _compiler_command -dumpmachine) - execute_process( - COMMAND ${_compiler_command} - OUTPUT_VARIABLE _compiler_target - ERROR_VARIABLE _compiler_error - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE _compiler_result) - if(NOT _compiler_result EQUAL 0 OR _compiler_target STREQUAL "") - message(FATAL_ERROR - "Could not determine the target of the CMake ${language} compiler: " - "${_compiler_error}") - endif() - - string(TOLOWER "${_compiler_target}" _compiler_target_lower) - _vortex_normalize_arch("${rust_target}" _rust_arch) - _vortex_normalize_arch("${_compiler_target_lower}" _compiler_arch) - if(NOT "${_compiler_arch}" STREQUAL "${_rust_arch}") - message(FATAL_ERROR - "CMake ${language} compiler target ${_compiler_target} does not " - "match Vortex Rust target ${rust_target}") - endif() - - if(rust_target MATCHES "-unknown-linux-gnu$") - # GNU distro triples may omit `gnu`, as in `x86_64-redhat-linux`, while - # incompatible ABIs add a different suffix such as `musl` or `gnux32`. - if(NOT _compiler_target_lower MATCHES "-linux(-gnu)?$") - message(FATAL_ERROR - "CMake ${language} compiler target ${_compiler_target} does not " - "use the GNU Linux ABI required by Rust target ${rust_target}") - endif() - elseif(rust_target MATCHES "-apple-darwin$" AND - NOT _compiler_target_lower MATCHES - "-apple-(darwin|macosx)([0-9]+(\\.[0-9]+)*)?$") - message(FATAL_ERROR - "CMake ${language} compiler target ${_compiler_target} does not " - "use the macOS ABI required by Rust target ${rust_target}") - endif() -endfunction() - -# Pin a host Rust tool, resolving the workspace selection behind a rustup proxy. -function(_vortex_resolve_rust_program workspace_root program_name output) - # find_program skips its search when this inherited scratch name is set. - set(_program "_program-NOTFOUND") - find_program(_program - NAMES "${program_name}" - NO_CMAKE_FIND_ROOT_PATH - NO_CACHE) - if(NOT _program) - message(FATAL_ERROR - "Could not find ${program_name} in CMake's program search path") - endif() - - # Rustup proxies may be hardlinks. RUSTUP_FORCE_ARG0 is rustup's multicall - # override, so detection does not depend on a filename or symlink target. - execute_process( - COMMAND "${CMAKE_COMMAND}" -E env RUSTUP_FORCE_ARG0=rustup "${_program}" --version - WORKING_DIRECTORY "${workspace_root}" - OUTPUT_VARIABLE _proxy_probe_output - ERROR_VARIABLE _proxy_probe_error - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE _proxy_probe_result) - if(NOT _proxy_probe_result EQUAL 0) - message(FATAL_ERROR - "Failed to inspect ${program_name} at ${_program} " - "(${_proxy_probe_result}): ${_proxy_probe_error}\n${_proxy_probe_output}") - endif() - - set(_resolved_program "${_program}") - set(_is_rustup_proxy OFF) - if(_proxy_probe_output MATCHES "^rustup [0-9]+\\.[0-9]+") - set(_is_rustup_proxy ON) - # Resolve from the workspace so rustup applies the active environment, - # directory, and toolchain-file selection before CMake pins the binary. - execute_process( - COMMAND "${CMAKE_COMMAND}" -E env - RUSTUP_FORCE_ARG0=rustup "${_program}" which "${program_name}" - WORKING_DIRECTORY "${workspace_root}" - OUTPUT_VARIABLE _resolved_program - ERROR_VARIABLE _rustup_error - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE _rustup_result) - if(NOT _rustup_result EQUAL 0) - message(FATAL_ERROR - "rustup proxy ${_program} could not resolve active ${program_name} " - "for ${workspace_root} (${_rustup_result}): ${_rustup_error}\n" - "${_resolved_program}") - endif() - endif() - - if(NOT EXISTS "${_resolved_program}" OR IS_DIRECTORY "${_resolved_program}") - message(FATAL_ERROR - "Resolved ${program_name} executable is not a file: ${_resolved_program}") - endif() - get_filename_component(_resolved_program "${_resolved_program}" REALPATH) - _vortex_reject_semicolon("Resolved ${program_name} executable" "${_resolved_program}") - if(_is_rustup_proxy) - message(STATUS - "Vortex resolved rustup ${program_name} proxy ${_program} to ${_resolved_program}") - endif() - set(${output} "${_resolved_program}" PARENT_SCOPE) -endfunction() - -# Resolve and validate the concrete Cargo and rustc binaries used by the build. -function(_vortex_find_rust_tools workspace_root) - set(_minimum_version "1.95.0") - _vortex_resolve_rust_program("${workspace_root}" cargo _cargo) - _vortex_resolve_rust_program("${workspace_root}" rustc _rustc) - - execute_process( - COMMAND "${_cargo}" -vV - WORKING_DIRECTORY "${workspace_root}" - OUTPUT_VARIABLE _cargo_verbose - ERROR_VARIABLE _cargo_error - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE _cargo_result) - if(NOT _cargo_result EQUAL 0) - message(FATAL_ERROR "Failed to run Cargo at ${_cargo}: ${_cargo_error}") +# Sets VORTEX_RUST_TARGET, VORTEX_RUSTC_RELEASE, VORTEX_RUSTUP_TOOLCHAIN, +# VORTEX_APPLE_SDKROOT, and VORTEX_APPLE_DEPLOYMENT_TARGET in the caller's +# scope. The cache entries VORTEX_CARGO_EXECUTABLE and VORTEX_RUSTC_EXECUTABLE +# hold the selected tools. +function(_vortex_resolve_rust_toolchain workspace_root) + # Cargo builds for the rustc host, so C++ must be built for the same machine. + if(CMAKE_CROSSCOMPILING) + message(FATAL_ERROR "Vortex's CMake integration supports native builds only") endif() + # Rustup proxies honor the workspace toolchain file when run from the + # workspace. RUSTUP_TOOLCHAIN is captured so Cargo builds keep this selection. + find_program(VORTEX_CARGO_EXECUTABLE NAMES cargo REQUIRED) + find_program(VORTEX_RUSTC_EXECUTABLE NAMES rustc REQUIRED) execute_process( - COMMAND "${_rustc}" -vV + COMMAND "${VORTEX_RUSTC_EXECUTABLE}" -vV WORKING_DIRECTORY "${workspace_root}" OUTPUT_VARIABLE _rustc_verbose - ERROR_VARIABLE _rustc_error - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE _rustc_result) - if(NOT _rustc_result EQUAL 0) - message(FATAL_ERROR "Failed to run rustc at ${_rustc}: ${_rustc_error}") - endif() - - string(REGEX MATCH "^cargo ([0-9]+\\.[0-9]+\\.[0-9]+)" _cargo_version_match "${_cargo_verbose}") - set(_cargo_semver "${CMAKE_MATCH_1}") - if(_cargo_semver STREQUAL "") - message(FATAL_ERROR - "Could not parse Cargo version output from ${_cargo}:\n${_cargo_verbose}") - elseif("${_cargo_semver}" VERSION_LESS "${_minimum_version}") - message(FATAL_ERROR - "Vortex requires Cargo ${_minimum_version} or newer; found:\n${_cargo_verbose}") - endif() - - # Cargo only orchestrates the build; the pinned rustc host defines its ABI. - string(REGEX MATCH "host: ([^\r\n]+)" _rustc_host_match "${_rustc_verbose}") - set(_rustc_host "${CMAKE_MATCH_1}") - string(REGEX MATCH "release: ([^\r\n]+)" _release_match "${_rustc_verbose}") - set(_rustc_release "${CMAKE_MATCH_1}") - if(_rustc_host STREQUAL "" OR _rustc_release STREQUAL "") - message(FATAL_ERROR - "Could not parse rustc host and release from ${_rustc}:\n${_rustc_verbose}") - endif() - - string(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" _rustc_semver "${_rustc_release}") - if(_rustc_semver STREQUAL "") - message(FATAL_ERROR - "Could not parse rustc release ${_rustc_release} from ${_rustc}:\n${_rustc_verbose}") - elseif("${_rustc_semver}" VERSION_LESS "${_minimum_version}") - message(FATAL_ERROR - "Vortex requires rustc ${_minimum_version} or newer; found ${_rustc_release}") - endif() - - string(REGEX MATCH "^[^\r\n]+" _cargo_version_line "${_cargo_verbose}") - message(STATUS "Vortex Cargo: ${_cargo_version_line} (${_cargo})") - message(STATUS "Vortex rustc: ${_rustc_release}, host ${_rustc_host} (${_rustc})") - - set(VORTEX_RESOLVED_CARGO_EXECUTABLE "${_cargo}" PARENT_SCOPE) - set(VORTEX_RESOLVED_RUSTC_EXECUTABLE "${_rustc}" PARENT_SCOPE) - set(VORTEX_RESOLVED_RUSTC_RELEASE "${_rustc_release}" PARENT_SCOPE) - set(VORTEX_RESOLVED_RUSTC_HOST "${_rustc_host}" PARENT_SCOPE) -endfunction() - -# Resolve the macOS SDK and deployment target needed by Cargo-built native code. -function(_vortex_resolve_apple_settings sdkroot_output deployment_output) - if(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(${sdkroot_output} "" PARENT_SCOPE) - set(${deployment_output} "" PARENT_SCOPE) - return() - endif() - - set(_apple_sdkroot "") - if(CMAKE_OSX_SYSROOT) - if(IS_ABSOLUTE "${CMAKE_OSX_SYSROOT}") - if(NOT IS_DIRECTORY "${CMAKE_OSX_SYSROOT}") - message(FATAL_ERROR "CMAKE_OSX_SYSROOT is not a directory: ${CMAKE_OSX_SYSROOT}") - endif() - file(REAL_PATH "${CMAKE_OSX_SYSROOT}" _apple_sdkroot) - else() - # Avoid an inherited scratch value suppressing the host-tool lookup. - set(_xcrun "_xcrun-NOTFOUND") - find_program(_xcrun - NAMES xcrun - REQUIRED - NO_CMAKE_FIND_ROOT_PATH - NO_CACHE) - execute_process( - COMMAND "${_xcrun}" --sdk "${CMAKE_OSX_SYSROOT}" --show-sdk-path - OUTPUT_VARIABLE _apple_sdkroot - ERROR_VARIABLE _xcrun_error - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE _xcrun_result) - if(NOT _xcrun_result EQUAL 0 OR NOT IS_DIRECTORY "${_apple_sdkroot}") - message(FATAL_ERROR - "Could not resolve CMAKE_OSX_SYSROOT=${CMAKE_OSX_SYSROOT} " - "with xcrun: ${_xcrun_error}") - endif() - file(REAL_PATH "${_apple_sdkroot}" _apple_sdkroot) - endif() - - # A Darwin system name also appears in some Apple device toolchains. - # Restrict the Rust darwin target to an actual macOS SDK. - get_filename_component(_apple_sdk_name "${_apple_sdkroot}" NAME) - if(NOT _apple_sdk_name MATCHES - "^MacOSX([0-9]+(\\.[0-9]+)*)?\\.sdk$") - message(FATAL_ERROR - "CMAKE_OSX_SYSROOT must resolve to a macOS SDK; found " - "${_apple_sdkroot}") - endif() - - _vortex_reject_semicolon("Resolved macOS SDK root" "${_apple_sdkroot}") - message(STATUS "Vortex macOS SDK: ${_apple_sdkroot}") - endif() - + COMMAND_ERROR_IS_FATAL ANY) + string(REGEX MATCH "host: ([^\r\n]+)" _match "${_rustc_verbose}") + set(_host "${CMAKE_MATCH_1}") + string(REGEX MATCH "release: ([^\r\n]+)" _match "${_rustc_verbose}") + set(_release "${CMAKE_MATCH_1}") + message(STATUS "Vortex rustc: ${_release}, host ${_host} (${VORTEX_RUSTC_EXECUTABLE})") + + # Forward CMake's SDK to Cargo-built native code. Without an explicit + # deployment target the cc crate uses the SDK version, which can exceed + # CMake's link target and makes ld64 warn about every Cargo-built C object; + # 11.0 is rustc's minimum for aarch64-apple-darwin. + set(_sdkroot "") set(_deployment_target "${CMAKE_OSX_DEPLOYMENT_TARGET}") - if(NOT _deployment_target) - # Use the C++ compiler's effective default rather than inferring one from - # the host OS or Xcode version. - _vortex_compiler_command(CXX _compiler_command) - if(_apple_sdkroot) - list(APPEND _compiler_command -isysroot "${_apple_sdkroot}") + if(APPLE) + if(IS_DIRECTORY "${CMAKE_OSX_SYSROOT}") + set(_sdkroot "${CMAKE_OSX_SYSROOT}") endif() - list(APPEND _compiler_command -dM -E -x c++ /dev/null) - execute_process( - COMMAND ${_compiler_command} - OUTPUT_VARIABLE _compiler_defines - ERROR_VARIABLE _compiler_error - RESULT_VARIABLE _compiler_result) - if(NOT _compiler_result EQUAL 0) - message(FATAL_ERROR - "Could not determine the default macOS deployment target: " - "${_compiler_error}") + if(NOT _deployment_target) + set(_deployment_target "11.0") endif() - string(REGEX MATCH - "__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__[ \t]+([0-9]+)" - _deployment_match "${_compiler_defines}") - set(_deployment_code "${CMAKE_MATCH_1}") - if(_deployment_code STREQUAL "") - message(FATAL_ERROR - "The C++ compiler did not report a default macOS deployment " - "target; set CMAKE_OSX_DEPLOYMENT_TARGET") - endif() - - # Apple encoded 10.9.0 as 1090; macOS 10.10 and later use MMmmpp. - if(_deployment_code LESS 10000) - math(EXPR _deployment_major "${_deployment_code} / 100") - math(EXPR _deployment_minor "(${_deployment_code} / 10) % 10") - math(EXPR _deployment_patch "${_deployment_code} % 10") - else() - math(EXPR _deployment_major "${_deployment_code} / 10000") - math(EXPR _deployment_minor "(${_deployment_code} / 100) % 100") - math(EXPR _deployment_patch "${_deployment_code} % 100") - endif() - set(_deployment_target "${_deployment_major}.${_deployment_minor}") - if(NOT _deployment_patch EQUAL 0) - string(APPEND _deployment_target ".${_deployment_patch}") - endif() - endif() - _vortex_reject_semicolon("macOS deployment target" "${_deployment_target}") - message(STATUS "Vortex macOS deployment target: ${_deployment_target}") - - set(${sdkroot_output} "${_apple_sdkroot}" PARENT_SCOPE) - set(${deployment_output} "${_deployment_target}" PARENT_SCOPE) -endfunction() - -# Select a supported native Rust target and verify the Rust, C, and C++ ABIs. -function(_vortex_resolve_native_target output) - # The target mapping assumes every compiler produces code for the build host. - if(CMAKE_CROSSCOMPILING) - message(FATAL_ERROR - "Vortex's CMake integration supports native builds only; " - "CMAKE_CROSSCOMPILING is true") - endif() - # Cargo-built native code cannot inherit CMake's generic compile sysroot. - # CMAKE_SYSROOT_LINK remains valid because CMake owns the final native link. - if(NOT "${CMAKE_SYSROOT}" STREQUAL "" OR - NOT "${CMAKE_SYSROOT_COMPILE}" STREQUAL "") - message(FATAL_ERROR - "Vortex does not support CMAKE_SYSROOT or CMAKE_SYSROOT_COMPILE; " - "generic compile sysroots cannot be applied consistently to " - "Cargo-built code. On macOS, use CMAKE_OSX_SYSROOT instead") - endif() - - # This integration builds one native Cargo archive and supports only arm64 - # on macOS, so reject x86_64 and multi-architecture lists. - set(_processor "${CMAKE_SYSTEM_PROCESSOR}") - if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_OSX_ARCHITECTURES) - if(NOT "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "arm64") - message(FATAL_ERROR - "CMAKE_OSX_ARCHITECTURES must be exactly arm64; CMake selected " - "${CMAKE_OSX_ARCHITECTURES}") - endif() - set(_processor "arm64") - endif() - _vortex_normalize_arch("${_processor}" _architecture) - - # Every accepted target has an allowlisted native static-link manifest. - if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - if(_architecture STREQUAL "x86_64") - set(_rust_target "x86_64-unknown-linux-gnu") - elseif(_architecture STREQUAL "aarch64") - set(_rust_target "aarch64-unknown-linux-gnu") - else() - message(FATAL_ERROR - "Vortex supports native Linux x86_64 and aarch64 only; " - "CMake selected ${_processor}") - endif() - - elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - if(NOT _architecture STREQUAL "aarch64") - message(FATAL_ERROR - "Vortex macOS development supports arm64 only; " - "CMake selected ${_processor}") - endif() - set(_rust_target "aarch64-apple-darwin") - message(STATUS - "Vortex macOS support is development-only and is not a supported " - "cuDF integration target") - else() - message(FATAL_ERROR - "Vortex's CMake integration supports native Linux and development-only " - "macOS; CMake selected ${CMAKE_SYSTEM_NAME}") - endif() - - if(NOT "${VORTEX_RESOLVED_RUSTC_HOST}" STREQUAL "${_rust_target}") - message(FATAL_ERROR - "Vortex supports native Rust builds only: rustc host is " - "${VORTEX_RESOLVED_RUSTC_HOST}, but CMake requires " - "${_rust_target}") endif() - _vortex_validate_native_compiler(C "${_rust_target}") - _vortex_validate_native_compiler(CXX "${_rust_target}") - set(${output} "${_rust_target}" PARENT_SCOPE) + set(VORTEX_RUST_TARGET "${_host}" PARENT_SCOPE) + set(VORTEX_RUSTC_RELEASE "${_release}" PARENT_SCOPE) + set(VORTEX_RUSTUP_TOOLCHAIN "$ENV{RUSTUP_TOOLCHAIN}" PARENT_SCOPE) + set(VORTEX_APPLE_SDKROOT "${_sdkroot}" PARENT_SCOPE) + set(VORTEX_APPLE_DEPLOYMENT_TARGET "${_deployment_target}" PARENT_SCOPE) endfunction() diff --git a/lang/cpp/tests/CMakeLists.txt b/lang/cpp/tests/CMakeLists.txt index 5d4bb9c3019..8d6f1501850 100644 --- a/lang/cpp/tests/CMakeLists.txt +++ b/lang/cpp/tests/CMakeLists.txt @@ -22,10 +22,3 @@ target_link_libraries(vortex_cxx_test PRIVATE nanoarrow_shared) catch_discover_tests(vortex_cxx_test) - -# Validate the private CMake/Rust integration without invoking Cargo. -add_test(NAME vortex_cmake_rust_toolchain - COMMAND "${CMAKE_COMMAND}" - "-DVORTEX_CPP_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/.." - "-DVORTEX_TEST_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/cmake" - -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/RustToolchainTests.cmake") diff --git a/lang/cpp/tests/cmake/RustToolchainCase.cmake b/lang/cpp/tests/cmake/RustToolchainCase.cmake deleted file mode 100644 index 2381c8bdc6f..00000000000 --- a/lang/cpp/tests/cmake/RustToolchainCase.cmake +++ /dev/null @@ -1,103 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright the Vortex contributors - -# Runs one RustToolchain.cmake scenario in an isolated CMake process. - -cmake_minimum_required(VERSION 3.28) - -foreach(_required IN ITEMS - TEST_BINARY_DIR - TEST_OPERATION - VORTEX_CPP_SOURCE_DIR) - if(NOT DEFINED ${_required} OR "${${_required}}" STREQUAL "") - message(FATAL_ERROR "RustToolchainCase.cmake requires ${_required}") - endif() -endforeach() - -include("${VORTEX_CPP_SOURCE_DIR}/cmake/RustToolchain.cmake") - -function(_make_fake_compiler output) - set(_compiler "${TEST_BINARY_DIR}/compiler") - file(WRITE "${_compiler}" - "#!/bin/sh\nprintf '%s\\n' '${TEST_COMPILER_OUTPUT}'\n") - file(CHMOD "${_compiler}" - PERMISSIONS - OWNER_READ OWNER_WRITE OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE - WORLD_READ WORLD_EXECUTE) - set(${output} "${_compiler}" PARENT_SCOPE) -endfunction() - -if(TEST_OPERATION STREQUAL "VALIDATE_COMPILER") - _make_fake_compiler(_compiler) - set(CMAKE_SYSTEM_NAME "${TEST_SYSTEM_NAME}") - set(CMAKE_OSX_ARCHITECTURES "${TEST_OSX_ARCHITECTURES}") - set(CMAKE_C_COMPILER "${_compiler}") - _vortex_validate_native_compiler(C "${TEST_RUST_TARGET}") - -elseif(TEST_OPERATION STREQUAL "RESOLVE_NATIVE_TARGET") - _make_fake_compiler(_compiler) - set(CMAKE_CROSSCOMPILING OFF) - set(CMAKE_SYSTEM_NAME "${TEST_SYSTEM_NAME}") - set(CMAKE_SYSTEM_PROCESSOR "${TEST_PROCESSOR}") - set(CMAKE_OSX_ARCHITECTURES "${TEST_OSX_ARCHITECTURES}") - set(CMAKE_SYSROOT "${TEST_SYSROOT}") - set(CMAKE_SYSROOT_COMPILE "${TEST_SYSROOT_COMPILE}") - set(CMAKE_C_COMPILER "${_compiler}") - set(CMAKE_CXX_COMPILER "${_compiler}") - set(VORTEX_RESOLVED_RUSTC_HOST "${TEST_RUST_HOST}") - - _vortex_resolve_native_target(_rust_target) - if(NOT "${TEST_EXPECTED_TARGET}" STREQUAL "" AND - NOT "${_rust_target}" STREQUAL "${TEST_EXPECTED_TARGET}") - message(FATAL_ERROR - "Expected Rust target ${TEST_EXPECTED_TARGET}; got ${_rust_target}") - endif() - -elseif(TEST_OPERATION STREQUAL "RESOLVE_PROGRAM") - set(_tool_dir "${TEST_BINARY_DIR}/bin") - set(_tool "${_tool_dir}/cargo") - file(MAKE_DIRECTORY "${_tool_dir}") - file(WRITE "${_tool}" "#!/bin/sh\nprintf '%s\\n' 'cargo 99.0.0'\n") - file(CHMOD "${_tool}" - PERMISSIONS - OWNER_READ OWNER_WRITE OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE - WORLD_READ WORLD_EXECUTE) - - # An inherited find_program result must not bypass the local lookup. - set(CMAKE_PROGRAM_PATH "${_tool_dir}") - set(_program "/bin/false") - _vortex_resolve_rust_program("${VORTEX_CPP_SOURCE_DIR}" cargo _resolved_tool) - file(REAL_PATH "${_tool}" _expected_tool) - if(NOT "${_resolved_tool}" STREQUAL "${_expected_tool}") - message(FATAL_ERROR - "Expected Cargo at ${_expected_tool}; got ${_resolved_tool}") - endif() - -elseif(TEST_OPERATION STREQUAL "RESOLVE_APPLE_SETTINGS") - set(CMAKE_SYSTEM_NAME "Darwin") - set(CMAKE_OSX_DEPLOYMENT_TARGET "${TEST_OSX_DEPLOYMENT_TARGET}") - if(TEST_SDK_KIND STREQUAL "macos") - set(CMAKE_OSX_SYSROOT "${TEST_BINARY_DIR}/MacOSX14.4.sdk") - file(MAKE_DIRECTORY "${CMAKE_OSX_SYSROOT}") - elseif(TEST_SDK_KIND STREQUAL "iphone") - set(CMAKE_OSX_SYSROOT "${TEST_BINARY_DIR}/iPhoneOS17.4.sdk") - file(MAKE_DIRECTORY "${CMAKE_OSX_SYSROOT}") - else() - set(CMAKE_OSX_SYSROOT "") - endif() - - _make_fake_compiler(_compiler) - set(CMAKE_CXX_COMPILER "${_compiler}") - _vortex_resolve_apple_settings(_sdkroot _deployment_target) - if(NOT "${TEST_EXPECTED_DEPLOYMENT}" STREQUAL "" AND - NOT "${_deployment_target}" STREQUAL "${TEST_EXPECTED_DEPLOYMENT}") - message(FATAL_ERROR - "Expected macOS deployment target ${TEST_EXPECTED_DEPLOYMENT}; " - "got ${_deployment_target}") - endif() - -else() - message(FATAL_ERROR "Unknown Rust toolchain test operation: ${TEST_OPERATION}") -endif() diff --git a/lang/cpp/tests/cmake/RustToolchainTests.cmake b/lang/cpp/tests/cmake/RustToolchainTests.cmake deleted file mode 100644 index d958542b3a0..00000000000 --- a/lang/cpp/tests/cmake/RustToolchainTests.cmake +++ /dev/null @@ -1,196 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright the Vortex contributors - -# Exercises Rust tool lookup and native ABI validation without building Cargo. - -cmake_minimum_required(VERSION 3.28) - -foreach(_required IN ITEMS - VORTEX_CPP_SOURCE_DIR - VORTEX_TEST_BINARY_DIR) - if(NOT DEFINED ${_required} OR "${${_required}}" STREQUAL "") - message(FATAL_ERROR "RustToolchainTests.cmake requires ${_required}") - endif() -endforeach() - -set(_case_script "${CMAKE_CURRENT_LIST_DIR}/RustToolchainCase.cmake") - -function(_run_case name) - set(_one_value_arguments - COMPILER_OUTPUT - EXPECTED_DEPLOYMENT - EXPECTED_ERROR - EXPECTED_TARGET - OPERATION - OSX_ARCHITECTURES - OSX_DEPLOYMENT_TARGET - PROCESSOR - RUST_HOST - RUST_TARGET - SDK_KIND - SYSTEM_NAME - SYSROOT - SYSROOT_COMPILE) - cmake_parse_arguments(PARSE_ARGV 1 CASE "" "${_one_value_arguments}" "") - if(CASE_UNPARSED_ARGUMENTS) - message(FATAL_ERROR - "Unexpected arguments for ${name}: ${CASE_UNPARSED_ARGUMENTS}") - endif() - if(NOT CASE_OPERATION) - message(FATAL_ERROR "Toolchain test ${name} requires OPERATION") - endif() - - set(_binary_dir "${VORTEX_TEST_BINARY_DIR}/${name}") - file(REMOVE_RECURSE "${_binary_dir}") - file(MAKE_DIRECTORY "${_binary_dir}") - set(_command - "${CMAKE_COMMAND}" - "-DTEST_BINARY_DIR=${_binary_dir}" - "-DTEST_COMPILER_OUTPUT=${CASE_COMPILER_OUTPUT}" - "-DTEST_EXPECTED_DEPLOYMENT=${CASE_EXPECTED_DEPLOYMENT}" - "-DTEST_EXPECTED_TARGET=${CASE_EXPECTED_TARGET}" - "-DTEST_OPERATION=${CASE_OPERATION}" - "-DTEST_OSX_ARCHITECTURES=${CASE_OSX_ARCHITECTURES}" - "-DTEST_OSX_DEPLOYMENT_TARGET=${CASE_OSX_DEPLOYMENT_TARGET}" - "-DTEST_PROCESSOR=${CASE_PROCESSOR}" - "-DTEST_RUST_HOST=${CASE_RUST_HOST}" - "-DTEST_RUST_TARGET=${CASE_RUST_TARGET}" - "-DTEST_SDK_KIND=${CASE_SDK_KIND}" - "-DTEST_SYSTEM_NAME=${CASE_SYSTEM_NAME}" - "-DTEST_SYSROOT=${CASE_SYSROOT}" - "-DTEST_SYSROOT_COMPILE=${CASE_SYSROOT_COMPILE}" - "-DVORTEX_CPP_SOURCE_DIR=${VORTEX_CPP_SOURCE_DIR}" - -P "${_case_script}") - execute_process( - COMMAND ${_command} - OUTPUT_VARIABLE _output - ERROR_VARIABLE _error - RESULT_VARIABLE _result) - string(CONCAT _log "${_output}" "${_error}") - - if(CASE_EXPECTED_ERROR) - if(_result EQUAL 0) - message(FATAL_ERROR - "Toolchain test ${name} unexpectedly succeeded:\n${_log}") - elseif(NOT _log MATCHES "${CASE_EXPECTED_ERROR}") - message(FATAL_ERROR - "Toolchain test ${name} failed for the wrong reason. Expected " - "'${CASE_EXPECTED_ERROR}' in:\n${_log}") - endif() - elseif(NOT _result EQUAL 0) - message(FATAL_ERROR "Toolchain test ${name} failed:\n${_log}") - endif() - - message(STATUS "Passed Rust toolchain case: ${name}") -endfunction() - -_run_case(linux_x86_64 - OPERATION VALIDATE_COMPILER - COMPILER_OUTPUT x86_64-linux-gnu - RUST_TARGET x86_64-unknown-linux-gnu) -_run_case(linux_aarch64 - OPERATION VALIDATE_COMPILER - COMPILER_OUTPUT aarch64-unknown-linux-gnu - RUST_TARGET aarch64-unknown-linux-gnu) -_run_case(linux_vendor - OPERATION VALIDATE_COMPILER - COMPILER_OUTPUT x86_64-redhat-linux - RUST_TARGET x86_64-unknown-linux-gnu) -_run_case(macos_darwin - OPERATION VALIDATE_COMPILER - COMPILER_OUTPUT arm64-apple-darwin25.6.0 - RUST_TARGET aarch64-apple-darwin) -_run_case(macos_macosx - OPERATION VALIDATE_COMPILER - COMPILER_OUTPUT arm64-apple-macosx14.0 - RUST_TARGET aarch64-apple-darwin) - -foreach(_target IN ITEMS - x86_64-linux-gnux32 - x86_64-unknown-linux-musl - x86_64-linux-android - x86_64-linux-uclibc) - _run_case(reject_${_target} - OPERATION VALIDATE_COMPILER - COMPILER_OUTPUT "${_target}" - RUST_TARGET x86_64-unknown-linux-gnu - EXPECTED_ERROR "Linux ABI") -endforeach() -_run_case(reject_ios - OPERATION VALIDATE_COMPILER - COMPILER_OUTPUT arm64-apple-ios17.0 - RUST_TARGET aarch64-apple-darwin - EXPECTED_ERROR "does not use the macOS") -_run_case(reject_catalyst - OPERATION VALIDATE_COMPILER - COMPILER_OUTPUT arm64-apple-ios13.1-macabi - RUST_TARGET aarch64-apple-darwin - EXPECTED_ERROR "does not use the macOS") -_run_case(reject_architecture_mismatch - OPERATION VALIDATE_COMPILER - COMPILER_OUTPUT aarch64-linux-gnu - RUST_TARGET x86_64-unknown-linux-gnu - EXPECTED_ERROR "does not match Vortex Rust target") - -_run_case(resolve_linux_target - OPERATION RESOLVE_NATIVE_TARGET - COMPILER_OUTPUT x86_64-linux-gnu - SYSTEM_NAME Linux - PROCESSOR amd64 - RUST_HOST x86_64-unknown-linux-gnu - EXPECTED_TARGET x86_64-unknown-linux-gnu) -_run_case(resolve_macos_target - OPERATION RESOLVE_NATIVE_TARGET - COMPILER_OUTPUT arm64-apple-darwin25.6.0 - SYSTEM_NAME Darwin - PROCESSOR arm64 - OSX_ARCHITECTURES arm64 - RUST_HOST aarch64-apple-darwin - EXPECTED_TARGET aarch64-apple-darwin) -_run_case(reject_explicit_macos_x86_64 - OPERATION RESOLVE_NATIVE_TARGET - SYSTEM_NAME Darwin - PROCESSOR x86_64 - OSX_ARCHITECTURES x86_64 - RUST_HOST x86_64-apple-darwin - EXPECTED_ERROR "must be exactly arm64") -_run_case(reject_implicit_macos_x86_64 - OPERATION RESOLVE_NATIVE_TARGET - SYSTEM_NAME Darwin - PROCESSOR x86_64 - RUST_HOST x86_64-apple-darwin - EXPECTED_ERROR "supports arm64 only") -_run_case(reject_compile_sysroot - OPERATION RESOLVE_NATIVE_TARGET - SYSTEM_NAME Linux - PROCESSOR x86_64 - RUST_HOST x86_64-unknown-linux-gnu - SYSROOT_COMPILE /tmp/vortex-sysroot - EXPECTED_ERROR "CMAKE_SYSROOT or CMAKE_SYSROOT_COMPILE") -_run_case(reject_non_macos_apple_platform - OPERATION RESOLVE_NATIVE_TARGET - SYSTEM_NAME iOS - PROCESSOR arm64 - RUST_HOST aarch64-apple-darwin - EXPECTED_ERROR "CMake selected iOS") - -_run_case(ignore_inherited_find_program_result - OPERATION RESOLVE_PROGRAM) -_run_case(resolve_macos_sdk - OPERATION RESOLVE_APPLE_SETTINGS - SDK_KIND macos - OSX_DEPLOYMENT_TARGET 14.0 - EXPECTED_DEPLOYMENT 14.0) -_run_case(reject_iphone_sdk - OPERATION RESOLVE_APPLE_SETTINGS - SDK_KIND iphone - OSX_DEPLOYMENT_TARGET 17.0 - EXPECTED_ERROR "must resolve to a macOS SDK") -_run_case(resolve_legacy_deployment_target - OPERATION RESOLVE_APPLE_SETTINGS - COMPILER_OUTPUT "#define __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ 1090" - EXPECTED_DEPLOYMENT 10.9) -_run_case(resolve_current_deployment_target - OPERATION RESOLVE_APPLE_SETTINGS - COMPILER_OUTPUT "#define __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ 101203" - EXPECTED_DEPLOYMENT 10.12.3) From a19d1d10bcdc303ba37eeefea86b8a4bc3c8d802 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 15:41:40 +0100 Subject: [PATCH 14/24] Simplify CMake Cargo plumbing and default to Debug Reduce RustToolchain.cmake to tool discovery, the rustc host, the RUSTUP_TOOLCHAIN capture, and the macOS deployment target. Drop the nightly pre-check, since rustc rejects the nightly-only flags itself, and stop forwarding CMAKE_OSX_SYSROOT and CMAKE__COMPILER_TARGET, which only matter for cross builds. Pass the Rust, C, and C++ flags to CargoBuild.cmake as -D lists instead of support files, and encode them where the Cargo environment is built. CMake strips enclosing single quotes from -D values, so the shell-quoted strings could not cross that boundary intact. Remove the compiler wrapper for CMAKE__COMPILER_ARG1 and the no-op --no-default-features. Default standalone builds to Debug when no build type is given, and map an empty build type to Cargo's dev profile without a warning. Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- docs/getting-started/cpp.rst | 6 +- lang/cpp/CMakeLists.txt | 5 ++ lang/cpp/README.md | 14 ++-- lang/cpp/cmake/CargoBuild.cmake | 26 +++--- lang/cpp/cmake/Configure.cmake | 127 ++++++----------------------- lang/cpp/cmake/Helpers.cmake | 37 --------- lang/cpp/cmake/RustToolchain.cmake | 44 +++------- 7 files changed, 67 insertions(+), 192 deletions(-) diff --git a/docs/getting-started/cpp.rst b/docs/getting-started/cpp.rst index 364006e72ef..deb569a7564 100644 --- a/docs/getting-started/cpp.rst +++ b/docs/getting-started/cpp.rst @@ -29,9 +29,9 @@ workspace checkout; the C++ directory cannot be built from an isolated source co -DVORTEX_BUILD_EXAMPLES=ON cmake --build build/cpp --parallel -Configuration queries Cargo and rustc and validates the native target and Rust -standard library. The locked ``vortex-ffi`` compilation occurs only when the CMake -build runs, so a separate ``cargo build`` step is neither required nor recommended. +Configuration locates Cargo and rustc and takes the Rust target from the rustc +host. The locked ``vortex-ffi`` compilation occurs only when the CMake build runs, +so a separate ``cargo build`` step is neither required nor recommended. Building examples can download nanoarrow during configure; Cargo can download locked Rust dependencies during the build. diff --git a/lang/cpp/CMakeLists.txt b/lang/cpp/CMakeLists.txt index 218c39c6f36..70a9c0bac92 100644 --- a/lang/cpp/CMakeLists.txt +++ b/lang/cpp/CMakeLists.txt @@ -26,6 +26,11 @@ else() endif() endif() +# Standalone builds default to Debug; embedded builds keep the parent's choice. +if(_vortex_cpp_standalone AND NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug) +endif() + # A toolchain-file change must re-run configure so Cargo and rustc are resolved # again. Cargo itself tracks manifests, the lockfile, and Rust sources at build # time because its phony CMake target runs on every build. diff --git a/lang/cpp/README.md b/lang/cpp/README.md index 685c2d045a6..4bc0e756d6f 100644 --- a/lang/cpp/README.md +++ b/lang/cpp/README.md @@ -47,9 +47,10 @@ The build supports: - native GNU/Linux on x86_64 and aarch64; - native macOS on arm64 for standalone development; -- a single-config generator; unless `VORTEX_CARGO_PROFILE` is set, `Debug`, `Release`, - `RelWithDebInfo`, and `MinSizeRel` map to Cargo `dev`, `release`, `release_debug`, and - `release_size`, respectively, while other build types warn and use `dev`; and +- a single-config generator; standalone builds default to `Debug`, and unless + `VORTEX_CARGO_PROFILE` is set, `Debug`, `Release`, `RelWithDebInfo`, and `MinSizeRel` map to + Cargo `dev`, `release`, `release_debug`, and `release_size`, respectively, an empty build type + uses `dev`, and other build types warn and use `dev`; and - Cargo and rustc 1.95 or newer, which Cargo enforces from the workspace `rust-version` during the build. @@ -86,10 +87,9 @@ standalone build: - `VORTEX_SANITIZE_RUST_STD=ON` rebuilds Rust's standard library with the selected sanitizer and requires the nightly `rust-src` component. Default: `OFF`. -Cargo runs with the lockfile, the selected native target and profile, and the selected FFI package's -default features disabled. Optional features such as `mimalloc` are not enabled. The integration -supplies its complete Rust flag sequence, overriding Rust flags from the environment and Cargo -configuration. +Cargo runs with the lockfile and the selected native target and profile. Optional features such as +`mimalloc` are not enabled. The integration supplies its complete Rust flag sequence, overriding Rust +flags from the environment and Cargo configuration. Cargo's target cache is stored under `/cargo-target`. The Cargo target runs whenever Vortex is built, while Cargo decides whether recompilation is needed. The standard CMake clean target diff --git a/lang/cpp/cmake/CargoBuild.cmake b/lang/cpp/cmake/CargoBuild.cmake index f9b8f7ef1b8..0bf25914a64 100644 --- a/lang/cpp/cmake/CargoBuild.cmake +++ b/lang/cpp/cmake/CargoBuild.cmake @@ -6,6 +6,8 @@ cmake_minimum_required(VERSION 3.28) +include("${CMAKE_CURRENT_LIST_DIR}/Helpers.cmake") + # Configure.cmake passes these required values as `-D` arguments when the # `vortex_ffi_cargo_build` target launches this script with `cmake -P`. function(_vortex_require_build_inputs) @@ -18,7 +20,9 @@ function(_vortex_require_build_inputs) VORTEX_FFI_PACKAGE VORTEX_CARGO_FFI_ARCHIVE VORTEX_CMAKE_FFI_ARCHIVE - VORTEX_CARGO_SUPPORT_DIR + VORTEX_RUSTFLAGS + VORTEX_CFLAGS + VORTEX_CXXFLAGS VORTEX_C_COMPILER VORTEX_CXX_COMPILER VORTEX_AR @@ -46,7 +50,6 @@ function(_vortex_make_cargo_command output) --package "${VORTEX_FFI_PACKAGE}" --lib --crate-type=staticlib - --no-default-features --target "${VORTEX_RUST_TARGET}" --target-dir "${VORTEX_CARGO_TARGET_DIR}" --profile "${VORTEX_CARGO_PROFILE}") @@ -75,11 +78,14 @@ function(_vortex_build_tool_path output) endfunction() # Assemble the target-specific Cargo environment from the selected tools and -# generated Rust, C, and C++ flag files. -function(_vortex_make_cargo_environment support_dir target_key_lower output) - file(READ "${support_dir}/rustflags" _rustflags) - file(READ "${support_dir}/cflags" _cflags) - file(READ "${support_dir}/cxxflags" _cxxflags) +# flags. +function(_vortex_make_cargo_environment target_key_lower output) + # Cargo separates CARGO_ENCODED_RUSTFLAGS arguments with ASCII unit separator, + # and the cc crate reads shell-quoted words with CC_SHELL_ESCAPED_FLAGS. + string(ASCII 31 _separator) + string(JOIN "${_separator}" _rustflags ${VORTEX_RUSTFLAGS}) + _vortex_encode_shell_arguments(_cflags ${VORTEX_CFLAGS}) + _vortex_encode_shell_arguments(_cxxflags ${VORTEX_CXXFLAGS}) _vortex_build_tool_path(_cargo_path) set(_environment @@ -111,10 +117,6 @@ function(_vortex_make_cargo_environment support_dir target_key_lower output) "MACOSX_DEPLOYMENT_TARGET=${VORTEX_APPLE_DEPLOYMENT_TARGET}") endif() - if(VORTEX_APPLE_SDKROOT) - list(APPEND _environment "SDKROOT=${VORTEX_APPLE_SDKROOT}") - endif() - set(${output} "${_environment}" PARENT_SCOPE) endfunction() @@ -122,7 +124,7 @@ _vortex_require_build_inputs() get_filename_component(_workspace_root "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) _vortex_cc_target_env_key(_target_key) _vortex_make_cargo_command(_cargo_command) -_vortex_make_cargo_environment("${VORTEX_CARGO_SUPPORT_DIR}" "${_target_key}" _cargo_environment) +_vortex_make_cargo_environment("${_target_key}" _cargo_environment) # Run Cargo with the CMake-selected tools and flags. Cargo remains responsible # for dependency tracking and incremental freshness. diff --git a/lang/cpp/cmake/Configure.cmake b/lang/cpp/cmake/Configure.cmake index f7ae6e87e0b..842dad3ea41 100644 --- a/lang/cpp/cmake/Configure.cmake +++ b/lang/cpp/cmake/Configure.cmake @@ -11,8 +11,9 @@ include("${CMAKE_CURRENT_LIST_DIR}/RustToolchain.cmake") include("${CMAKE_CURRENT_LIST_DIR}/SystemDependencies.cmake") # Use an explicit Cargo profile override or map the single-config CMake build -# type to a profile and artifact directory. Unknown build types warn and use -# Cargo's development profile; multi-config generators remain unsupported. +# type to a profile and artifact directory. Empty and unknown build types use +# Cargo's development profile, with a warning for unknown ones; multi-config +# generators remain unsupported. function(_vortex_resolve_cargo_profile configuration_output profile_output artifact_directory_output) if(CMAKE_CONFIGURATION_TYPES) message(FATAL_ERROR @@ -51,6 +52,9 @@ function(_vortex_resolve_cargo_profile configuration_output profile_output artif elseif(_build_type STREQUAL "MINSIZEREL") # MinSizeRel uses the release profile optimized for binary size. set(_cargo_profile "release_size") + elseif(_build_type STREQUAL "") + # An empty build type compiles C++ without optimization, which matches dev. + set(_cargo_profile "dev") else() # Unknown build types fall back to Cargo's development profile with a warning. message(WARNING @@ -123,11 +127,10 @@ function(_vortex_resolve_ffi_package endfunction() # Validate and configure the optional sanitizer for native and Rust code. -# Sanitizers require Clang, a Debug build, and nightly Rust; standard-library -# instrumentation is optional. +# Sanitizers require Clang and a Debug build; rustc itself rejects the +# nightly-only flags on stable. Standard-library instrumentation is optional. function(_vortex_resolve_sanitizer configuration - rustc_release native_flag_output rustflags_output build_std_output) @@ -161,13 +164,6 @@ function(_vortex_resolve_sanitizer message(FATAL_ERROR "Vortex sanitizer builds require CMAKE_BUILD_TYPE=Debug") endif() - # Rust sanitizer instrumentation depends on nightly-only compiler flags. - if(NOT VORTEX_SANITIZER STREQUAL "" AND NOT rustc_release MATCHES "nightly") - message(FATAL_ERROR - "VORTEX_SANITIZER=${VORTEX_SANITIZER} requires a nightly Rust " - "toolchain; found ${rustc_release}") - endif() - set(_native_flag "") set(_rust_sanitizer "") if(VORTEX_SANITIZER STREQUAL "ASAN") @@ -197,31 +193,14 @@ function(_vortex_resolve_sanitizer endfunction() # Reconstruct CMake's effective C and C++ flags for Cargo build scripts, then -# append target, deployment-target, sanitizer, and PIC requirements. Return the -# shell-encoded C/C++ flag strings. -function(_vortex_encode_native_flags +# append deployment-target, sanitizer, and PIC requirements. Return the C/C++ +# flag lists. +function(_vortex_native_flags configuration apple_deployment_target sanitizer_flag cflags_output cxxflags_output) - - if(CMAKE_C_COMPILER_TARGET AND CMAKE_C_COMPILER_ID MATCHES "^(AppleClang|Clang)$") - # Forward Clang's explicit C target to Cargo-built C dependencies. - set(_c_target "${CMAKE_C_COMPILER_TARGET}") - else() - # No explicit Clang C target needs forwarding. - set(_c_target "") - endif() - - if(CMAKE_CXX_COMPILER_TARGET AND CMAKE_CXX_COMPILER_ID MATCHES "^(AppleClang|Clang)$") - # Forward Clang's explicit C++ target to Cargo-built C++ dependencies. - set(_cxx_target "${CMAKE_CXX_COMPILER_TARGET}") - else() - # No explicit Clang C++ target needs forwarding. - set(_cxx_target "") - endif() - set(_cmake_c_flags "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${configuration}}") set(_cmake_cxx_flags "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${configuration}}") _vortex_reject_semicolon("effective CMAKE_C_FLAGS" "${_cmake_c_flags}") @@ -229,16 +208,6 @@ function(_vortex_encode_native_flags separate_arguments(_cflags UNIX_COMMAND "${_cmake_c_flags}") separate_arguments(_cxxflags UNIX_COMMAND "${_cmake_cxx_flags}") - if(_c_target) - # Preserve CMake's explicit Clang target for C dependencies. - list(APPEND _cflags "--target=${_c_target}") - endif() - - if(_cxx_target) - # Preserve CMake's explicit Clang target for C++ dependencies. - list(APPEND _cxxflags "--target=${_cxx_target}") - endif() - if(apple_deployment_target) # Match Cargo-built native code to CMake's minimum macOS version. list(APPEND _cflags "-mmacosx-version-min=${apple_deployment_target}") @@ -254,48 +223,9 @@ function(_vortex_encode_native_flags # Native dependencies become part of the archive embedded in shared parents. list(APPEND _cflags -fPIC) list(APPEND _cxxflags -fPIC) - _vortex_encode_shell_arguments(_cflags_shell ${_cflags}) - _vortex_encode_shell_arguments(_cxxflags_shell ${_cxxflags}) - - set(${cflags_output} "${_cflags_shell}" PARENT_SCOPE) - set(${cxxflags_output} "${_cxxflags_shell}" PARENT_SCOPE) -endfunction() - -# Add frame pointers and PIC to optional sanitizer rustflags, then -# encode the exact rustc argv with Cargo's ASCII unit separator. -function(_vortex_encode_rustflags output) - set(_rustflags "${ARGN}") - list(APPEND _rustflags -C force-frame-pointers=yes -C relocation-model=pic) - # Cargo separates CARGO_ENCODED_RUSTFLAGS arguments with ASCII unit separator. - string(ASCII 31 _separator) - string(JOIN "${_separator}" _encoded ${_rustflags}) - set(${output} "${_encoded}" PARENT_SCOPE) -endfunction() -# Write the Rust, C, and C++ flags consumed by CargoBuild.cmake. Create compiler -# wrappers when CMake configured prefix arguments, and return the compiler paths -# Cargo build scripts should use. Avoid rewriting unchanged support files. -function(_vortex_prepare_cargo_support - support_dir - encoded_rustflags - cflags - cxxflags - c_compiler_output - cxx_compiler_output) - file(MAKE_DIRECTORY "${support_dir}") - - _vortex_make_compiler_wrapper( - "${CMAKE_C_COMPILER}" "${CMAKE_C_COMPILER_ARG1}" - "${support_dir}" cc _c_compiler) - _vortex_make_compiler_wrapper( - "${CMAKE_CXX_COMPILER}" "${CMAKE_CXX_COMPILER_ARG1}" - "${support_dir}" cxx _cxx_compiler) - _vortex_write_if_different("${support_dir}/rustflags" "${encoded_rustflags}") - _vortex_write_if_different("${support_dir}/cflags" "${cflags}") - _vortex_write_if_different("${support_dir}/cxxflags" "${cxxflags}") - - set(${c_compiler_output} "${_c_compiler}" PARENT_SCOPE) - set(${cxx_compiler_output} "${_cxx_compiler}" PARENT_SCOPE) + set(${cflags_output} "${_cflags}" PARENT_SCOPE) + set(${cxxflags_output} "${_cxxflags}" PARENT_SCOPE) endfunction() # Configure Cargo and expose the staged FFI archive as a private dependency of @@ -315,7 +245,6 @@ block(SCOPE_FOR VARIABLES) _vortex_resolve_rust_toolchain("${_workspace_root}") _vortex_resolve_sanitizer( "${_configuration}" - "${VORTEX_RUSTC_RELEASE}" _sanitizer_compile_flag _sanitizer_rustflags _cargo_build_std) @@ -324,37 +253,32 @@ block(SCOPE_FOR VARIABLES) message(STATUS "Vortex ignores ambient Rust flags in its Cargo build") endif() - _vortex_encode_native_flags( + _vortex_native_flags( "${_configuration}" "${VORTEX_APPLE_DEPLOYMENT_TARGET}" "${_sanitizer_compile_flag}" _native_c_flags _native_cxx_flags) - _vortex_encode_rustflags(_encoded_rustflags ${_sanitizer_rustflags}) + # Frame pointers and PIC apply to every Rust build; the archive is embedded + # in shared parents. + set(_rustflags ${_sanitizer_rustflags} -C force-frame-pointers=yes -C relocation-model=pic) # Cargo owns incremental invalidation inside this CMake-build-local cache. # Registering the directory as additional clean state gives the standard # CMake clean target the same effect as `cargo clean --target-dir ...`. set(_cargo_target_dir "${CMAKE_CURRENT_BINARY_DIR}/cargo-target") set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES "${_cargo_target_dir}") - set(_cargo_support_dir "${CMAKE_CURRENT_BINARY_DIR}/cargo-support") - _vortex_prepare_cargo_support( - "${_cargo_support_dir}" - "${_encoded_rustflags}" - "${_native_c_flags}" - "${_native_cxx_flags}" - _native_c_compiler - _native_cxx_compiler) set(_cargo_ffi_archive "${_cargo_target_dir}/${VORTEX_RUST_TARGET}/${_cargo_artifact_directory}/${_cargo_archive_name}") set(_ffi_archive "${CMAKE_CURRENT_BINARY_DIR}/vortex-artifacts/libvortex_ffi.a") # Each value below becomes one `-D` argument of the driver and later one - # environment entry, where a semicolon would split it. + # environment entry, where a semicolon would split it. The flag variables + # are passed as lists on purpose. foreach(_name IN ITEMS VORTEX_CARGO_EXECUTABLE VORTEX_RUSTC_EXECUTABLE VORTEX_RUSTUP_TOOLCHAIN - VORTEX_APPLE_SDKROOT VORTEX_APPLE_DEPLOYMENT_TARGET - _native_c_compiler _native_cxx_compiler CMAKE_AR CMAKE_RANLIB) + VORTEX_APPLE_DEPLOYMENT_TARGET + CMAKE_C_COMPILER CMAKE_CXX_COMPILER CMAKE_AR CMAKE_RANLIB) _vortex_reject_semicolon("${_name}" "${${_name}}") endforeach() @@ -370,17 +294,18 @@ block(SCOPE_FOR VARIABLES) "-DVORTEX_CARGO_PROFILE=${_cargo_profile}" "-DVORTEX_FFI_PACKAGE=${_ffi_package}" "-DVORTEX_CARGO_FFI_ARCHIVE=${_cargo_ffi_archive}" - "-DVORTEX_CARGO_SUPPORT_DIR=${_cargo_support_dir}" "-DVORTEX_NVCC_EXECUTABLE=${_nvcc_executable}" "-DVORTEX_CUDA_ROOT=${_cuda_root}" "-DVORTEX_CARGO_BUILD_STD=${_cargo_build_std}" "-DVORTEX_CMAKE_FFI_ARCHIVE=${_ffi_archive}" - "-DVORTEX_C_COMPILER=${_native_c_compiler}" - "-DVORTEX_CXX_COMPILER=${_native_cxx_compiler}" + "-DVORTEX_RUSTFLAGS=${_rustflags}" + "-DVORTEX_CFLAGS=${_native_c_flags}" + "-DVORTEX_CXXFLAGS=${_native_cxx_flags}" + "-DVORTEX_C_COMPILER=${CMAKE_C_COMPILER}" + "-DVORTEX_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DVORTEX_AR=${CMAKE_AR}" "-DVORTEX_RANLIB=${CMAKE_RANLIB}" "-DVORTEX_APPLE_DEPLOYMENT_TARGET=${VORTEX_APPLE_DEPLOYMENT_TARGET}" - "-DVORTEX_APPLE_SDKROOT=${VORTEX_APPLE_SDKROOT}" -P "${CMAKE_CURRENT_LIST_DIR}/CargoBuild.cmake" BYPRODUCTS "${_ffi_archive}" COMMENT "Building the PIC Vortex FFI static archive with Cargo" diff --git a/lang/cpp/cmake/Helpers.cmake b/lang/cpp/cmake/Helpers.cmake index 8eb00b0a09c..57739f9d2b3 100644 --- a/lang/cpp/cmake/Helpers.cmake +++ b/lang/cpp/cmake/Helpers.cmake @@ -14,17 +14,6 @@ function(_vortex_reject_semicolon label value) endif() endfunction() -# Write content only when path is missing or has different contents. -function(_vortex_write_if_different path content) - if(EXISTS "${path}") - file(READ "${path}" _existing_content) - if("${_existing_content}" STREQUAL "${content}") - return() - endif() - endif() - file(WRITE "${path}" "${content}") -endfunction() - # Encode ARGN as POSIX shell words in one space-delimited string. function(_vortex_encode_shell_arguments output) set(_encoded "") @@ -37,29 +26,3 @@ function(_vortex_encode_shell_arguments output) endforeach() set(${output} "${_encoded}" PARENT_SCOPE) endfunction() - -# Return the compiler path directly, or create a POSIX wrapper that preserves -# `arg1`. The wrapper filename hashes the complete command so Cargo observes a -# changed CC/CXX value when CMake changes the otherwise-hidden prefix argument. -function(_vortex_make_compiler_wrapper compiler arg1 directory name output) - if(arg1 STREQUAL "") - set(${output} "${compiler}" PARENT_SCOPE) - return() - endif() - - separate_arguments(_compiler_arg1 NATIVE_COMMAND "${arg1}") - _vortex_encode_shell_arguments( - _compiler_command "${compiler}" ${_compiler_arg1}) - set(_script "#!/bin/sh\nexec ${_compiler_command} \"$@\"\n") - string(SHA256 _command_hash "${_script}") - string(SUBSTRING "${_command_hash}" 0 16 _command_hash) - - set(_wrapper "${directory}/${name}-${_command_hash}") - _vortex_write_if_different("${_wrapper}" "${_script}") - file(CHMOD "${_wrapper}" - PERMISSIONS - OWNER_READ OWNER_WRITE OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE - WORLD_READ WORLD_EXECUTE) - set(${output} "${_wrapper}" PARENT_SCOPE) -endfunction() diff --git a/lang/cpp/cmake/RustToolchain.cmake b/lang/cpp/cmake/RustToolchain.cmake index edb3f2bb0c4..7422855c554 100644 --- a/lang/cpp/cmake/RustToolchain.cmake +++ b/lang/cpp/cmake/RustToolchain.cmake @@ -5,16 +5,10 @@ include_guard(GLOBAL) -# Sets VORTEX_RUST_TARGET, VORTEX_RUSTC_RELEASE, VORTEX_RUSTUP_TOOLCHAIN, -# VORTEX_APPLE_SDKROOT, and VORTEX_APPLE_DEPLOYMENT_TARGET in the caller's -# scope. The cache entries VORTEX_CARGO_EXECUTABLE and VORTEX_RUSTC_EXECUTABLE -# hold the selected tools. +# Sets VORTEX_RUST_TARGET, VORTEX_RUSTUP_TOOLCHAIN, and +# VORTEX_APPLE_DEPLOYMENT_TARGET in the caller's scope. The cache entries +# VORTEX_CARGO_EXECUTABLE and VORTEX_RUSTC_EXECUTABLE hold the selected tools. function(_vortex_resolve_rust_toolchain workspace_root) - # Cargo builds for the rustc host, so C++ must be built for the same machine. - if(CMAKE_CROSSCOMPILING) - message(FATAL_ERROR "Vortex's CMake integration supports native builds only") - endif() - # Rustup proxies honor the workspace toolchain file when run from the # workspace. RUSTUP_TOOLCHAIN is captured so Cargo builds keep this selection. find_program(VORTEX_CARGO_EXECUTABLE NAMES cargo REQUIRED) @@ -25,29 +19,15 @@ function(_vortex_resolve_rust_toolchain workspace_root) OUTPUT_VARIABLE _rustc_verbose COMMAND_ERROR_IS_FATAL ANY) string(REGEX MATCH "host: ([^\r\n]+)" _match "${_rustc_verbose}") - set(_host "${CMAKE_MATCH_1}") - string(REGEX MATCH "release: ([^\r\n]+)" _match "${_rustc_verbose}") - set(_release "${CMAKE_MATCH_1}") - message(STATUS "Vortex rustc: ${_release}, host ${_host} (${VORTEX_RUSTC_EXECUTABLE})") + set(VORTEX_RUST_TARGET "${CMAKE_MATCH_1}" PARENT_SCOPE) + set(VORTEX_RUSTUP_TOOLCHAIN "$ENV{RUSTUP_TOOLCHAIN}" PARENT_SCOPE) - # Forward CMake's SDK to Cargo-built native code. Without an explicit - # deployment target the cc crate uses the SDK version, which can exceed - # CMake's link target and makes ld64 warn about every Cargo-built C object; - # 11.0 is rustc's minimum for aarch64-apple-darwin. - set(_sdkroot "") - set(_deployment_target "${CMAKE_OSX_DEPLOYMENT_TARGET}") - if(APPLE) - if(IS_DIRECTORY "${CMAKE_OSX_SYSROOT}") - set(_sdkroot "${CMAKE_OSX_SYSROOT}") - endif() - if(NOT _deployment_target) - set(_deployment_target "11.0") - endif() + # Without an explicit deployment target the cc crate uses the SDK version, + # which can exceed CMake's link target and makes ld64 warn about every + # Cargo-built C object; 11.0 is rustc's minimum for aarch64-apple-darwin. + if(APPLE AND NOT CMAKE_OSX_DEPLOYMENT_TARGET) + set(VORTEX_APPLE_DEPLOYMENT_TARGET "11.0" PARENT_SCOPE) + else() + set(VORTEX_APPLE_DEPLOYMENT_TARGET "${CMAKE_OSX_DEPLOYMENT_TARGET}" PARENT_SCOPE) endif() - - set(VORTEX_RUST_TARGET "${_host}" PARENT_SCOPE) - set(VORTEX_RUSTC_RELEASE "${_release}" PARENT_SCOPE) - set(VORTEX_RUSTUP_TOOLCHAIN "$ENV{RUSTUP_TOOLCHAIN}" PARENT_SCOPE) - set(VORTEX_APPLE_SDKROOT "${_sdkroot}" PARENT_SCOPE) - set(VORTEX_APPLE_DEPLOYMENT_TARGET "${_deployment_target}" PARENT_SCOPE) endfunction() From f435699565385bf20efa34d20c855d66243bab74 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 15:54:11 +0100 Subject: [PATCH 15/24] Simplify the C++ CMake entry point Call project() unconditionally and use PROJECT_IS_TOP_LEVEL for the standalone defaults, which removes the embedded language detection and the helper path variables. Drop the toolchain-file configure dependency, since configure no longer reads anything it changes, and glob the wrapper sources like the tests do. Group the file into commented sections and explain why Catch2 is built as C++17: its compiled sources gate std::string_view, optional, variant, and byte support on that standard, and the C++23 tests include its headers. Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- lang/cpp/CMakeLists.txt | 79 +++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 50 deletions(-) diff --git a/lang/cpp/CMakeLists.txt b/lang/cpp/CMakeLists.txt index 70a9c0bac92..ec014fe0972 100644 --- a/lang/cpp/CMakeLists.txt +++ b/lang/cpp/CMakeLists.txt @@ -6,88 +6,64 @@ cmake_minimum_required(VERSION 3.28) -set(_vortex_workspace_root "${CMAKE_CURRENT_SOURCE_DIR}/../..") -set(_vortex_cmake_dir "${CMAKE_CURRENT_SOURCE_DIR}/cmake") - -# A standalone configure owns project() and its language set. When embedded, -# preserve the parent's project metadata and enable only languages it has not -# already initialized. -if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) - project(VortexCXX DESCRIPTION "Vortex C++ wrapper" LANGUAGES C CXX) - set(_vortex_cpp_standalone ON) -else() - set(_vortex_cpp_standalone OFF) - get_property(_vortex_enabled_languages GLOBAL PROPERTY ENABLED_LANGUAGES) - if(NOT "C" IN_LIST _vortex_enabled_languages) - enable_language(C) - endif() - if(NOT "CXX" IN_LIST _vortex_enabled_languages) - enable_language(CXX) - endif() -endif() +# project() is safe inside a parent's add_subdirectory(); PROJECT_IS_TOP_LEVEL +# tells the two cases apart wherever the defaults below differ. +project(VortexCXX DESCRIPTION "Vortex C++ wrapper" LANGUAGES C CXX) # Standalone builds default to Debug; embedded builds keep the parent's choice. -if(_vortex_cpp_standalone AND NOT CMAKE_BUILD_TYPE) +if(PROJECT_IS_TOP_LEVEL AND NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() -# A toolchain-file change must re-run configure so Cargo and rustc are resolved -# again. Cargo itself tracks manifests, the lockfile, and Rust sources at build -# time because its phony CMake target runs on every build. -set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS - "${_vortex_workspace_root}/rust-toolchain.toml") - +# Public options. Embedding parents set these before add_subdirectory(). option(VORTEX_BUILD_TESTING "Build Vortex C++ tests" OFF) option(VORTEX_BUILD_EXAMPLES "Build Vortex C++ examples" OFF) option(VORTEX_ENABLE_CUDA "Build the CUDA-enabled Vortex FFI" OFF) set(VORTEX_CARGO_PROFILE "" CACHE STRING "Override the Cargo profile selected from CMAKE_BUILD_TYPE") + +# Sanitizers instrument the Rust archive, its C dependencies, and the C++ +# sources together; Configure.cmake enforces the toolchain constraints. set(VORTEX_SANITIZER "" CACHE STRING "Instrument Vortex and its consumers with asan or tsan") -set_property(CACHE VORTEX_SANITIZER PROPERTY STRINGS "" asan tsan) option(VORTEX_SANITIZE_RUST_STD "Rebuild Rust's standard library with sanitizer instrumentation" OFF) # Standalone builds treat warnings as errors; embedded builds do not impose that # policy on a parent. -if(_vortex_cpp_standalone) - set(_vortex_warnings_as_errors_default ON) -else() - set(_vortex_warnings_as_errors_default OFF) -endif() option(VORTEX_WARNINGS_AS_ERRORS "Treat warnings in Vortex-owned C++ sources as errors" - "${_vortex_warnings_as_errors_default}") + "${PROJECT_IS_TOP_LEVEL}") # Run the private configure-time integration that defines the Cargo build and -# imported Rust FFI target used by the public C++ library. -include("${_vortex_cmake_dir}/Configure.cmake") - -set(_vortex_cpp_sources - src/array.cpp - src/data_source.cpp - src/dtype.cpp - src/expression.cpp - src/scalar.cpp - src/scan.cpp - src/session.cpp - src/writer.cpp) +# the imported vortex_ffi_static target consumed below. +include(cmake/Configure.cmake) + +# The C++ wrapper over the C FFI. Public headers live under include/. +file(GLOB _vortex_cpp_sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") + +# Consumers inherit the FFI archive, its headers, and its system libraries +# through this target. Hidden visibility keeps wrapper symbols out of a parent +# shared library's exports. add_library(vortex_cxx STATIC ${_vortex_cpp_sources}) set_target_properties(vortex_cxx PROPERTIES POSITION_INDEPENDENT_CODE ON CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN ON) target_compile_features(vortex_cxx PUBLIC cxx_std_20) +target_include_directories(vortex_cxx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(vortex_cxx PUBLIC vortex_ffi_static) + # Warning policy is private to Vortex-owned sources so embedding this target # never injects warning flags into consumers. target_compile_options(vortex_cxx PRIVATE -Wall -Wextra -Wpedantic) if(VORTEX_WARNINGS_AS_ERRORS) target_compile_options(vortex_cxx PRIVATE -Werror) endif() -target_include_directories(vortex_cxx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") -target_link_libraries(vortex_cxx PUBLIC vortex_ffi_static) +# The only supported consumer-facing target name. add_library(Vortex::cpp_static ALIAS vortex_cxx) +# Test and example dependencies are fetched at configure time and never installed. if(VORTEX_BUILD_TESTING OR VORTEX_BUILD_EXAMPLES) include(FetchContent) FetchContent_Declare( @@ -110,12 +86,15 @@ if(VORTEX_BUILD_TESTING) GIT_TAG v3.8.1) FetchContent_MakeAvailable(Catch) include(Catch) + + # Catch2 enables std::string_view, optional, variant, and byte support from + # C++17, in headers and compiled sources alike; build it with those on so it + # matches the C++23 tests that include its headers. target_compile_features(Catch2 PRIVATE cxx_std_17) target_compile_definitions(Catch2 PRIVATE CATCH_CONFIG_NO_POSIX_SIGNALS) - if(_vortex_cpp_standalone) - enable_testing() - endif() + # Embedding parents must also call enable_testing() for ctest to find these. + enable_testing() add_subdirectory(tests) endif() From d7a23c3c5c740aa7624eae2e765c6297a2684b40 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 15:08:12 +0000 Subject: [PATCH 16/24] Disable unnecessary CMake module scanning Signed-off-by: Alexander Droste --- lang/cpp/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lang/cpp/CMakeLists.txt b/lang/cpp/CMakeLists.txt index ec014fe0972..62daa7faabd 100644 --- a/lang/cpp/CMakeLists.txt +++ b/lang/cpp/CMakeLists.txt @@ -10,6 +10,10 @@ cmake_minimum_required(VERSION 3.28) # tells the two cases apart wherever the defaults below differ. project(VortexCXX DESCRIPTION "Vortex C++ wrapper" LANGUAGES C CXX) +# Vortex does not use C++ modules, so it does not require compiler-specific +# dependency scanners such as clang-scan-deps. +set(CMAKE_CXX_SCAN_FOR_MODULES OFF) + # Standalone builds default to Debug; embedded builds keep the parent's choice. if(PROJECT_IS_TOP_LEVEL AND NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) From 71ebeab82db0ee35bf91ae70720dc03fa3ccbdfd Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 15:10:46 +0000 Subject: [PATCH 17/24] Disable unnecessary FFI CMake module scanning Signed-off-by: Alexander Droste --- vortex-ffi/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vortex-ffi/CMakeLists.txt b/vortex-ffi/CMakeLists.txt index 05ca5c348c5..e515f841bbc 100644 --- a/vortex-ffi/CMakeLists.txt +++ b/vortex-ffi/CMakeLists.txt @@ -7,6 +7,11 @@ include(FetchContent) project(VortexFFI VERSION 0.0.1 LANGUAGES C) + +# Vortex does not use C++ modules, so it does not require compiler-specific +# dependency scanners such as clang-scan-deps. +set(CMAKE_CXX_SCAN_FOR_MODULES OFF) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_C_STANDARD 17) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wextra -Wpedantic") From dac563e4db75221fb8457d8f2dc0980b427de6e1 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 16:57:57 +0100 Subject: [PATCH 18/24] Consolidate the CMake integration under vortex-ffi Move the Cargo integration modules from lang/cpp/cmake to vortex-ffi/cmake and make vortex-ffi/CMakeLists.txt own the Cargo build, exporting Vortex::ffi_static as a global imported target. lang/cpp consumes that target, adding vortex-ffi itself when nothing else has, and a root CMakeLists.txt adds both so consumers can link Vortex::ffi_static from C or Vortex::cpp_static from C++ after one add_subdirectory(). Port the C tests and examples to the static target, prefix example target names on both sides so a combined build has no clashes, and fix scan.c, which used pthread_t as a thread count and never compiled on macOS. Drive the two FFI CI jobs through CMake instead of a manual Cargo step, default sanitizer builds to rustup's nightly toolchain unless RUSTUP_TOOLCHAIN is set, and drop the no-op --no-default-features from the Rust sanitizer tests and docs. Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- .github/workflows/ci.yml | 18 +- .github/workflows/rust-instrumented.yml | 33 ++-- CMakeLists.txt | 54 ++++++ docs/getting-started/cpp.rst | 7 +- lang/cpp/CMakeLists.txt | 57 ++++--- lang/cpp/README.md | 41 +++-- lang/cpp/examples/CMakeLists.txt | 9 +- vortex-ffi/CMakeLists.txt | 156 ++++++------------ vortex-ffi/README.md | 100 +++++------ .../cpp => vortex-ffi}/cmake/CargoBuild.cmake | 2 +- .../cpp => vortex-ffi}/cmake/Configure.cmake | 12 +- {lang/cpp => vortex-ffi}/cmake/Helpers.cmake | 0 .../cmake/RustToolchain.cmake | 0 .../cmake/SystemDependencies.cmake | 4 +- vortex-ffi/examples/CMakeLists.txt | 28 ++-- vortex-ffi/examples/scan.c | 32 ++-- vortex-ffi/test/CMakeLists.txt | 32 ++-- 17 files changed, 285 insertions(+), 300 deletions(-) create mode 100644 CMakeLists.txt rename {lang/cpp => vortex-ffi}/cmake/CargoBuild.cmake (99%) rename {lang/cpp => vortex-ffi}/cmake/Configure.cmake (97%) rename {lang/cpp => vortex-ffi}/cmake/Helpers.cmake (100%) rename {lang/cpp => vortex-ffi}/cmake/RustToolchain.cmake (100%) rename {lang/cpp => vortex-ffi}/cmake/SystemDependencies.cmake (89%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5634543256..7edc7c60895 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -782,13 +782,13 @@ jobs: - uses: ./.github/actions/setup-prebuild with: enable-sccache: "true" - - name: "regenerate FFI header file" - run: | - cargo +$NIGHTLY_TOOLCHAIN build --profile ci -p vortex-ffi - - name: Build and run C++ unit tests + - name: Build and run C API tests + env: + # Nightly regenerates cinclude/vortex.h with cbindgen during the build. + RUSTUP_TOOLCHAIN: ${{ env.NIGHTLY_TOOLCHAIN }} run: | - cd vortex-ffi - mkdir build - cmake -Bbuild -DRUST_BUILD_PROFILE=ci - cmake --build build -j $(nproc) - ctest --test-dir build -j $(nproc) + cmake -S vortex-ffi -B vortex-ffi/build \ + -DVORTEX_CARGO_PROFILE=ci \ + -DVORTEX_BUILD_TESTING=ON + cmake --build vortex-ffi/build --parallel + ctest --test-dir vortex-ffi/build --output-on-failure diff --git a/.github/workflows/rust-instrumented.yml b/.github/workflows/rust-instrumented.yml index 77607c0ed3d..bb515d69fd6 100644 --- a/.github/workflows/rust-instrumented.yml +++ b/.github/workflows/rust-instrumented.yml @@ -208,17 +208,14 @@ jobs: --target x86_64-unknown-linux-gnu -Zbuild-std \ -p vortex-buffer -p vortex-fastlanes -p vortex-fsst -p vortex-alp -p vortex-array - # vortex-ffi requires --no-default-features as otherwise we pull in - # Mimalloc which interferes with sanitizers # cargo nextest reports less sanitizer issues than cargo test - # TODO(myrrc): remove --no-default-features once we make Mimalloc opt-in # --tests skips doctests: rustdoc ignores RUSTFLAGS (it uses RUSTDOCFLAGS), # so doctests would build vortex-ffi without -Zsanitizer and mismatch the # sanitizer-built deps. - name: Run vortex-ffi tests with sanitizer run: | RUSTFLAGS="${RUSTFLAGS} ${{ matrix.sanitizer_flags }}" \ - cargo +$NIGHTLY_TOOLCHAIN test --locked --no-default-features \ + cargo +$NIGHTLY_TOOLCHAIN test --locked \ --target x86_64-unknown-linux-gnu --no-fail-fast -Zbuild-std \ -p vortex-ffi --tests -- --no-capture @@ -230,9 +227,7 @@ jobs: # We don't run memory sanitizer as it's clang-only and provides many # false positives for Catch2 - sanitizer: asan - sanitizer_flags: "-Zsanitizer=address,leak" - sanitizer: tsan - sanitizer_flags: "-Zsanitizer=thread" name: "Rust/C++ FFI tests (${{ matrix.sanitizer }})" timeout-minutes: 5 env: @@ -244,8 +239,6 @@ jobs: TSAN_OPTIONS: "symbolize=1:suppressions=${{ github.workspace }}/vortex-ffi/tsan_suppressions.txt" TSAN_SYMBOLIZER_PATH: "/usr/bin/llvm-symbolizer" VORTEX_SKIP_SLOW_TESTS: "1" - # -Cunsafe-allow-abi-mismatch=sanitizer: libraries like compiler_builtins - # unset -Zsanitizer flag and we should allow that. runs-on: >- ${{ github.repository == 'vortex-data/vortex' && format('runs-on={0}/runner=amd64-medium/image=ubuntu24-full-x64-pre-v2/tag=rust-ffi-test-sanitizer', github.run_id) @@ -263,20 +256,20 @@ jobs: run: | rustup toolchain install $NIGHTLY_TOOLCHAIN rustup component add --toolchain $NIGHTLY_TOOLCHAIN rust-src rustfmt clippy llvm-tools-preview - - name: Build FFI library - run: | - # TODO(myrrc): remove --no-default-features - RUSTFLAGS="-A warnings -Cunsafe-allow-abi-mismatch=sanitizer \ - -C debuginfo=2 -C opt-level=0 -C strip=none -Zexternal-clangrt \ - ${{ matrix.sanitizer_flags }}" \ - cargo +$NIGHTLY_TOOLCHAIN build --locked --no-default-features \ - --target x86_64-unknown-linux-gnu -Zbuild-std \ - -p vortex-ffi - - name: Build FFI library tests and examples + - name: Build FFI library, tests, and examples working-directory: vortex-ffi + env: + RUSTUP_TOOLCHAIN: ${{ env.NIGHTLY_TOOLCHAIN }} run: | - cmake -Bbuild -DBUILD_TESTS=1 -DBUILD_EXAMPLES=1 -DSANITIZER=${{ matrix.sanitizer }} -DTARGET_TRIPLE="x86_64-unknown-linux-gnu" - cmake --build build -j + cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DVORTEX_SANITIZER=${{ matrix.sanitizer }} \ + -DVORTEX_SANITIZE_RUST_STD=ON \ + -DVORTEX_BUILD_TESTING=ON \ + -DVORTEX_BUILD_EXAMPLES=ON + cmake --build build --parallel - name: Run tests run: | set -o pipefail diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000000..2ab5d5483c8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright the Vortex contributors + +# CMake entry point for the Vortex native libraries. +# +# Layout: +# vortex-ffi/ Builds the Rust FFI archive with Cargo and exports +# Vortex::ffi_static for C consumers. Its cmake/ modules select +# the Rust toolchain, drive Cargo, and attach the system libraries +# the archive needs. The Cargo cache and the staged archive live +# under ffi/ in the build directory. +# lang/cpp/ Builds the C++ wrapper on top and exports Vortex::cpp_static. +# +# Usage: +# add_subdirectory(path/to/vortex vortex) +# target_link_libraries(my_c_target PRIVATE Vortex::ffi_static) +# target_link_libraries(my_cpp_target PRIVATE Vortex::cpp_static) +# +# Each subdirectory also configures on its own, and lang/cpp adds vortex-ffi +# itself when nothing else has. A separate `cargo build` is never required. +# +# Options, set before add_subdirectory() or passed with -D: +# VORTEX_BUILD_TESTING Tests of both layers, run with ctest. +# VORTEX_BUILD_EXAMPLES Examples of both layers. +# VORTEX_CARGO_PROFILE Cargo profile; otherwise mapped from CMAKE_BUILD_TYPE. +# VORTEX_ENABLE_CUDA Linux-only CUDA-enabled FFI archive. +# VORTEX_SANITIZER asan or tsan for Rust, C, and C++ together. +# VORTEX_SANITIZE_RUST_STD Also instrument Rust's standard library. +# VORTEX_WARNINGS_AS_ERRORS ON for a Vortex-owned top level, OFF when embedded. +# +# A Vortex-owned top level, this file or either subdirectory, also defaults +# CMAKE_BUILD_TYPE to Debug. +# +# Documentation: +# lang/cpp/README.md Full option reference, embedding rules, the +# shared-library boundary, CUDA deployment, +# sanitizer and coverage workflows. +# docs/getting-started/cpp.rst C++ quickstart with API examples. +# vortex-ffi/README.md C API: runtime threading, header regeneration, +# CMake usage, Rust-side sanitizer tests. +# vortex-ffi/cinclude/vortex.h The C API header, generated by cbindgen. +# vortex-cuda/ffi/README.md CUDA-enabled FFI and Arrow Device export. +# vortex-cuda/README.md CUDA crate and cuDF integration notes. +# docs/project/bindings.md Language bindings roadmap. + +cmake_minimum_required(VERSION 3.28) + +project(VortexNative DESCRIPTION "Vortex native libraries" LANGUAGES NONE) + +# ctest only descends into subdirectories from a root that has testing enabled. +enable_testing() + +add_subdirectory(vortex-ffi ffi) +add_subdirectory(lang/cpp cpp) diff --git a/docs/getting-started/cpp.rst b/docs/getting-started/cpp.rst index deb569a7564..ffd0d9c6572 100644 --- a/docs/getting-started/cpp.rst +++ b/docs/getting-started/cpp.rst @@ -38,11 +38,12 @@ locked Rust dependencies during the build. Tests and examples default to ``OFF``. ``VORTEX_WARNINGS_AS_ERRORS`` defaults to ``ON`` for a standalone build and ``OFF`` when a parent adds ``lang/cpp``. -Source-tree consumers add ``lang/cpp`` and link the canonical target: +Source-tree consumers add the repository root, which builds the Rust FFI once and +the C++ wrapper on top, and link the canonical target: .. code-block:: cmake - add_subdirectory(path/to/vortex/lang/cpp vortex-cpp) + add_subdirectory(path/to/vortex vortex) target_link_libraries(target PRIVATE Vortex::cpp_static) The Vortex archives are PIC and can be embedded into a shared parent. Keep calls @@ -52,7 +53,7 @@ interface target does not apply parent-wide symbol-export policy. The CMake integration is source-only: it does not provide installation rules or a ``find_package(Vortex)`` package. Downstream projects should vendor or fetch a pinned -Vortex checkout and add ``lang/cpp`` directly. +Vortex checkout and add it directly. Native macOS arm64 is supported for standalone development, but macOS is not a cuDF integration target. GNU/Linux x86_64 and aarch64 are modeled, while full diff --git a/lang/cpp/CMakeLists.txt b/lang/cpp/CMakeLists.txt index 62daa7faabd..25065b197d6 100644 --- a/lang/cpp/CMakeLists.txt +++ b/lang/cpp/CMakeLists.txt @@ -1,46 +1,43 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -# Entry point for the static Vortex C++ and Rust FFI libraries. It supports both -# standalone configuration and inclusion from a parent via add_subdirectory(). +# Entry point for the static Vortex C++ wrapper. It consumes the Rust FFI +# archive from vortex-ffi and exports Vortex::cpp_static. It supports standalone +# configuration and add_subdirectory() from the repository root or a parent. cmake_minimum_required(VERSION 3.28) -# project() is safe inside a parent's add_subdirectory(); PROJECT_IS_TOP_LEVEL -# tells the two cases apart wherever the defaults below differ. +# project() is safe inside a parent's add_subdirectory(). A Vortex-owned top +# level, this directory or the repository root, gets developer defaults below; +# an embedding parent keeps its own. project(VortexCXX DESCRIPTION "Vortex C++ wrapper" LANGUAGES C CXX) +if(CMAKE_PROJECT_NAME MATCHES "^Vortex") + set(_vortex_top_level ON) +else() + set(_vortex_top_level OFF) +endif() # Vortex does not use C++ modules, so it does not require compiler-specific # dependency scanners such as clang-scan-deps. set(CMAKE_CXX_SCAN_FOR_MODULES OFF) -# Standalone builds default to Debug; embedded builds keep the parent's choice. -if(PROJECT_IS_TOP_LEVEL AND NOT CMAKE_BUILD_TYPE) +if(_vortex_top_level AND NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() -# Public options. Embedding parents set these before add_subdirectory(). -option(VORTEX_BUILD_TESTING "Build Vortex C++ tests" OFF) -option(VORTEX_BUILD_EXAMPLES "Build Vortex C++ examples" OFF) -option(VORTEX_ENABLE_CUDA "Build the CUDA-enabled Vortex FFI" OFF) -set(VORTEX_CARGO_PROFILE "" CACHE STRING "Override the Cargo profile selected from CMAKE_BUILD_TYPE") - -# Sanitizers instrument the Rust archive, its C dependencies, and the C++ -# sources together; Configure.cmake enforces the toolchain constraints. -set(VORTEX_SANITIZER "" CACHE STRING "Instrument Vortex and its consumers with asan or tsan") -option(VORTEX_SANITIZE_RUST_STD - "Rebuild Rust's standard library with sanitizer instrumentation" - OFF) - -# Standalone builds treat warnings as errors; embedded builds do not impose that -# policy on a parent. +# Public options. Embedding parents set these before add_subdirectory(). The +# Cargo, CUDA, and sanitizer options are defined by vortex-ffi/CMakeLists.txt. +option(VORTEX_BUILD_TESTING "Build Vortex tests" OFF) +option(VORTEX_BUILD_EXAMPLES "Build Vortex examples" OFF) option(VORTEX_WARNINGS_AS_ERRORS - "Treat warnings in Vortex-owned C++ sources as errors" - "${PROJECT_IS_TOP_LEVEL}") + "Treat warnings in Vortex-owned sources as errors" + "${_vortex_top_level}") -# Run the private configure-time integration that defines the Cargo build and -# the imported vortex_ffi_static target consumed below. -include(cmake/Configure.cmake) +# The Rust FFI archive comes from vortex-ffi. A standalone configure of this +# directory adds it here; the repository root or a parent may already have. +if(NOT TARGET Vortex::ffi_static) + add_subdirectory(../../vortex-ffi ffi) +endif() # The C++ wrapper over the C FFI. Public headers live under include/. file(GLOB _vortex_cpp_sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") @@ -55,7 +52,7 @@ set_target_properties(vortex_cxx PROPERTIES VISIBILITY_INLINES_HIDDEN ON) target_compile_features(vortex_cxx PUBLIC cxx_std_20) target_include_directories(vortex_cxx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") -target_link_libraries(vortex_cxx PUBLIC vortex_ffi_static) +target_link_libraries(vortex_cxx PUBLIC Vortex::ffi_static) # Warning policy is private to Vortex-owned sources so embedding this target # never injects warning flags into consumers. @@ -64,7 +61,7 @@ if(VORTEX_WARNINGS_AS_ERRORS) target_compile_options(vortex_cxx PRIVATE -Werror) endif() -# The only supported consumer-facing target name. +# The only supported consumer-facing name for the C++ API. add_library(Vortex::cpp_static ALIAS vortex_cxx) # Test and example dependencies are fetched at configure time and never installed. @@ -89,7 +86,9 @@ if(VORTEX_BUILD_TESTING) GIT_REPOSITORY https://github.com/catchorg/Catch2.git GIT_TAG v3.8.1) FetchContent_MakeAvailable(Catch) - include(Catch) + # By path: Catch2 exports its module directory only to the directory that + # populated it, which may be vortex-ffi/test. + include("${catch_SOURCE_DIR}/extras/Catch.cmake") # Catch2 enables std::string_view, optional, variant, and byte support from # C++17, in headers and compiled sources alike; build it with those on so it diff --git a/lang/cpp/README.md b/lang/cpp/README.md index 4bc0e756d6f..9fb47b9e393 100644 --- a/lang/cpp/README.md +++ b/lang/cpp/README.md @@ -5,11 +5,11 @@ Vortex provides a C++20 API for reading and writing Vortex files. See the ## Quick start -Build from the repository root with CMake 3.28 or newer, Ninja, native C/C++ compilers, and -Cargo and rustc available to CMake's program search: +Build from the repository root with CMake 3.28 or newer, native C/C++ compilers, and Cargo and +rustc available to CMake's program search: ```sh -cmake -S lang/cpp -B build/cpp -G Ninja -DCMAKE_BUILD_TYPE=Release +cmake -S lang/cpp -B build/cpp -DCMAKE_BUILD_TYPE=Release cmake --build build/cpp --parallel ``` @@ -17,17 +17,18 @@ CMake builds the Rust FFI through Cargo. A separate `cargo build` is not require ## Embed in a CMake project -Vendor or fetch a pinned, complete Vortex checkout, then add `lang/cpp` once: +Vendor or fetch a pinned, complete Vortex checkout, then add the repository root once: ```cmake -add_subdirectory(path/to/vortex/lang/cpp vortex-cpp) -target_link_libraries(my_target PRIVATE Vortex::cpp_static) +add_subdirectory(path/to/vortex vortex) +target_link_libraries(my_cpp_target PRIVATE Vortex::cpp_static) +target_link_libraries(my_c_target PRIVATE Vortex::ffi_static) ``` -`lang/cpp/CMakeLists.txt` is the only supported entry point. Its only supported consumer target is -`Vortex::cpp_static`, an alias of the `vortex_cxx` build target. It transitively links the separate -Rust FFI archive and required native libraries; consume the target rather than copying -`libvortex_cxx.a` alone. +The root builds the Rust FFI archive once under `vortex-ffi` and layers this directory on top; +`lang/cpp` and `vortex-ffi` can also be added on their own. `Vortex::cpp_static` is an alias of the +`vortex_cxx` build target. It transitively links the FFI archive and required native libraries, so +consume the target rather than copying `libvortex_cxx.a` alone. The integration is source-only: it does not install Vortex or provide `find_package(Vortex)`. It enables C and C++ when needed, but keeps compile and link policy target-scoped and does not replace @@ -66,11 +67,12 @@ target. ## Configuration Set these public Vortex-specific options before `add_subdirectory`, or pass them with `-D` for a -standalone build: +standalone build. The Cargo, CUDA, and sanitizer options are defined by `vortex-ffi/CMakeLists.txt` +and apply to both layers: -- `VORTEX_BUILD_TESTING=ON` builds the `vortex_cxx_test` C++23 test target. Embedded builds require - the parent to enable CTest. Default: `OFF`. -- `VORTEX_BUILD_EXAMPLES=ON` builds the examples. Default: `OFF`. +- `VORTEX_BUILD_TESTING=ON` builds the `vortex_cxx_test` C++23 test target and the C API tests in + `vortex-ffi`. Embedded builds require the parent to enable CTest. Default: `OFF`. +- `VORTEX_BUILD_EXAMPLES=ON` builds the C++ and C examples. Default: `OFF`. - `VORTEX_ENABLE_CUDA=ON` selects the Linux-only `vortex-cuda-ffi` archive, adds `vortex_cuda.h` to the existing `Vortex::cpp_static` target, and requires `find_package(CUDAToolkit)`. Set `CUDAToolkit_ROOT` when CMake cannot find the toolkit. Default: `OFF`. @@ -80,7 +82,8 @@ standalone build: - `VORTEX_WARNINGS_AS_ERRORS` promotes warnings only while compiling `vortex_cxx`; it does not affect tests, examples, consumers, Rust, or CUDA compilation. Default: `ON` standalone and `OFF` when embedded. -- `VORTEX_SANITIZER=asan|tsan` requires `Debug`, Clang or AppleClang, and nightly Rust. ASan enables +- `VORTEX_SANITIZER=asan|tsan` requires `Debug` and Clang or AppleClang, and selects rustup's `nightly` + toolchain unless `RUSTUP_TOOLCHAIN` is set. ASan enables address and undefined-behavior checks for native code and address instrumentation for Rust; TSan enables thread instrumentation. Flags propagate to targets linking Vortex, but CUDA device code, the CUB helper, and nvCOMP are not sanitizer-instrumented. Default: empty. @@ -91,7 +94,8 @@ Cargo runs with the lockfile and the selected native target and profile. Optiona `mimalloc` are not enabled. The integration supplies its complete Rust flag sequence, overriding Rust flags from the environment and Cargo configuration. -Cargo's target cache is stored under `/cargo-target`. The Cargo target runs whenever +Cargo's target cache is stored under the `vortex-ffi` binary directory, which is `ffi/cargo-target` +inside a root or `lang/cpp` build directory. The Cargo target runs whenever Vortex is built, while Cargo decides whether recompilation is needed. The standard CMake clean target removes both CMake outputs and this Cargo cache: @@ -144,11 +148,12 @@ The `reader`, `writer`, `dtype`, `scan`, and `scan_to_arrow` executables are wri ### Sanitizers -Install nightly Rust, then select it explicitly. CMake must use Clang or AppleClang: +Sanitizer builds use rustup's `nightly` toolchain unless `RUSTUP_TOOLCHAIN` selects another, and +CMake must use Clang or AppleClang: ```sh rustup toolchain install nightly -RUSTUP_TOOLCHAIN=nightly cmake -S lang/cpp -B build/cpp-asan -G Ninja \ +cmake -S lang/cpp -B build/cpp-asan -G Ninja \ -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ diff --git a/lang/cpp/examples/CMakeLists.txt b/lang/cpp/examples/CMakeLists.txt index f4ad8eb2fa2..c51777c08f6 100644 --- a/lang/cpp/examples/CMakeLists.txt +++ b/lang/cpp/examples/CMakeLists.txt @@ -1,7 +1,10 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -foreach(name reader writer dtype scan scan_to_arrow) - add_executable(${name} ${name}.cpp) - target_link_libraries(${name} PRIVATE Vortex::cpp_static nanoarrow_shared) +# Target names carry a prefix so a build that also includes the C examples has +# no clashes; the executables keep their plain names. +foreach(_name reader writer dtype scan scan_to_arrow) + add_executable(cpp_${_name} ${_name}.cpp) + set_target_properties(cpp_${_name} PROPERTIES OUTPUT_NAME ${_name}) + target_link_libraries(cpp_${_name} PRIVATE Vortex::cpp_static nanoarrow_shared) endforeach() diff --git a/vortex-ffi/CMakeLists.txt b/vortex-ffi/CMakeLists.txt index e515f841bbc..37bf3c0377c 100644 --- a/vortex-ffi/CMakeLists.txt +++ b/vortex-ffi/CMakeLists.txt @@ -1,132 +1,76 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -cmake_minimum_required(VERSION 3.10) -include(FetchContent) +# Entry point for the Rust FFI archive. It builds vortex-ffi with Cargo and +# exports Vortex::ffi_static for C consumers; lang/cpp layers the C++ wrapper on +# top. It supports standalone configuration and add_subdirectory() from a parent. -project(VortexFFI - VERSION 0.0.1 - LANGUAGES C) +cmake_minimum_required(VERSION 3.28) + +# project() is safe inside a parent's add_subdirectory(). A Vortex-owned top +# level, this directory or the repository root, gets developer defaults below; +# an embedding parent keeps its own. +project(VortexFFI DESCRIPTION "Vortex C FFI" LANGUAGES C CXX) +if(CMAKE_PROJECT_NAME MATCHES "^Vortex") + set(_vortex_top_level ON) +else() + set(_vortex_top_level OFF) +endif() # Vortex does not use C++ modules, so it does not require compiler-specific # dependency scanners such as clang-scan-deps. set(CMAKE_CXX_SCAN_FOR_MODULES OFF) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set(CMAKE_C_STANDARD 17) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wextra -Wpedantic") - -option(BUILD_TESTS "Build tests" OFF) -option(BUILD_EXAMPLES "Build examples" OFF) - -set(SANITIZER "" CACHE STRING "Build with sanitizers") -set(TARGET_TRIPLE "" CACHE STRING "Rust target triple for FFI library") -set(RUST_BUILD_PROFILE "" CACHE STRING "Cargo profile name for the Rust FFI library") - -if (NOT CMAKE_BUILD_TYPE) +if(_vortex_top_level AND NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() -if (NOT SANITIZER STREQUAL "") - message(NOTICE "Sanitizer: ${SANITIZER}") - if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") - message(FATAL_ERROR "Only debug build is supported for sanitizer builds") - endif() - - if (SANITIZER STREQUAL "asan") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address,undefined,leak") - if (BUILD_TESTS) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined,leak") - endif() - elseif (SANITIZER STREQUAL "tsan") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread") - if (BUILD_TESTS) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") - endif() - else() - message(FATAL_ERROR "Unknown sanitizer ${SANITIZER}") - endif() -endif() - -message(NOTICE "Build type: ${CMAKE_BUILD_TYPE}") -if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND - NOT CMAKE_BUILD_TYPE STREQUAL "Release" AND - NOT CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") - message(FATAL_ERROR "vortex-ffi can only be built in Release, Debug, or RelWithDebInfo mode") -endif() - -if (RUST_BUILD_PROFILE STREQUAL "") - if (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") - set(RUST_BUILD_PROFILE "release_debug") - else() - string(TOLOWER ${CMAKE_BUILD_TYPE} RUST_BUILD_PROFILE) - endif() -endif() -set(LIBRARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../target/${TARGET_TRIPLE}/${RUST_BUILD_PROFILE}") - -if(WIN32) - set(LIBRARY_PATH "${LIBRARY_DIR}/libvortex_ffi.lib") - set(LIBRARY_PATH_SHARED "${LIBRARY_DIR}/libvortex_ffi.dll") -elseif(APPLE) - set(LIBRARY_PATH "${LIBRARY_DIR}/libvortex_ffi.a") - set(LIBRARY_PATH_SHARED "${LIBRARY_DIR}/libvortex_ffi.dylib") -else() - set(LIBRARY_PATH "${LIBRARY_DIR}/libvortex_ffi.a") - set(LIBRARY_PATH_SHARED "${LIBRARY_DIR}/libvortex_ffi.so") -endif() - -set(LIBRARY_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/cinclude") - -message(NOTICE " -Library dir ${LIBRARY_DIR} -Shared library path ${LIBRARY_PATH_SHARED} -Static library path ${LIBRARY_PATH} -Headers path ${LIBRARY_HEADERS}") - -if (NOT EXISTS "${LIBRARY_PATH_SHARED}") - message(FATAL_ERROR "Shared library not found, run `cargo build --release -p vortex-ffi`") -endif() -if (NOT EXISTS "${LIBRARY_PATH}") - message(FATAL_ERROR "Static library not found, run `cargo build --release -p vortex-ffi`") -endif() - -add_library(vortex_ffi STATIC IMPORTED) -set_target_properties(vortex_ffi PROPERTIES - IMPORTED_LOCATION "${LIBRARY_PATH}" - INTERFACE_INCLUDE_DIRECTORIES "${LIBRARY_HEADERS}" -) - -add_library(vortex_ffi_shared SHARED IMPORTED) -set_target_properties(vortex_ffi_shared PROPERTIES - IMPORTED_LOCATION "${LIBRARY_PATH_SHARED}" - INTERFACE_INCLUDE_DIRECTORIES "${LIBRARY_HEADERS}" - INTERFACE_LINK_OPTIONS "LINKER:-rpath,${LIBRARY_DIR}" -) - -if (BUILD_TESTS OR BUILD_EXAMPLES) +# Public options. Embedding parents set these before add_subdirectory(). The +# testing and examples options are shared with lang/cpp. +option(VORTEX_BUILD_TESTING "Build Vortex tests" OFF) +option(VORTEX_BUILD_EXAMPLES "Build Vortex examples" OFF) +option(VORTEX_ENABLE_CUDA "Build the CUDA-enabled Vortex FFI" OFF) +set(VORTEX_CARGO_PROFILE "" CACHE STRING "Override the Cargo profile selected from CMAKE_BUILD_TYPE") + +# Sanitizers instrument the Rust archive, its C dependencies, and every target +# that links Vortex::ffi_static; Configure.cmake enforces the toolchain constraints. +set(VORTEX_SANITIZER "" CACHE STRING "Instrument Vortex and its consumers with asan or tsan") +option(VORTEX_SANITIZE_RUST_STD + "Rebuild Rust's standard library with sanitizer instrumentation" + OFF) + +option(VORTEX_WARNINGS_AS_ERRORS + "Treat warnings in Vortex-owned sources as errors" + "${_vortex_top_level}") + +# Define the Cargo build and the imported vortex_ffi_static archive. +include(cmake/Configure.cmake) + +# The only supported consumer-facing name for the C API. +add_library(Vortex::ffi_static ALIAS vortex_ffi_static) + +# Test and example dependencies are fetched at configure time and never installed. +if(VORTEX_BUILD_TESTING OR VORTEX_BUILD_EXAMPLES) + include(FetchContent) FetchContent_Declare( - Nanoarrow - GIT_REPOSITORY https://github.com/apache/arrow-nanoarrow - GIT_TAG apache-arrow-nanoarrow-0.8.0 - ) + Nanoarrow + GIT_REPOSITORY https://github.com/apache/arrow-nanoarrow + GIT_TAG apache-arrow-nanoarrow-0.8.0) FetchContent_MakeAvailable(Nanoarrow) - # arrow-nanoarrow creates a self-referential symlink + # arrow-nanoarrow creates a self-referential symlink: # python/subprojects/arrow-nanoarrow -> ../.. - if (IS_SYMLINK "${nanoarrow_SOURCE_DIR}/python/subprojects/arrow-nanoarrow") + if(IS_SYMLINK "${nanoarrow_SOURCE_DIR}/python/subprojects/arrow-nanoarrow") file(REMOVE "${nanoarrow_SOURCE_DIR}/python/subprojects/arrow-nanoarrow") endif() endif() -if (BUILD_TESTS) - enable_language(CXX) - set(CMAKE_CXX_STANDARD 20) - set(CMAKE_CXX_STANDARD_REQUIRED ON) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Wextra -Wpedantic") +if(VORTEX_BUILD_TESTING) + # Embedding parents must also call enable_testing() for ctest to find these. enable_testing() add_subdirectory(test) endif() -if (BUILD_EXAMPLES) +if(VORTEX_BUILD_EXAMPLES) add_subdirectory(examples) endif() diff --git a/vortex-ffi/README.md b/vortex-ffi/README.md index 5cac7b7f490..d6f6ed46bc7 100644 --- a/vortex-ffi/README.md +++ b/vortex-ffi/README.md @@ -27,100 +27,74 @@ cargo +nightly build -p vortex-ffi ## Usage from a CMake project -``` -# in vortex folder -cargo build --release -p vortex-ffi +CMake builds the Rust archive through Cargo; no separate `cargo build` is needed. Add the +repository root, or this directory alone, and link the static target: -# in your CMakeLists.txt -include_directory(vortex/vortex-ffi) -target_link_libraries(my_target, vortex_ffi_shared) -# or target_link_libraries(my_target, vortex_ffi) +```cmake +add_subdirectory(path/to/vortex vortex) +target_link_libraries(my_target PRIVATE Vortex::ffi_static) ``` +The target carries the headers in `cinclude/`, the archive, and the system libraries it needs. +Build options such as `VORTEX_CARGO_PROFILE`, `VORTEX_ENABLE_CUDA`, and `VORTEX_SANITIZER` are +documented in the [C++ README](../lang/cpp/README.md) and apply to both layers. + ## Running C examples ```sh -cmake -Bbuild -DBUILD_EXAMPLES=1 -cmake --build build -./build/examples/dtype -./build/examples/scan -./build/examples/scan_to_arrow -./build/examples/write_sample +cmake -S . -B build -DVORTEX_BUILD_EXAMPLES=ON +cmake --build build --parallel +./build/examples/write_sample sample.vortex +./build/examples/dtype 'sample.vortex' +./build/examples/scan 'sample.vortex' +./build/examples/scan_to_arrow 'sample.vortex' ``` ## Testing C part -Build the test library: - -```sh -cmake -Bbuild -DBUILD_TESTS=1 -cmake --build build -``` - -Run the tests: +The tests use Catch2, so a C++ compiler is required: ```sh -ctest --test-dir build -j $(nproc) +cmake -S . -B build -DVORTEX_BUILD_TESTING=ON +cmake --build build --parallel +ctest --test-dir build --output-on-failure ``` -You will need C++ compiler toolchain to run the tests since they use Catch2. - ## Testing Rust part with sanitizers -AddressSanitizer: +The Rust tests run under a sanitizer with nightly `cargo test`. Substitute the native target +triple, for example `x86_64-unknown-linux-gnu`: ```sh # inside vortex-ffi -RUSTFLAGS="-Z sanitizer=address" \ -cargo +nightly test -Zbuild-std \ - --no-default-features --target \ - -- --no-capture -``` - -MemorySanitizer: - -```sh -RUSTFLAGS="-Z sanitizer=memory -Cunsafe-allow-abi-mismatch=sanitizer" \ -cargo +nightly test -Zbuild-std \ - --no-default-features --target \ - -- --no-capture +RUSTFLAGS="-Zsanitizer=address -Cunsafe-allow-abi-mismatch=sanitizer" \ +cargo +nightly test -Zbuild-std --target --tests -- --no-capture ``` -ThreadSanitizer: - -```sh -TSAN_OPTIONS="suppressions=$HOME/vortex/vortex-ffi/tsan_suppressions.txt" \ -RUSTFLAGS="-Z sanitizer=thread -Cunsafe-allow-abi-mismatch=sanitizer" \ -cargo +nightly test -Zbuild-std \ - --no-default-features --target \ - -- --no-capture -``` +Use `-Zsanitizer=memory` for MemorySanitizer and `-Zsanitizer=thread` for ThreadSanitizer; the +latter needs `TSAN_OPTIONS="suppressions=$PWD/tsan_suppressions.txt"`. - `-Zbuild-std` is needed as memory and thread sanitizers report std errors otherwise. -- `--no-default-features` is needed as we use Mimalloc otherwise which interferes with sanitizers. - `allow-abi-mismatch` is safe because in our dependency graph only crates like `compiler_builtins` unset sanitization, and they do it on purpose. +- `--tests` skips doctests, which rustdoc builds without `RUSTFLAGS` and which would therefore + mismatch the sanitizer-built dependencies. - Make sure to use `cargo test` and not `cargo nextest` as nextest reports less leaks. - If you want stack trace symbolization, install `llvm-symbolizer`. ## Testing Rust and C with sanitizers -1. Build FFI library with external sanitizer runtime: - -```sh -RUSTFLAGS="-Zsanitizer=address -Zexternal-clangrt" \ -cargo +nightly build -Zbuild-std --target= \ - --no-default-features -p vortex-ffi -``` - -2. Build tests with target triple: +CMake instruments the Rust archive, its C dependencies, and the tests together. It uses rustup's +`nightly` toolchain, which needs the `rust-src` component, unless `RUSTUP_TOOLCHAIN` selects another, +and it needs Clang for the C and C++ side: ```sh -cmake -Bbuild -DSANITIZER=asan -DTARGET_TRIPLE= +cmake -S . -B build \ + -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ + -DVORTEX_SANITIZER=asan -DVORTEX_SANITIZE_RUST_STD=ON \ + -DVORTEX_BUILD_TESTING=ON +cmake --build build --parallel +./build/test/vortex_ffi_test 2>&1 | rustfilt -i- ``` -3. Run the tests (ctest doesn't output failures in detail): - -```sh -./build/test/vortex_ffi_test 2>& 1 | rustfilt -i- -``` +`VORTEX_SANITIZER=tsan` selects ThreadSanitizer; point `TSAN_OPTIONS` at `tsan_suppressions.txt`. diff --git a/lang/cpp/cmake/CargoBuild.cmake b/vortex-ffi/cmake/CargoBuild.cmake similarity index 99% rename from lang/cpp/cmake/CargoBuild.cmake rename to vortex-ffi/cmake/CargoBuild.cmake index 0bf25914a64..36918989ebe 100644 --- a/lang/cpp/cmake/CargoBuild.cmake +++ b/vortex-ffi/cmake/CargoBuild.cmake @@ -121,7 +121,7 @@ function(_vortex_make_cargo_environment target_key_lower output) endfunction() _vortex_require_build_inputs() -get_filename_component(_workspace_root "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) +get_filename_component(_workspace_root "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) _vortex_cc_target_env_key(_target_key) _vortex_make_cargo_command(_cargo_command) _vortex_make_cargo_environment("${_target_key}" _cargo_environment) diff --git a/lang/cpp/cmake/Configure.cmake b/vortex-ffi/cmake/Configure.cmake similarity index 97% rename from lang/cpp/cmake/Configure.cmake rename to vortex-ffi/cmake/Configure.cmake index 842dad3ea41..143345f9dc6 100644 --- a/lang/cpp/cmake/Configure.cmake +++ b/vortex-ffi/cmake/Configure.cmake @@ -233,7 +233,7 @@ endfunction() block(SCOPE_FOR VARIABLES) _vortex_resolve_cargo_profile(_configuration _cargo_profile _cargo_artifact_directory) - get_filename_component(_workspace_root "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) + get_filename_component(_workspace_root "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) _vortex_resolve_ffi_package( "${_workspace_root}" _ffi_package @@ -248,6 +248,11 @@ block(SCOPE_FOR VARIABLES) _sanitizer_compile_flag _sanitizer_rustflags _cargo_build_std) + # Sanitizer builds need nightly-only rustc flags; default to rustup's + # nightly unless the environment already selected a toolchain. + if(NOT VORTEX_SANITIZER STREQUAL "" AND VORTEX_RUSTUP_TOOLCHAIN STREQUAL "") + set(VORTEX_RUSTUP_TOOLCHAIN nightly) + endif() if(NOT "$ENV{CARGO_ENCODED_RUSTFLAGS}" STREQUAL "" OR NOT "$ENV{RUSTFLAGS}" STREQUAL "") message(STATUS "Vortex ignores ambient Rust flags in its Cargo build") @@ -312,9 +317,8 @@ block(SCOPE_FOR VARIABLES) USES_TERMINAL VERBATIM) - # The imported target remains directory-scoped and is only carried to users - # through Vortex::cpp_static. - add_library(vortex_ffi_static STATIC IMPORTED) + # Global so that sibling directories such as lang/cpp can link it. + add_library(vortex_ffi_static STATIC IMPORTED GLOBAL) set_target_properties(vortex_ffi_static PROPERTIES IMPORTED_LOCATION "${_ffi_archive}" INTERFACE_INCLUDE_DIRECTORIES "${_ffi_include_dirs}") diff --git a/lang/cpp/cmake/Helpers.cmake b/vortex-ffi/cmake/Helpers.cmake similarity index 100% rename from lang/cpp/cmake/Helpers.cmake rename to vortex-ffi/cmake/Helpers.cmake diff --git a/lang/cpp/cmake/RustToolchain.cmake b/vortex-ffi/cmake/RustToolchain.cmake similarity index 100% rename from lang/cpp/cmake/RustToolchain.cmake rename to vortex-ffi/cmake/RustToolchain.cmake diff --git a/lang/cpp/cmake/SystemDependencies.cmake b/vortex-ffi/cmake/SystemDependencies.cmake similarity index 89% rename from lang/cpp/cmake/SystemDependencies.cmake rename to vortex-ffi/cmake/SystemDependencies.cmake index 9e620e647e6..d52c81a9697 100644 --- a/lang/cpp/cmake/SystemDependencies.cmake +++ b/vortex-ffi/cmake/SystemDependencies.cmake @@ -24,8 +24,8 @@ function(_vortex_attach_system_dependencies target rust_target) ${CMAKE_DL_LIBS} c) elseif(rust_target STREQUAL "aarch64-apple-darwin") - # The public Vortex target is C++, whose driver adds libc++ and - # libSystem. Publish only additional framework/library requirements. + # The archive needs no C++ runtime, so C and C++ consumers alike only + # need libSystem from their driver plus these extra libraries. find_library(_vortex_core_foundation CoreFoundation REQUIRED NO_CACHE) target_link_libraries("${target}" INTERFACE iconv "${_vortex_core_foundation}") else() diff --git a/vortex-ffi/examples/CMakeLists.txt b/vortex-ffi/examples/CMakeLists.txt index 47228d9a903..4763a0b0513 100644 --- a/vortex-ffi/examples/CMakeLists.txt +++ b/vortex-ffi/examples/CMakeLists.txt @@ -1,18 +1,16 @@ # SPDX-License-Identifier: CC-BY-4.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -# allow linking with vortex_ffi_shared although it's not in current folder -cmake_policy(SET CMP0079 NEW) - -add_executable(scan scan.c) -target_link_libraries(scan PRIVATE vortex_ffi_shared) - -add_executable(scan_to_arrow scan_to_arrow.c) -target_link_libraries(scan_to_arrow PRIVATE - nanoarrow_shared vortex_ffi_shared) - -add_executable(dtype dtype.c) -target_link_libraries(dtype PRIVATE vortex_ffi_shared) - -add_executable(write_sample write_sample.c) -target_link_libraries(write_sample PRIVATE vortex_ffi_shared) +# Target names carry a prefix so a build that also includes the C++ examples +# has no clashes; the executables keep their plain names. +foreach(_name scan scan_to_arrow dtype write_sample) + add_executable(ffi_${_name} ${_name}.c) + set_target_properties(ffi_${_name} PROPERTIES OUTPUT_NAME ${_name}) + target_compile_features(ffi_${_name} PRIVATE c_std_17) + target_compile_options(ffi_${_name} PRIVATE -Wall -Wextra -Wpedantic) + if(VORTEX_WARNINGS_AS_ERRORS) + target_compile_options(ffi_${_name} PRIVATE -Werror) + endif() + target_link_libraries(ffi_${_name} PRIVATE Vortex::ffi_static) +endforeach() +target_link_libraries(ffi_scan_to_arrow PRIVATE nanoarrow_shared) diff --git a/vortex-ffi/examples/scan.c b/vortex-ffi/examples/scan.c index 67df597c69f..b91c8ec1c5e 100644 --- a/vortex-ffi/examples/scan.c +++ b/vortex-ffi/examples/scan.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: CC-BY-4.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors #include "vortex.h" +#include #include #include #include @@ -16,10 +17,10 @@ void print_estimate(const char *what, const vx_estimate *estimate) { printf("%s: unknown\n", what); return; case VX_ESTIMATE_EXACT: - printf("%s: %lu\n", what, estimate->estimate); + printf("%s: %" PRIu64 "\n", what, estimate->estimate); return; case VX_ESTIMATE_INEXACT: - printf("%s: approximately %lu\n", what, estimate->estimate); + printf("%s: approximately %" PRIu64 "\n", what, estimate->estimate); break; } } @@ -30,7 +31,7 @@ void print_error(const char *what, const vx_error *error) { } struct scan_thread_info { - pthread_t thread_id; + size_t thread_id; pthread_mutex_t *mutex; vx_scan *scan; size_t partitions, arrays, rows; @@ -59,7 +60,7 @@ void *execute_scan_thread(void *arg) { return NULL; } - printf("Thread %lu processing partition %lu, ", info->thread_id + 1, info->partitions); + printf("Thread %zu processing partition %zu, ", info->thread_id + 1, info->partitions); print_estimate("row count", &row_count); // An array is a batch of rows from a partition @@ -77,21 +78,18 @@ void *execute_scan_thread(void *arg) { } } - printf("Thread %lu finished, processed %lu partitions, %lu arrays, %lu rows\n", - info->thread_id + 1, - info->partitions, - info->arrays, - info->rows); + printf("Thread %zu finished, processed %zu partitions, %zu arrays, %zu rows\n", info->thread_id + 1, + info->partitions, info->arrays, info->rows); return NULL; } -vx_error *execute_scan(vx_scan *scan, pthread_t num_threads) { +vx_error *execute_scan(vx_scan *scan, size_t num_threads) { pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_t threads[MAX_THREADS]; struct scan_thread_info infos[MAX_THREADS] = {0}; - printf("Starting scan, using %lu threads\n", num_threads); - for (pthread_t id = 0; id < num_threads; ++id) { + printf("Starting scan, using %zu threads\n", num_threads); + for (size_t id = 0; id < num_threads; ++id) { struct scan_thread_info *info = &infos[id]; info->thread_id = id; info->mutex = &mutex; @@ -100,7 +98,7 @@ vx_error *execute_scan(vx_scan *scan, pthread_t num_threads) { } size_t partitions = 0, arrays = 0, rows = 0; - for (pthread_t id = 0; id < num_threads; ++id) { + for (size_t id = 0; id < num_threads; ++id) { pthread_join(threads[id], NULL); struct scan_thread_info *info = &infos[id]; @@ -114,11 +112,11 @@ vx_error *execute_scan(vx_scan *scan, pthread_t num_threads) { rows += info->rows; } - printf("Finished scan, processed %lu partitions, %lu arrays, %lu rows\n", partitions, arrays, rows); + printf("Finished scan, processed %zu partitions, %zu arrays, %zu rows\n", partitions, arrays, rows); return NULL; } -int parse_options(int argc, char *argv[], pthread_t *threads, char **paths) { +int parse_options(int argc, char *argv[], size_t *threads, char **paths) { int opt; while ((opt = getopt(argc, argv, "j:")) != -1) { switch (opt) { @@ -132,7 +130,7 @@ int parse_options(int argc, char *argv[], pthread_t *threads, char **paths) { } if (*threads != 0 && (*threads < 1 || *threads > MAX_THREADS)) { - fprintf(stderr, "Invalid thread count %lu, expected [1; 64]\n", *threads); + fprintf(stderr, "Invalid thread count %zu, expected [1; 64]\n", *threads); return 1; } @@ -146,7 +144,7 @@ int parse_options(int argc, char *argv[], pthread_t *threads, char **paths) { } int main(int argc, char *argv[]) { - pthread_t threads = 0; + size_t threads = 0; char *paths; if (parse_options(argc, argv, &threads, &paths)) { return 1; diff --git a/vortex-ffi/test/CMakeLists.txt b/vortex-ffi/test/CMakeLists.txt index be0288ef954..dcde94fcffc 100644 --- a/vortex-ffi/test/CMakeLists.txt +++ b/vortex-ffi/test/CMakeLists.txt @@ -1,21 +1,33 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors -include(CTest) + FetchContent_Declare( - Catch - GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.8.1 -) + Catch + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.8.1) FetchContent_MakeAvailable(Catch) -include(Catch) +# By path: Catch2 exports its module directory only to the directory that +# populated it, which may be a parent that fetched Catch2 first. +include("${catch_SOURCE_DIR}/extras/Catch.cmake") + +# Catch2 enables std::string_view, optional, variant, and byte support from +# C++17, in headers and compiled sources alike; build it with those on so it +# matches the C++20 tests that include its headers. +target_compile_features(Catch2 PRIVATE cxx_std_17) # https://github.com/catchorg/Catch2/issues/1833 target_compile_definitions(Catch2 PRIVATE CATCH_CONFIG_NO_POSIX_SIGNALS) -file(GLOB TEST_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") -message(NOTICE "Test files ${TEST_FILES}") -add_executable(vortex_ffi_test ${TEST_FILES}) +file(GLOB _vortex_ffi_test_sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") + +add_executable(vortex_ffi_test ${_vortex_ffi_test_sources}) +target_compile_features(vortex_ffi_test PRIVATE cxx_std_20) +target_compile_options(vortex_ffi_test PRIVATE -Wall -Wextra -Wpedantic) +if(VORTEX_WARNINGS_AS_ERRORS) + target_compile_options(vortex_ffi_test PRIVATE -Werror) +endif() target_link_libraries(vortex_ffi_test PRIVATE - vortex_ffi_shared + Vortex::ffi_static Catch2::Catch2WithMain nanoarrow_shared) + catch_discover_tests(vortex_ffi_test) From 64080fd7fabf7cc8e5c36e6bcadfc36a0f7832ee Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 17:01:40 +0100 Subject: [PATCH 19/24] Export compile commands for standalone configures Restore CMAKE_EXPORT_COMPILE_COMMANDS, which the consolidated entry points had dropped, for configures where a Vortex directory is the top level so editor tooling keeps working. Embedded builds leave the parent's setting alone. Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- CMakeLists.txt | 5 +++++ lang/cpp/CMakeLists.txt | 5 +++++ vortex-ffi/CMakeLists.txt | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ab5d5483c8..3ffe49e19c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,11 @@ cmake_minimum_required(VERSION 3.28) project(VortexNative DESCRIPTION "Vortex native libraries" LANGUAGES NONE) +# A standalone configure writes compile_commands.json for editor tooling. +if(PROJECT_IS_TOP_LEVEL) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +endif() + # ctest only descends into subdirectories from a root that has testing enabled. enable_testing() diff --git a/lang/cpp/CMakeLists.txt b/lang/cpp/CMakeLists.txt index 25065b197d6..8310a3c304c 100644 --- a/lang/cpp/CMakeLists.txt +++ b/lang/cpp/CMakeLists.txt @@ -21,6 +21,11 @@ endif() # dependency scanners such as clang-scan-deps. set(CMAKE_CXX_SCAN_FOR_MODULES OFF) +# A standalone configure writes compile_commands.json for editor tooling. +if(PROJECT_IS_TOP_LEVEL) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +endif() + if(_vortex_top_level AND NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() diff --git a/vortex-ffi/CMakeLists.txt b/vortex-ffi/CMakeLists.txt index 37bf3c0377c..da49837c526 100644 --- a/vortex-ffi/CMakeLists.txt +++ b/vortex-ffi/CMakeLists.txt @@ -21,6 +21,11 @@ endif() # dependency scanners such as clang-scan-deps. set(CMAKE_CXX_SCAN_FOR_MODULES OFF) +# A standalone configure writes compile_commands.json for editor tooling. +if(PROJECT_IS_TOP_LEVEL) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +endif() + if(_vortex_top_level AND NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() From 706dbfe49b83b81d3da9add4cc15eba207fa8be5 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 17:06:52 +0100 Subject: [PATCH 20/24] Accept a list of sanitizers in VORTEX_SANITIZER Take a comma- or semicolon-separated list of asan, lsan, ubsan, and tsan instead of two fixed bundles, and map each entry to its native and Rust names. UBSan has no Rust counterpart, so a native-only selection needs no nightly toolchain and none is defaulted. CI asks for asan,lsan,ubsan explicitly to keep the coverage the asan bundle used to imply. Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- .github/workflows/ci.yml | 2 +- .github/workflows/rust-instrumented.yml | 2 +- CMakeLists.txt | 4 +- lang/cpp/README.md | 14 ++-- vortex-ffi/CMakeLists.txt | 3 +- vortex-ffi/README.md | 12 +-- vortex-ffi/cmake/Configure.cmake | 97 ++++++++++++++----------- 7 files changed, 75 insertions(+), 59 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7edc7c60895..8a5ff470402 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -598,7 +598,7 @@ jobs: -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ - -DVORTEX_SANITIZER=asan \ + -DVORTEX_SANITIZER=asan,lsan,ubsan \ -DVORTEX_SANITIZE_RUST_STD=ON \ -DVORTEX_BUILD_TESTING=ON \ -DVORTEX_BUILD_EXAMPLES=ON diff --git a/.github/workflows/rust-instrumented.yml b/.github/workflows/rust-instrumented.yml index bb515d69fd6..9a97c6093e3 100644 --- a/.github/workflows/rust-instrumented.yml +++ b/.github/workflows/rust-instrumented.yml @@ -226,7 +226,7 @@ jobs: include: # We don't run memory sanitizer as it's clang-only and provides many # false positives for Catch2 - - sanitizer: asan + - sanitizer: "asan,lsan,ubsan" - sanitizer: tsan name: "Rust/C++ FFI tests (${{ matrix.sanitizer }})" timeout-minutes: 5 diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ffe49e19c7..683cef6f506 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,9 @@ # VORTEX_BUILD_EXAMPLES Examples of both layers. # VORTEX_CARGO_PROFILE Cargo profile; otherwise mapped from CMAKE_BUILD_TYPE. # VORTEX_ENABLE_CUDA Linux-only CUDA-enabled FFI archive. -# VORTEX_SANITIZER asan or tsan for Rust, C, and C++ together. +# VORTEX_SANITIZER Comma-separated list of asan, lsan, ubsan, tsan +# for Rust, C, and C++ together; ubsan has no Rust +# side and covers C and C++ only. # VORTEX_SANITIZE_RUST_STD Also instrument Rust's standard library. # VORTEX_WARNINGS_AS_ERRORS ON for a Vortex-owned top level, OFF when embedded. # diff --git a/lang/cpp/README.md b/lang/cpp/README.md index 9fb47b9e393..6820a3dac8f 100644 --- a/lang/cpp/README.md +++ b/lang/cpp/README.md @@ -82,11 +82,13 @@ and apply to both layers: - `VORTEX_WARNINGS_AS_ERRORS` promotes warnings only while compiling `vortex_cxx`; it does not affect tests, examples, consumers, Rust, or CUDA compilation. Default: `ON` standalone and `OFF` when embedded. -- `VORTEX_SANITIZER=asan|tsan` requires `Debug` and Clang or AppleClang, and selects rustup's `nightly` - toolchain unless `RUSTUP_TOOLCHAIN` is set. ASan enables - address and undefined-behavior checks for native code and address instrumentation for Rust; TSan - enables thread instrumentation. Flags propagate to targets linking Vortex, but CUDA device code, - the CUB helper, and nvCOMP are not sanitizer-instrumented. Default: empty. +- `VORTEX_SANITIZER` takes a comma-separated list of `asan`, `lsan`, `ubsan`, and `tsan`, for + example `asan,ubsan`. It requires `Debug` and Clang or AppleClang. Each sanitizer instruments the + C and C++ code, including Cargo-built C dependencies, and all but `ubsan` also instrument the Rust + code, since rustc has no UBSan. Rust instrumentation selects rustup's `nightly` toolchain unless + `RUSTUP_TOOLCHAIN` is set. Apple clang does not support `lsan` on arm64. Flags + propagate to targets linking Vortex, but CUDA device code, the CUB helper, and nvCOMP are not + sanitizer-instrumented. Default: empty. - `VORTEX_SANITIZE_RUST_STD=ON` rebuilds Rust's standard library with the selected sanitizer and requires the nightly `rust-src` component. Default: `OFF`. @@ -157,7 +159,7 @@ cmake -S lang/cpp -B build/cpp-asan -G Ninja \ -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ - -DVORTEX_SANITIZER=asan \ + -DVORTEX_SANITIZER=asan,ubsan \ -DVORTEX_BUILD_TESTING=ON cmake --build build/cpp-asan --parallel ctest --test-dir build/cpp-asan --output-on-failure diff --git a/vortex-ffi/CMakeLists.txt b/vortex-ffi/CMakeLists.txt index da49837c526..dc4c56eb96b 100644 --- a/vortex-ffi/CMakeLists.txt +++ b/vortex-ffi/CMakeLists.txt @@ -39,7 +39,8 @@ set(VORTEX_CARGO_PROFILE "" CACHE STRING "Override the Cargo profile selected fr # Sanitizers instrument the Rust archive, its C dependencies, and every target # that links Vortex::ffi_static; Configure.cmake enforces the toolchain constraints. -set(VORTEX_SANITIZER "" CACHE STRING "Instrument Vortex and its consumers with asan or tsan") +set(VORTEX_SANITIZER "" CACHE STRING + "Comma-separated sanitizers for Vortex and its consumers: asan, lsan, ubsan, tsan") option(VORTEX_SANITIZE_RUST_STD "Rebuild Rust's standard library with sanitizer instrumentation" OFF) diff --git a/vortex-ffi/README.md b/vortex-ffi/README.md index d6f6ed46bc7..2acf080d6a9 100644 --- a/vortex-ffi/README.md +++ b/vortex-ffi/README.md @@ -84,17 +84,19 @@ latter needs `TSAN_OPTIONS="suppressions=$PWD/tsan_suppressions.txt"`. ## Testing Rust and C with sanitizers -CMake instruments the Rust archive, its C dependencies, and the tests together. It uses rustup's -`nightly` toolchain, which needs the `rust-src` component, unless `RUSTUP_TOOLCHAIN` selects another, -and it needs Clang for the C and C++ side: +CMake instruments the Rust archive, its C dependencies, and the tests together. `VORTEX_SANITIZER` +takes a comma-separated list of `asan`, `lsan`, `ubsan`, and `tsan`. Each instruments the C and C++ +code, and all but `ubsan` also instrument the Rust code, since rustc has no UBSan. Rust +instrumentation uses rustup's `nightly` toolchain, which needs the `rust-src` component, +unless `RUSTUP_TOOLCHAIN` selects another, and the C and C++ side needs Clang: ```sh cmake -S . -B build \ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ - -DVORTEX_SANITIZER=asan -DVORTEX_SANITIZE_RUST_STD=ON \ + -DVORTEX_SANITIZER=asan,ubsan -DVORTEX_SANITIZE_RUST_STD=ON \ -DVORTEX_BUILD_TESTING=ON cmake --build build --parallel ./build/test/vortex_ffi_test 2>&1 | rustfilt -i- ``` -`VORTEX_SANITIZER=tsan` selects ThreadSanitizer; point `TSAN_OPTIONS` at `tsan_suppressions.txt`. +For ThreadSanitizer use `tsan` and point `TSAN_OPTIONS` at `tsan_suppressions.txt`. diff --git a/vortex-ffi/cmake/Configure.cmake b/vortex-ffi/cmake/Configure.cmake index 143345f9dc6..7b74917cbdf 100644 --- a/vortex-ffi/cmake/Configure.cmake +++ b/vortex-ffi/cmake/Configure.cmake @@ -126,57 +126,66 @@ function(_vortex_resolve_ffi_package set(${cuda_root_output} "${_cuda_root}" PARENT_SCOPE) endfunction() -# Validate and configure the optional sanitizer for native and Rust code. -# Sanitizers require Clang and a Debug build; rustc itself rejects the -# nightly-only flags on stable. Standard-library instrumentation is optional. +# Validate and configure the optional sanitizers. VORTEX_SANITIZER is a comma- +# or semicolon-separated list of asan, lsan, ubsan, and tsan. Each entry +# instruments the C and C++ code that clang compiles, including Cargo-built C +# dependencies, and all but ubsan also instrument the Rust code through rustc, +# which has no UBSan. Sanitizers require Clang and a Debug build, and rustc +# itself rejects the nightly-only Rust flags on stable. Standard-library +# instrumentation is optional. function(_vortex_resolve_sanitizer configuration native_flag_output rustflags_output build_std_output) - string(TOUPPER "${VORTEX_SANITIZER}" VORTEX_SANITIZER) - # Reject unknown names before deriving native and Rust compiler flags. - if(NOT VORTEX_SANITIZER STREQUAL "" AND - NOT VORTEX_SANITIZER STREQUAL "ASAN" AND - NOT VORTEX_SANITIZER STREQUAL "TSAN") - message(FATAL_ERROR - "VORTEX_SANITIZER must be empty, asan, or tsan; got " - "${VORTEX_SANITIZER}") - endif() - - # Rebuilding std is meaningful only when sanitizer instrumentation is enabled. - if(VORTEX_SANITIZE_RUST_STD AND VORTEX_SANITIZER STREQUAL "") - message(FATAL_ERROR - "VORTEX_SANITIZE_RUST_STD=ON requires VORTEX_SANITIZER=asan or tsan") - endif() + string(REPLACE "," ";" _sanitizers "${VORTEX_SANITIZER}") + string(TOLOWER "${_sanitizers}" _sanitizers) + + set(_native "") + set(_rust "") + foreach(_sanitizer IN LISTS _sanitizers) + if(_sanitizer STREQUAL "asan") + list(APPEND _native address) + list(APPEND _rust address) + elseif(_sanitizer STREQUAL "lsan") + list(APPEND _native leak) + list(APPEND _rust leak) + elseif(_sanitizer STREQUAL "ubsan") + list(APPEND _native undefined) + elseif(_sanitizer STREQUAL "tsan") + list(APPEND _native thread) + list(APPEND _rust thread) + else() + message(FATAL_ERROR + "VORTEX_SANITIZER accepts asan, lsan, ubsan, and tsan; got '${_sanitizer}'") + endif() + endforeach() - # Use Clang so Rust and native code share one compatible sanitizer runtime. - if(NOT VORTEX_SANITIZER STREQUAL "" AND - (NOT CMAKE_C_COMPILER_ID MATCHES "^(AppleClang|Clang)$" OR - NOT CMAKE_CXX_COMPILER_ID MATCHES "^(AppleClang|Clang)$")) + # Rebuilding std is meaningful only when Rust itself is instrumented. + if(VORTEX_SANITIZE_RUST_STD AND NOT _rust) message(FATAL_ERROR - "Vortex sanitizer builds require Clang or AppleClang for C and C++; " - "found ${CMAKE_C_COMPILER_ID} and ${CMAKE_CXX_COMPILER_ID}") - endif() - - # Restrict sanitizer instrumentation to the supported Debug configuration. - if(NOT VORTEX_SANITIZER STREQUAL "" AND NOT configuration STREQUAL "DEBUG") - message(FATAL_ERROR "Vortex sanitizer builds require CMAKE_BUILD_TYPE=Debug") + "VORTEX_SANITIZE_RUST_STD=ON requires a Rust sanitizer: asan, lsan, or tsan") endif() set(_native_flag "") - set(_rust_sanitizer "") - if(VORTEX_SANITIZER STREQUAL "ASAN") - set(_native_flag "-fsanitize=address,undefined") - set(_rust_sanitizer "address") - elseif(VORTEX_SANITIZER STREQUAL "TSAN") - set(_native_flag "-fsanitize=thread") - set(_rust_sanitizer "thread") - endif() - set(_rustflags "") - set(_build_std "${VORTEX_SANITIZE_RUST_STD}") - if(_rust_sanitizer) + if(_native) + # Use Clang so Rust and native code share one compatible sanitizer runtime. + if(NOT CMAKE_C_COMPILER_ID MATCHES "^(AppleClang|Clang)$" OR + NOT CMAKE_CXX_COMPILER_ID MATCHES "^(AppleClang|Clang)$") + message(FATAL_ERROR + "Vortex sanitizer builds require Clang or AppleClang for C and C++; " + "found ${CMAKE_C_COMPILER_ID} and ${CMAKE_CXX_COMPILER_ID}") + endif() + if(NOT configuration STREQUAL "DEBUG") + message(FATAL_ERROR "Vortex sanitizer builds require CMAKE_BUILD_TYPE=Debug") + endif() + + list(JOIN _native "," _native_joined) + set(_native_flag "-fsanitize=${_native_joined}") + endif() + if(_rust) + list(JOIN _rust "," _rust_joined) list(APPEND _rustflags -A warnings -Cunsafe-allow-abi-mismatch=sanitizer @@ -184,12 +193,12 @@ function(_vortex_resolve_sanitizer -C opt-level=0 # Use the sanitizer runtime linked by the final C++ target for both languages. -Zexternal-clangrt - "-Zsanitizer=${_rust_sanitizer}") + "-Zsanitizer=${_rust_joined}") endif() set(${native_flag_output} "${_native_flag}" PARENT_SCOPE) set(${rustflags_output} "${_rustflags}" PARENT_SCOPE) - set(${build_std_output} "${_build_std}" PARENT_SCOPE) + set(${build_std_output} "${VORTEX_SANITIZE_RUST_STD}" PARENT_SCOPE) endfunction() # Reconstruct CMake's effective C and C++ flags for Cargo build scripts, then @@ -248,9 +257,9 @@ block(SCOPE_FOR VARIABLES) _sanitizer_compile_flag _sanitizer_rustflags _cargo_build_std) - # Sanitizer builds need nightly-only rustc flags; default to rustup's + # Rust sanitizers need nightly-only rustc flags; default to rustup's # nightly unless the environment already selected a toolchain. - if(NOT VORTEX_SANITIZER STREQUAL "" AND VORTEX_RUSTUP_TOOLCHAIN STREQUAL "") + if(_sanitizer_rustflags AND VORTEX_RUSTUP_TOOLCHAIN STREQUAL "") set(VORTEX_RUSTUP_TOOLCHAIN nightly) endif() From 9c5636a30aa94acfd37251bca1c47009a035c37a Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 17:13:58 +0100 Subject: [PATCH 21/24] Tidy the C++ test CMake file and coverage script Drop the stray blank line and odd alignment in tests/CMakeLists.txt. Make gcov-report.sh run from its own directory, rely on the Debug default, use --coverage so the gcov runtime is linked explicitly, anchor the lcov exclude globs to full paths, and accept only `html` as the optional argument as the README documents. Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- lang/cpp/gcov-report.sh | 12 ++++++++---- lang/cpp/tests/CMakeLists.txt | 4 +--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lang/cpp/gcov-report.sh b/lang/cpp/gcov-report.sh index 5da22a8f742..e41afcb7007 100755 --- a/lang/cpp/gcov-report.sh +++ b/lang/cpp/gcov-report.sh @@ -3,19 +3,23 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors +# Builds the C++ tests with gcov instrumentation, runs them, and writes +# coverage.info next to this script; pass `html` to also render coverage/. set -eu +cd "$(dirname "$0")" + cmake -S . -B build -G Ninja \ - -DCMAKE_BUILD_TYPE=Debug \ -DVORTEX_BUILD_TESTING=ON \ - -DCMAKE_CXX_FLAGS='-fprofile-arcs -ftest-coverage' + -DCMAKE_CXX_FLAGS=--coverage cmake --build build --parallel ctest --test-dir build --output-on-failure +# lcov matches exclude globs against full source paths. geninfo build/CMakeFiles/vortex_cxx.dir/ \ build/tests/CMakeFiles/vortex_cxx_test.dir/ \ --rc geninfo_unexecuted_blocks=1 \ - --exclude /usr --exclude build/_deps --exclude tests \ + --exclude '/usr/*' --exclude '*/_deps/*' --exclude '*/tests/*' \ -j -b src -o coverage.info -if [ $# -gt 0 ]; then +if [ "${1:-}" = html ]; then genhtml coverage.info -o coverage fi diff --git a/lang/cpp/tests/CMakeLists.txt b/lang/cpp/tests/CMakeLists.txt index 8d6f1501850..211f0259bf6 100644 --- a/lang/cpp/tests/CMakeLists.txt +++ b/lang/cpp/tests/CMakeLists.txt @@ -1,12 +1,10 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright the Vortex contributors - FetchContent_Declare( magic_enum GIT_REPOSITORY https://github.com/Neargye/magic_enum.git - GIT_TAG v0.9.7 -) + GIT_TAG v0.9.7) FetchContent_MakeAvailable(magic_enum) file(GLOB TEST_FILES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") From 1f230e9722cc79e9837fba88823ef891021501c3 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 17:31:49 +0100 Subject: [PATCH 22/24] Treat CMake dependencies as system includes Signed-off-by: Alexander Droste --- lang/cpp/CMakeLists.txt | 7 +++++++ lang/cpp/tests/CMakeLists.txt | 1 + vortex-ffi/CMakeLists.txt | 6 ++++++ vortex-ffi/test/CMakeLists.txt | 1 + 4 files changed, 15 insertions(+) diff --git a/lang/cpp/CMakeLists.txt b/lang/cpp/CMakeLists.txt index 8310a3c304c..71192e3b754 100644 --- a/lang/cpp/CMakeLists.txt +++ b/lang/cpp/CMakeLists.txt @@ -72,8 +72,14 @@ add_library(Vortex::cpp_static ALIAS vortex_cxx) # Test and example dependencies are fetched at configure time and never installed. if(VORTEX_BUILD_TESTING OR VORTEX_BUILD_EXAMPLES) include(FetchContent) + + # Nanoarrow's developer warnings use -Werror and can reject newer compilers. + # Its headers are system headers when consumed by Vortex-owned targets. + set(NANOARROW_DEBUG_EXTRA_WARNINGS OFF CACHE BOOL + "Enable Nanoarrow's additional compiler warnings") FetchContent_Declare( Nanoarrow + SYSTEM GIT_REPOSITORY https://github.com/apache/arrow-nanoarrow GIT_TAG apache-arrow-nanoarrow-0.8.0) FetchContent_MakeAvailable(Nanoarrow) @@ -88,6 +94,7 @@ endif() if(VORTEX_BUILD_TESTING) FetchContent_Declare( Catch + SYSTEM GIT_REPOSITORY https://github.com/catchorg/Catch2.git GIT_TAG v3.8.1) FetchContent_MakeAvailable(Catch) diff --git a/lang/cpp/tests/CMakeLists.txt b/lang/cpp/tests/CMakeLists.txt index 211f0259bf6..02d8327e4df 100644 --- a/lang/cpp/tests/CMakeLists.txt +++ b/lang/cpp/tests/CMakeLists.txt @@ -3,6 +3,7 @@ FetchContent_Declare( magic_enum + SYSTEM GIT_REPOSITORY https://github.com/Neargye/magic_enum.git GIT_TAG v0.9.7) FetchContent_MakeAvailable(magic_enum) diff --git a/vortex-ffi/CMakeLists.txt b/vortex-ffi/CMakeLists.txt index dc4c56eb96b..bcdb8fc2897 100644 --- a/vortex-ffi/CMakeLists.txt +++ b/vortex-ffi/CMakeLists.txt @@ -58,8 +58,14 @@ add_library(Vortex::ffi_static ALIAS vortex_ffi_static) # Test and example dependencies are fetched at configure time and never installed. if(VORTEX_BUILD_TESTING OR VORTEX_BUILD_EXAMPLES) include(FetchContent) + + # Nanoarrow's developer warnings use -Werror and can reject newer compilers. + # Its headers are system headers when consumed by Vortex-owned targets. + set(NANOARROW_DEBUG_EXTRA_WARNINGS OFF CACHE BOOL + "Enable Nanoarrow's additional compiler warnings") FetchContent_Declare( Nanoarrow + SYSTEM GIT_REPOSITORY https://github.com/apache/arrow-nanoarrow GIT_TAG apache-arrow-nanoarrow-0.8.0) FetchContent_MakeAvailable(Nanoarrow) diff --git a/vortex-ffi/test/CMakeLists.txt b/vortex-ffi/test/CMakeLists.txt index dcde94fcffc..10499b59a10 100644 --- a/vortex-ffi/test/CMakeLists.txt +++ b/vortex-ffi/test/CMakeLists.txt @@ -3,6 +3,7 @@ FetchContent_Declare( Catch + SYSTEM GIT_REPOSITORY https://github.com/catchorg/Catch2.git GIT_TAG v3.8.1) FetchContent_MakeAvailable(Catch) From 39e8b0c770f3af2c412b45535286c6541b8a2c05 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 17:43:20 +0100 Subject: [PATCH 23/24] Do not require Ninja for C++ CI Signed-off-by: Alexander Droste --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a5ff470402..32be8206f0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -594,7 +594,7 @@ jobs: env: RUSTUP_TOOLCHAIN: ${{ env.NIGHTLY_TOOLCHAIN }} run: | - cmake -S lang/cpp -B build -G Ninja \ + cmake -S lang/cpp -B build \ -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ From 4f1a465afefeea3efa5451a9eee2fa2881696b53 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Fri, 4 Sep 2026 17:49:41 +0100 Subject: [PATCH 24/24] Do not require Ninja for C++ coverage Signed-off-by: Alexander Droste --- lang/cpp/gcov-report.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/cpp/gcov-report.sh b/lang/cpp/gcov-report.sh index e41afcb7007..c9f982284fb 100755 --- a/lang/cpp/gcov-report.sh +++ b/lang/cpp/gcov-report.sh @@ -8,7 +8,7 @@ set -eu cd "$(dirname "$0")" -cmake -S . -B build -G Ninja \ +cmake -S . -B build \ -DVORTEX_BUILD_TESTING=ON \ -DCMAKE_CXX_FLAGS=--coverage cmake --build build --parallel