# CMake Rust Integration Plan This document outlines the design for `UseRust.cmake`, a CMake module for building Rust libraries without Cargo, using `rustc` and `cbindgen` directly. ## Overview Based on the `UseJava.cmake` pattern, we will create: 1. **FindRust.cmake** - Locate `rustc` and `cbindgen` executables 2. **UseRust.cmake** - Provide `add_rust_library()` and related functions ## Architecture ``` FindRust.cmake - Find rustc, cbindgen, set version variables UseRust.cmake - Main module with add_rust_library(), etc. UseRust/CrateRoot.cmake - Helper to generate crate root if needed ``` ## FindRust.cmake Locates the Rust toolchain and sets: ```cmake Rust_FOUND # TRUE if rustc found Rust_RUSTC_EXECUTABLE # Path to rustc Rust_CBINDGEN_EXECUTABLE # Path to cbindgen Rust_VERSION # Rust version string Rust_VERSION_MAJOR # Major version Rust_VERSION_MINOR # Minor version Rust_VERSION_PATCH # Patch version ``` ## UseRust.cmake Functions ### Primary Function: add_rust_library ```cmake add_rust_library(<target_name> STATIC | SHARED [SOURCES] <source1.rs> [<source2.rs>...] [CRATE_TYPE <lib|staticlib|cdylib|rlib>] [EDITION <2018|2021|2024>] [OUTPUT_NAME <name>] [OUTPUT_DIR <dir>] [GENERATE_HEADERS <header-target> [DESTINATION <dir>] [CONFIG <cbindgen.toml>]] [EXTERN <name>=<path> ...] [RUSTFLAGS <flags>...] ) ``` **Parameters:** - `STATIC | SHARED` - Library type (maps to `staticlib` or `cdylib`) - `SOURCES` - Rust source files (first file is crate root, or lib.rs) - `CRATE_TYPE` - Override the crate type passed to rustc - `EDITION` - Rust edition (default: 2021) - `OUTPUT_NAME` - Override output library name - `OUTPUT_DIR` - Output directory (default: `CMAKE_CURRENT_BINARY_DIR`) - `GENERATE_HEADERS` - Create an INTERFACE target for C headers via cbindgen - `EXTERN` - External crate dependencies as `name=path` pairs (maps to `--extern`) - `RUSTFLAGS` - Additional flags to pass to rustc ### Header Generation with cbindgen The `GENERATE_HEADERS` option integrates cbindgen to produce C headers: ```cmake add_rust_library(ssh_string STATIC SOURCES string.rs EDITION 2021 GENERATE_HEADERS ssh_string_headers DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/include CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/cbindgen.toml ) # Use generated headers in C code add_library(ssh ...) target_link_libraries(ssh PRIVATE ssh_string ssh_string_headers) ``` The header target is an INTERFACE library with: - `INTERFACE_INCLUDE_DIRECTORIES` set to the header output directory - Dependency on the Rust library target (ensures headers are generated first) ## Implementation Details ### Compilation Flow 1. **Collect sources** - Identify crate root (lib.rs or first .rs file) 2. **Run rustc** via `add_custom_command`: ``` rustc --crate-type=staticlib --edition=2021 -o <output> <crate_root> ``` 3. **Run cbindgen** via `add_custom_command` (if GENERATE_HEADERS): ``` cbindgen --config <config> --output <header> <crate_root> ``` 4. **Create targets**: - `add_library(<target> STATIC IMPORTED)` or custom target - `add_library(<header-target> INTERFACE)` for headers ### Target Properties Set Following UseJava.cmake pattern, set properties on the target: ```cmake RUST_LIBRARY_FILE # Path to the compiled library RUST_CRATE_ROOT # Path to the crate root source RUST_EDITION # Rust edition used RUST_HEADER_FILE # Path to generated header (if any) ``` ### Custom Commands Structure ```cmake # Build extern flags from EXTERN parameter set(_EXTERN_FLAGS) foreach(_extern IN LISTS _add_rust_library_EXTERN) list(APPEND _EXTERN_FLAGS --extern ${_extern}) endforeach() # Compile Rust to static library add_custom_command( OUTPUT ${_RUST_LIB_OUTPUT} COMMAND ${Rust_RUSTC_EXECUTABLE} --crate-type=${_CRATE_TYPE} --edition=${_EDITION} ${_RUST_OPT_FLAGS} ${_EXTERN_FLAGS} ${_RUSTFLAGS} -o ${_RUST_LIB_OUTPUT} ${_CRATE_ROOT} DEPENDS ${_RUST_SOURCES} ${_EXTERN_DEPS} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Compiling Rust library ${_TARGET_NAME}" VERBATIM ) # Generate C header with cbindgen add_custom_command( OUTPUT ${_HEADER_OUTPUT} COMMAND ${Rust_CBINDGEN_EXECUTABLE} --config ${_CBINDGEN_CONFIG} --output ${_HEADER_OUTPUT} ${_CRATE_ROOT} DEPENDS ${_RUST_SOURCES} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Generating C headers for ${_TARGET_NAME}" VERBATIM ) # Main library target add_custom_target(${_TARGET_NAME} ALL DEPENDS ${_RUST_LIB_OUTPUT}) # Header interface target add_library(${_HEADER_TARGET} INTERFACE) target_include_directories(${_HEADER_TARGET} INTERFACE ${_HEADER_DIR}) add_dependencies(${_HEADER_TARGET} ${_TARGET_NAME}) ``` ### Handling Multiple Source Files Unlike Cargo, rustc compiles a single crate root. For multiple files: 1. The first source or `lib.rs` is the crate root 2. Other files must be included via `mod` statements in the crate root 3. All sources are listed as dependencies for rebuild tracking ```cmake add_rust_library(mylib STATIC SOURCES lib.rs # Crate root - contains "mod helper;" helper.rs # Included via mod statement ) ``` ## cbindgen Integration ### Configuration File cbindgen uses a `cbindgen.toml` configuration file: ```toml language = "C" header = "/* Auto-generated by cbindgen. Do not edit. */" include_guard = "SSH_STRING_H" [export] prefix = "ssh_" ``` ### Workflow 1. If `CONFIG` is provided, use that cbindgen.toml 2. Otherwise, look for `cbindgen.toml` in source directory 3. If neither exists, use default cbindgen settings ### Header Dependencies The header generation depends on: - All Rust source files (for changes in function signatures) - The cbindgen config file (for output format changes) ## Example Usage ### Basic Static Library ```cmake find_package(Rust REQUIRED COMPONENTS cbindgen) include(UseRust) add_rust_library(ssh_string STATIC SOURCES src/string.rs EDITION 2021 GENERATE_HEADERS ssh_string_headers CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/cbindgen.toml ) # Link Rust library to C code target_link_libraries(ssh PRIVATE ssh_string ssh_string_headers ) ``` ### Shared Library ```cmake add_rust_library(ssh_crypto SHARED SOURCES src/crypto.rs EDITION 2021 GENERATE_HEADERS ssh_crypto_headers ) ``` ## Installation Support ```cmake install_rust_library(<target> DESTINATION <lib-dir> [HEADERS_DESTINATION <include-dir>] [COMPONENT <component>] ) ``` ## Variables for Customization ```cmake CMAKE_RUST_EDITION # Default edition (2021) CMAKE_RUST_FLAGS # Additional flags for all rustc invocations CMAKE_RUST_FLAGS_DEBUG # Flags for Debug builds CMAKE_RUST_FLAGS_RELEASE # Flags for Release builds (-O, -C opt-level=3) ``` ## Design Decisions ### External Crate Dependencies Support external crate dependencies via the `--extern` flag. Add `EXTERN` parameter: ```cmake add_rust_library(<target_name> ... [EXTERN <name>=<path> ...] ) ``` Example usage: ```cmake # First, build a dependency as rlib add_rust_library(my_utils STATIC CRATE_TYPE rlib SOURCES utils.rs ) # Then use it in another library add_rust_library(my_app STATIC SOURCES app.rs EXTERN my_utils=$<TARGET_PROPERTY:my_utils,RUST_LIBRARY_FILE> ) ``` This maps to rustc flags: ``` rustc --extern my_utils=/path/to/libmy_utils.rlib ... ``` ### Build Type Mapping Map `CMAKE_BUILD_TYPE` to rustc optimization and debug flags: | CMAKE_BUILD_TYPE | rustc flags | |------------------|--------------------------------| | Debug | `-g -C opt-level=0` | | Release | `-C opt-level=3 -C lto` | | RelWithDebInfo | `-g -C opt-level=2` | | MinSizeRel | `-C opt-level=s -C lto` | Implementation: ```cmake if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(_RUST_OPT_FLAGS -g -C opt-level=0) elseif(CMAKE_BUILD_TYPE STREQUAL "Release") set(_RUST_OPT_FLAGS -C opt-level=3 -C lto) elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") set(_RUST_OPT_FLAGS -g -C opt-level=2) elseif(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel") set(_RUST_OPT_FLAGS -C opt-level=s -C lto) else() set(_RUST_OPT_FLAGS -g -C opt-level=0) # Default to debug endif() ``` ### Platform-Specific Output Names Generate correct library names per platform: | Platform | Static Library | Shared Library | |----------|-----------------|-------------------| | Linux | `lib<name>.a` | `lib<name>.so` | | macOS | `lib<name>.a` | `lib<name>.dylib` | | Windows | `<name>.lib` | `<name>.dll` | Implementation: ```cmake if(WIN32) set(_STATIC_PREFIX "") set(_STATIC_SUFFIX ".lib") set(_SHARED_PREFIX "") set(_SHARED_SUFFIX ".dll") elseif(APPLE) set(_STATIC_PREFIX "lib") set(_STATIC_SUFFIX ".a") set(_SHARED_PREFIX "lib") set(_SHARED_SUFFIX ".dylib") else() # Linux/Unix set(_STATIC_PREFIX "lib") set(_STATIC_SUFFIX ".a") set(_SHARED_PREFIX "lib") set(_SHARED_SUFFIX ".so") endif() ``` ### cbindgen Availability cbindgen is **optional**. FindRust.cmake behavior: - `find_package(Rust REQUIRED)` - Only requires rustc - `find_package(Rust REQUIRED COMPONENTS cbindgen)` - Requires both If `GENERATE_HEADERS` is used without cbindgen available, emit a clear error: ```cmake if(_add_rust_library_GENERATE_HEADERS AND NOT Rust_CBINDGEN_EXECUTABLE) message(FATAL_ERROR "add_rust_library: GENERATE_HEADERS requires cbindgen. " "Use find_package(Rust REQUIRED COMPONENTS cbindgen)") endif() ``` ## Implementation Phases ### Phase 1: Core Functionality - [ ] FindRust.cmake - locate rustc and cbindgen - [ ] Basic add_rust_library() for STATIC libraries - [ ] Source file dependency tracking - [ ] Platform-specific output names ### Phase 2: Build Types and Shared Libraries - [ ] CMAKE_BUILD_TYPE to rustc flags mapping ### Phase 3: Header Generation - [ ] cbindgen integration with GENERATE_HEADERS - [ ] INTERFACE target for include directories - [ ] Config file support ### Phase 4: Shared libraris (optional) - [ ] SHARED library support (cdylib) - [ ] EXTERN parameter for crate dependencies ### Phase 5: Installation and Polish (optional) - [ ] install_rust_library() - [ ] Cross-compilation support - [ ] Target export/import support ## Future: Cargo Integration For projects that require external crate dependencies from crates.io, we may need Cargo integration. This section outlines a potential approach. ### Motivation While `--extern` works for local crate dependencies, fetching and building crates from crates.io requires dependency resolution that Cargo handles. ### Approach: Hybrid Model Use Cargo for dependency fetching and building, but integrate with CMake: ```cmake add_cargo_library(<target_name> MANIFEST <Cargo.toml> [CRATE_TYPE staticlib|cdylib] [GENERATE_HEADERS <header-target>] [PROFILE debug|release] ) ``` ### Implementation Strategy 1. **Cargo as Build Tool**: Let Cargo handle the full build including dependencies 2. **CMake Integration**: Import the resulting library as a CMake target 3. **Header Generation**: Still use cbindgen separately (or via build.rs) ```cmake # Example: Build a Cargo project and import into CMake add_cargo_library(ssh_crypto MANIFEST ${CMAKE_CURRENT_SOURCE_DIR}/rust/Cargo.toml CRATE_TYPE staticlib GENERATE_HEADERS ssh_crypto_headers ) target_link_libraries(ssh PRIVATE ssh_crypto ssh_crypto_headers) ``` ### Cargo Build Integration ```cmake add_custom_command( OUTPUT ${_CARGO_OUTPUT_DIR}/${_LIB_NAME} COMMAND ${Cargo_EXECUTABLE} build --manifest-path ${_MANIFEST} --target-dir ${_CARGO_OUTPUT_DIR} $<$<CONFIG:Release>:--release> DEPENDS ${_RUST_SOURCES} ${_MANIFEST} Cargo.lock COMMENT "Building Cargo project ${_TARGET_NAME}" VERBATIM ) ``` ### FindCargo.cmake ```cmake Cargo_FOUND # TRUE if cargo found Cargo_EXECUTABLE # Path to cargo Cargo_VERSION # Cargo version string ``` ### Design Decisions 1. **Build Directory**: Use CMake binary dir as Cargo target-dir ```cmake COMMAND ${Cargo_EXECUTABLE} build --manifest-path ${_MANIFEST} --target-dir ${CMAKE_CURRENT_BINARY_DIR}/cargo ``` This keeps all build artifacts within CMake's build tree, enabling: - Clean builds with `rm -rf build/` - Out-of-source builds work naturally - No pollution of source directory 2. **Dependency Caching**: Cargo caches compiled dependencies in target-dir - Incremental builds remain fast - Cache is per-build-directory (Debug/Release can have separate caches) 3. **Offline Builds**: For reproducible builds, support `cargo vendor` ```cmake add_cargo_library(mylib MANIFEST Cargo.toml VENDORED_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/vendor ) ``` This sets `CARGO_HOME` or uses `.cargo/config.toml` to use vendored sources. 4. **Lock File**: Cargo.lock should be committed for reproducible builds ## References - [UseJava.cmake](https://github.com/Kitware/CMake/blob/master/Modules/UseJava.cmake) - Blueprint for this module - [rustc documentation](https://doc.rust-lang.org/rustc/) - [cbindgen documentation](https://github.com/mozilla/cbindgen)