# 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)