About Corrosion
Corrosion, formerly known as cmake-cargo, is a tool for integrating Rust into an existing CMake project. Corrosion is capable of automatically importing executables, static libraries, and dynamic libraries from a Rust package or workspace as CMake targets.
The imported static and dynamic library types can be linked into C/C++ CMake targets using the usual
CMake functions such as target_link_libraries()
.
For rust executables and dynamic libraries corrosion provides a corrosion_link_libraries
helper function to conveniently add the necessary flags to link C/C++ libraries into
the rust target.
Requirements
- Corrosion supports CMake 3.15 and newer with the v0.5 release. If you are using the v0.5 release, please view the documentation here
- The master branch of Corrosion currently requires CMake 3.22 or newer.
Quick Start
You can add corrosion to your project via the FetchContent
CMake module or one of the other methods
described in the Setup chapter.
Afterwards you can import Rust targets defined in a Cargo.toml
manifest file by using
corrosion_import_crate
. This will add CMake targets with names matching the crate names defined
in the Cargo.toml manifest. These targets can then subsequently be used, e.g. to link the imported
target into a regular C/C++ target.
The example below shows how to add Corrosion to your project via FetchContent
and how to import a rust library and link it into a regular C/C++ CMake target.
include(FetchContent)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
GIT_TAG v0.5 # Optionally specify a commit hash, version tag or branch here
)
# Set any global configuration variables such as `Rust_TOOLCHAIN` before this line!
FetchContent_MakeAvailable(Corrosion)
# Import targets defined in a package or workspace manifest `Cargo.toml` file
corrosion_import_crate(MANIFEST_PATH rust-lib/Cargo.toml)
add_executable(your_cool_cpp_bin main.cpp)
# In this example the the `Cargo.toml` file passed to `corrosion_import_crate` is assumed to have
# defined a static (`staticlib`) or shared (`cdylib`) rust library with the name "rust-lib".
# A target with the same name is now available in CMake and you can use it to link the rust library into
# your C/C++ CMake target(s).
target_link_libraries(your_cool_cpp_bin PUBLIC rust-lib)
Please see the Usage chapter for a complete discussion of possible configuration options.
Adding Corrosion to your project
There are two fundamental installation methods that are supported by Corrosion - installation as a CMake package or using it as a subdirectory in an existing CMake project. For CMake versions below 3.19 Corrosion strongly recommends installing the package, either via a package manager or manually using CMake's installation facilities. If you have CMake 3.19 or newer, we recommend to use either the FetchContent or the Subdirectory method to integrate Corrosion.
FetchContent
If you are using CMake >= 3.19 or installation is difficult or not feasible in your environment, you can use the FetchContent module to include Corrosion. This will download Corrosion and use it as if it were a subdirectory at configure time.
In your CMakeLists.txt:
include(FetchContent)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
GIT_TAG v0.5 # Optionally specify a commit hash, version tag or branch here
)
# Set any global configuration variables such as `Rust_TOOLCHAIN` before this line!
FetchContent_MakeAvailable(Corrosion)
Subdirectory
Corrosion can also be used directly as a subdirectory. This solution may work well for small
projects, but it's discouraged for large projects with many dependencies, especially those which may
themselves use Corrosion. Either copy the Corrosion library into your source tree, being sure to
preserve the LICENSE
file, or add this repository as a git submodule:
git submodule add https://github.com/corrosion-rs/corrosion.git
From there, using Corrosion is easy. In your CMakeLists.txt:
add_subdirectory(path/to/corrosion)
Installation
Installation will pre-build all of Corrosion's native tooling (required only for CMake versions below 3.19) and install it together with Corrosions CMake files into a standard location. On CMake >= 3.19 installing Corrosion does not offer any speed advantages, unless the native tooling option is explicitly enabled.
Install from source
First, download and install Corrosion:
git clone https://github.com/corrosion-rs/corrosion.git
# Optionally, specify -DCMAKE_INSTALL_PREFIX=<target-install-path> to specify a
# custom installation directory
cmake -Scorrosion -Bbuild -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
# This next step may require sudo or admin privileges if you're installing to a system location,
# which is the default.
cmake --install build --config Release
You'll want to ensure that the install directory is available in your PATH
or CMAKE_PREFIX_PATH
environment variable. This is likely to already be the case by default on a Unix system, but on
Windows it will install to C:\Program Files (x86)\Corrosion
by default, which will not be in your
PATH
or CMAKE_PREFIX_PATH
by default.
Once Corrosion is installed, and you've ensured the package is available in your PATH
, you
can use it from your own project like any other package from your CMakeLists.txt:
find_package(Corrosion REQUIRED)
Package Manager
Homebrew (unofficial)
Corrosion is available via Homebrew and can be installed via
brew install corrosion
Please note that this package is community maintained. Please also keep in mind that Corrosion follows
semantic versioning and minor version bumps (i.e. 0.3
-> 0.4
) may contain breaking changes, while
Corrosion is still pre 1.0
.
Please read the release notes when upgrading Corrosion.
Usage
Automatically import crate targets with corrosion_import_crate
In order to integrate a Rust crate into CMake, you first need to import Rust crates from
a package or workspace. Corrosion provides corrosion_import_crate()
to automatically import
crates defined in a Cargo.toml Manifest file:
corrosion_import_crate(
MANIFEST_PATH <path/to/cargo.toml>
[ALL_FEATURES]
[NO_DEFAULT_FEATURES]
[NO_STD]
[NO_LINKER_OVERRIDE]
[LOCKED]
[FROZEN]
[PROFILE <cargo-profile>]
[IMPORTED_CRATES <variable-name>]
[CRATE_TYPES <crate_type1> ... <crate_typeN>]
[OVERRIDE_CRATE_TYPE <crate_name>=<crate_type1,crate_type2,...> ...]
[CRATES <crate1> ... <crateN>]
[FEATURES <feature1> ... <featureN>]
[FLAGS <flag1> ... <flagN>]
)
- MANIFEST_PATH: Path to a Cargo.toml Manifest file.
- ALL_FEATURES: Equivalent to --all-features passed to cargo build
- NO_DEFAULT_FEATURES: Equivalent to --no-default-features passed to cargo build
- NO_STD: Disable linking of standard libraries (required for no_std crates).
- NO_LINKER_OVERRIDE: Will let Rust/Cargo determine which linker to use instead of corrosion (when linking is invoked by Rust)
- LOCKED: Pass
--locked
to cargo build and cargo metadata. - FROZEN: Pass
--frozen
to cargo build and cargo metadata. - PROFILE: Specify cargo build profile (
dev
/release
or a custom profile;bench
andtest
are not supported) - IMPORTED_CRATES: Save the list of imported crates into the variable with the provided name in the current scope.
- CRATE_TYPES: Only import the specified crate types. Valid values:
staticlib
,cdylib
,bin
. - OVERRIDE_CRATE_TYPE: Override the crate-types of a cargo crate with the given comma-separated values.
Internally uses the
rustc
flag--crate-type
to override the crate-type. Valid values for the crate types are the library typesstaticlib
andcdylib
. - CRATES: Only import the specified crates from a workspace. Values: Crate names.
- FEATURES: Enable the specified features. Equivalent to --features passed to
cargo build
. - FLAGS: Arbitrary flags to
cargo build
.
Corrosion will use cargo metadata
to add a cmake target for each crate defined in the Manifest file
and add the necessary rules to build the targets.
For Rust executables an IMPORTED
executable target is created with the same name as defined in the [[bin]]
section of the Manifest corresponding to this target.
If no such name was defined the target name defaults to the Rust package name.
For Rust library targets an INTERFACE
library target is created with the same name as defined in the [lib]
section of the Manifest. This INTERFACE
library links an internal corrosion target, which is either a
SHARED
or STATIC
IMPORTED
library, depending on the Rust crate type (cdylib
vs staticlib
).
The created library targets can be linked into other CMake targets by simply using target_link_libraries.
Corrosion will by default copy the produced Rust artifacts into ${CMAKE_CURRENT_BINARY_DIR}
. The target location
can be changed by setting the CMake OUTPUT_DIRECTORY
target properties on the imported Rust targets.
See the OUTPUT_DIRECTORY section for more details.
Many of the options available for corrosion_import_crate
can also be individually set per
target, see Per Target options for details.
Experimental: Install crate and headers with corrosion_install
The default CMake install commands do not work correctly with the targets exported from corrosion_import_crate()
.
Corrosion provides corrosion_install
to automatically install relevant files:
** EXPERIMENTAL **: This function is currently still considered experimental and is not officially released yet. Feedback and Suggestions are welcome.
corrosion_install(TARGETS <target1> ... <targetN> [EXPORT <export-name>]
[[ARCHIVE|LIBRARY|RUNTIME|PUBLIC_HEADER]
[DESTINATION <dir>]
[PERMISSIONS <permissions...>]
[CONFIGURATIONS [Debug|Release|<other-configuration>]]
] [...])
- TARGETS: Target or targets to install.
- EXPORT: Creates an export that can be installed with
install(EXPORT)
.must be globally unique. Also creates a file at ${CMAKE_BINARY_DIR}/corrosion/ Corrosion.cmake that must be included in the installed config file. - ARCHIVE/LIBRARY/RUNTIME/PUBLIC_HEADER: Designates that the following settings only apply to that specific type of object.
- DESTINATION: The subdirectory within the CMAKE_INSTALL_PREFIX that a specific object should be placed. Defaults to values from GNUInstallDirs.
- PERMISSIONS: The permissions of files copied into the install prefix.
Any PUBLIC
or INTERFACE
file sets will be installed.
The example below shows how to import a rust library and make it available for install through CMake.
include(FetchContent)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
GIT_TAG v0.5 # Optionally specify a commit hash, version tag or branch here
)
# Set any global configuration variables such as `Rust_TOOLCHAIN` before this line!
FetchContent_MakeAvailable(Corrosion)
# Import targets defined in a package or workspace manifest `Cargo.toml` file
corrosion_import_crate(MANIFEST_PATH rust-lib/Cargo.toml)
# Add a manually written header file which will be exported
# Requires CMake >=3.23
target_sources(rust-lib INTERFACE
FILE_SET HEADERS
BASE_DIRS include
FILES
include/rust-lib/rust-lib.h
)
# OR for CMake <= 3.23
target_include_directories(is_odd INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_sources(is_odd
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/rust-lib/rust-lib.h>
$<INSTALL_INTERFACE:include/rust-lib/rust-lib.h>
)
# Rust libraries must be installed using `corrosion_install`.
corrosion_install(TARGETS rust-lib EXPORT RustLibTargets)
# Installs the main target
install(
EXPORT RustLibTargets
NAMESPACE RustLib::
DESTINATION lib/cmake/RustLib
)
# Necessary for packaging helper commands
include(CMakePackageConfigHelpers)
# Create a file for checking version compatibility
# Optional
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/RustLibConfigVersion.cmake"
VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}"
COMPATIBILITY AnyNewerVersion
)
# Configures the main config file that cmake loads
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/RustLibConfig.cmake"
INSTALL_DESTINATION lib/cmake/RustLib
NO_SET_AND_CHECK_MACRO
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# Config.cmake.in contains
# @PACKAGE_INIT@
#
# include(${CMAKE_CURRENT_LIST_DIR}/RustLibTargetsCorrosion.cmake)
# include(${CMAKE_CURRENT_LIST_DIR}/RustLibTargets.cmake)
# Install all generated files
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/RustLibConfigVersion.cmake
${CMAKE_CURRENT_BINARY_DIR}/RustLibConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/corrosion/RustLibTargetsCorrosion.cmake
DESTINATION lib/cmake/RustLib
)
Per Target options
Some configuration options can be specified individually for each target. You can set them via the
corrosion_set_xxx()
functions specified below:
corrosion_set_env_vars(<target_name> <key1=value1> [... <keyN=valueN>])
: Define environment variables that should be set during the invocation ofcargo build
for the specified target. Please note that the environment variable will only be set for direct builds of the target via cmake, and not for any build where cargo built the crate in question as a dependency for another target. The environment variables may contain generator expressions.corrosion_add_target_rustflags(<target_name> <rustflag> [... <rustflagN>])
: When building the target, theRUSTFLAGS
environment variable will contain the flags added via this function. Please note that any dependencies (built by cargo) will also see these flags. See also:corrosion_add_target_local_rustflags
.corrosion_add_target_local_rustflags(target_name rustc_flag [more_flags ...])
: Support setting rustflags for only the main target (crate) and none of its dependencies. This is useful in cases where you only need rustflags on the main-crate, but need to set different flags for different targets. Without "local" Rustflags this would require rebuilds of the dependencies when switching targets.corrosion_set_hostbuild(<target_name>)
: The target should be compiled for the Host target and ignore any cross-compile configuration.corrosion_set_features(<target_name> [ALL_FEATURES <Bool>] [NO_DEFAULT_FEATURES] [FEATURES <feature1> ... ])
: For a given target, enable specific features viaFEATURES
, toggleALL_FEATURES
on or off or disable all features viaNO_DEFAULT_FEATURES
. For more information on features, please see also the cargo reference.corrosion_set_cargo_flags(<target_name> <flag1> ...])
: For a given target, add options and flags at the end ofcargo build
invocation. This will be appended after any arguments passed through theFLAGS
during the crate import.corrosion_set_linker(target_name linker)
: Uselinker
to link the target. Please note that this only has an effect for targets where the final linker invocation is done by cargo, i.e. targets where foreign code is linked into rust code and not the other way around. Please also note that if you are cross-compiling and specify a linker such asclang
, you are responsible for also adding a rustflag which adds the necessary--target=
argument for the linker.
Global Corrosion Options
Selecting the Rust toolchain and target triple
The following variables are evaluated automatically in most cases. In typical cases you
shouldn't need to alter any of these. If you do want to specify them manually, make sure to set
them before find_package(Corrosion REQUIRED)
.
Rust_TOOLCHAIN:STRING
- Specify a named rustup toolchain to use. Changes to this variable resets all other options. Default: If the first-foundrustc
is arustup
proxy, then the default rustup toolchain (seerustup show
) is used. Otherwise, the variable is unset by default.Rust_ROOT:STRING
- CMake provided. Path to a Rust toolchain to use. This is an alternative if you want to select a specific Rust toolchain, but it's not managed by rustup. Default: NothingRust_COMPILER:STRING
- Path torustc
, which should be used for compiling or for toolchain detection (if it is arustup
proxy). Default: Therustc
in the first-found toolchain, either fromrustup
, or from a toolchain available in the user'sPATH
.Rust_RESOLVE_RUSTUP_TOOLCHAINS:BOOL
- If the foundrustc
is arustup
proxy, resolve a concrete path to a specific toolchain managed byrustup
, according to therustup
toolchain selection rules and other options detailed here. If this option is turned off, the foundrustc
will be used as-is to compile, even if it is arustup
proxy, which might increase compilation time. Default:ON
if the foundrustc
is a rustup proxy or arustup
managed toolchain was requested,OFF
otherwise. ForcedOFF
ifrustup
was not found.Rust_CARGO:STRING
- Path tocargo
. Default: thecargo
installed next to${Rust_COMPILER}
.Rust_CARGO_TARGET:STRING
- The default target triple to build for. Alter for cross-compiling. Default: On Visual Studio Generator, the matching triple forCMAKE_VS_PLATFORM_NAME
. Otherwise, the default target triple reported by${Rust_COMPILER} --version --verbose
.
Enable Convenience Options
The following options are off by default, but may increase convenience:
Rust_RUSTUP_INSTALL_MISSING_TARGET:BOOL
: Automatically install a missing target viarustup
instead of failing.
Developer/Maintainer Options
These options are not used in the course of normal Corrosion usage, but are used to configure how Corrosion is built and installed. Only applies to Corrosion builds and subdirectory uses.
CORROSION_BUILD_TESTS:BOOL
- Build the Corrosion tests. Default:Off
if Corrosion is a subdirectory,ON
if it is the top-level project
Information provided by Corrosion
For your convenience, Corrosion sets a number of variables which contain information about the version of the rust
toolchain. You can use the CMake version comparison operators
(e.g. VERSION_GREATER_EQUAL
) on the main
variable (e.g. if(Rust_VERSION VERSION_GREATER_EQUAL "1.57.0")
), or you can inspect the major, minor and patch
versions individually.
Rust_VERSION<_MAJOR|_MINOR|_PATCH>
- The version of rustc.Rust_CARGO_VERSION<_MAJOR|_MINOR|_PATCH>
- The cargo version.Rust_LLVM_VERSION<_MAJOR|_MINOR|_PATCH>
- The LLVM version used by rustc.Rust_IS_NIGHTLY
- 1 if a nightly toolchain is used, otherwise 0. Useful for selecting an unstable feature for a crate, that is only available on nightly toolchains.- Cache variables containing information based on the target triple for the selected target
as well as the default host target:
Rust_CARGO_TARGET_ARCH
,Rust_CARGO_HOST_ARCH
: e.g.x86_64
oraarch64
Rust_CARGO_TARGET_VENDOR
,Rust_CARGO_HOST_VENDOR
: e.g.apple
,pc
,unknown
etc.Rust_CARGO_TARGET_OS
,Rust_CARGO_HOST_OS
: e.g.darwin
,linux
,windows
,none
Rust_CARGO_TARGET_ENV
,Rust_CARGO_HOST_ENV
: e.g.gnu
,musl
Selecting a custom cargo profile
Rust 1.57 stabilized the support for custom
profiles. If you are using a sufficiently new rust toolchain,
you may select a custom profile by adding the optional argument PROFILE <profile_name>
to
corrosion_import_crate()
. If you do not specify a profile, or you use an older toolchain, corrosion will select
the standard dev
profile if the CMake config is either Debug
or unspecified. In all other cases the release
profile is chosen for cargo.
Importing C-Style Libraries Written in Rust
Corrosion makes it completely trivial to import a crate into an existing CMake project. Consider a project called rust2cpp with the following file structure:
rust2cpp/
rust/
src/
lib.rs
Cargo.lock
Cargo.toml
CMakeLists.txt
main.cpp
This project defines a simple Rust lib crate, like so, in rust2cpp/rust/Cargo.toml
:
[package]
name = "rust-lib"
version = "0.1.0"
authors = ["Andrew Gaspar <andrew.gaspar@outlook.com>"]
license = "MIT"
edition = "2018"
[dependencies]
[lib]
crate-type=["staticlib"]
In addition to "staticlib"
, you can also use "cdylib"
. In fact, you can define both with a
single crate and switch between which is used using the standard
BUILD_SHARED_LIBS
variable.
This crate defines a simple crate called rust-lib
. Importing this crate into your
CMakeLists.txt is trivial:
# Note: you must have already included Corrosion for `corrosion_import_crate` to be available. See # the `Installation` section above.
corrosion_import_crate(MANIFEST_PATH rust/Cargo.toml)
Now that you've imported the crate into CMake, all of the executables, static libraries, and dynamic libraries defined in the Rust can be directly referenced. So, merely define your C++ executable as normal in CMake and add your crate's library using target_link_libraries:
add_executable(cpp-exe main.cpp)
target_link_libraries(cpp-exe PUBLIC rust-lib)
That's it! You're now linking your Rust library to your C++ library.
Generate Bindings to Rust Library Automatically
Currently, you must manually declare bindings in your C or C++ program to the exported routines and types in your Rust project. You can see boths sides of this in the Rust code and in the C++ code.
Integration with cbindgen is planned for the future.
Importing Libraries Written in C and C++ Into Rust
The rust targets can be imported with corrosion_import_crate()
into CMake.
For targets where the linker should be invoked by Rust corrosion provides
corrosion_link_libraries()
to link your C/C++ libraries with the Rust target.
For additional linker flags you may use corrosion_add_target_local_rustflags()
and pass linker arguments via the -Clink-args
flag to rustc. These flags will
only be passed to the final rustc invocation and not affect any rust dependencies.
C bindings can be generated via bindgen.
Corrosion does not offer any direct integration yet, but you can either generate the
bindings in the build-script of your crate, or generate the bindings as a CMake build step
(e.g. a custom target) and add a dependency from cargo-prebuild_<rust_target>
to your
custom target for generating the bindings.
Example:
# Import your Rust targets
corrosion_import_crate(MANIFEST_PATH rust/Cargo.toml)
# Link C/C++ libraries with your Rust target
corrosion_link_libraries(target_name c_library)
# Optionally explicitly define which linker to use.
corrosion_set_linker(target_name your_custom_linker)
# Optionally set linker arguments
corrosion_add_target_local_rustflags(target_name "-Clink-args=<linker arguments>")
# Optionally tell CMake that the rust crate depends on another target (e.g. a code generator)
add_dependencies(cargo-prebuild_<target_name> custom_bindings_target)
Cross Compiling
Corrosion attempts to support cross-compiling as generally as possible, though not all configurations are tested. Cross-compiling is explicitly supported in the following scenarios.
In all cases, you will need to install the standard library for the Rust target triple. When using Rustup, you can use it to install the target standard library:
rustup target add <target-rust-triple>
If the target triple is automatically derived, Corrosion will print the target during configuration. For example:
-- Rust Target: aarch64-linux-android
Windows-to-Windows
Corrosion supports cross-compiling between arbitrary Windows architectures using the Visual Studio
Generator. For example, to cross-compile for ARM64 from any platform, simply set the -A
architecture flag:
cmake -S. -Bbuild-arm64 -A ARM64
cmake --build build-arm64
Please note that for projects containing a build-script at least Rust 1.54 is required due to a bug in previous cargo versions, which causes the build-script to incorrectly be built for the target platform.
Linux-to-Linux
In order to cross-compile on Linux, you will need to install a cross-compiler. For example, on
Ubuntu, to cross compile for 64-bit Little-Endian PowerPC Little-Endian, install
g++-powerpc64le-linux-gnu
from apt-get:
sudo apt install g++-powerpc64le-linux-gnu
Currently, Corrosion does not automatically determine the target triple while cross-compiling on
Linux, so you'll need to specify a matching Rust_CARGO_TARGET
.
cmake -S. -Bbuild-ppc64le -DRust_CARGO_TARGET=powerpc64le-unknown-linux-gnu -DCMAKE_CXX_COMPILER=powerpc64le-linux-gnu-g++
cmake --build build-ppc64le
Android
Cross-compiling for Android is supported on all platforms with the Makefile and Ninja generators, and the Rust target triple will automatically be selected. The CMake cross-compiling instructions for Android apply here. For example, to build for ARM64:
cmake -S. -Bbuild-android-arm64 -GNinja -DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_ANDROID_NDK=/path/to/android-ndk-rxxd -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a
Important note: The Android SDK ships with CMake 3.10 at newest, which Android Studio will prefer over any CMake you've installed locally. CMake 3.10 is insufficient for using Corrosion, which requires a minimum of CMake 3.22. If you're using Android Studio to build your project, follow the instructions in the Android Studio documentation for using a specific version of CMake.
CMake OUTPUT_DIRECTORY
target properties and IMPORTED_LOCATION
Corrosion respects the following OUTPUT_DIRECTORY
target properties:
If the target property is set (e.g. by defining the CMAKE_XYZ_OUTPUT_DIRECTORY
variable before calling
corrosion_import_crate()
), corrosion will copy the built rust artifacts to the location defined in the
target property.
Due to limitations in CMake these target properties are evaluated in a deferred manner, to
support the user setting the target properties after the call to corrosion_import_crate()
.
This has the side effect that the IMPORTED_LOCATION
property will be set late, and users should not
use get_property
to read IMPORTED_LOCATION
at configure time. Instead, generator expressions
should be used to get the location of the target artifact.
If IMPORTED_LOCATION
is needed at configure time users may use cmake_language(DEFER CALL ...)
to defer
evaluation to after the IMPORTED_LOCATION
property is set.
What does corrosion do?
The specifics of what corrosion does should be regarded as an implementation detail and not relied on when writing user code. However, a basic understanding of what corrosion does may be helpful when investigating issues.
FindRust
Corrosion maintains a CMake module FindRust
which is executed when Corrosion is loaded, i.e. at the time
of find_package(corrosion)
, FetchContent_MakeAvailable(corrosion)
or add_subdirectory(corrosion)
depending
on the method used to include Corrosion.
FindRust
will search for installed rust toolchains, respecting the options prefixed with Rust_
documented in
the Usage chapter.
It will select one Rust toolchain to be used for the compilation of Rust code. Toolchains managed by rustup
will be resolved and corrosion will always select a specific toolchain, not a rustup
proxy.
Importing Rust crates
Corrosion's main function is corrosion_import_crate
, which internally will call cargo metadata
to provide
structured information based on the Cargo.toml
manifest.
Corrosion will then iterate over all workspace and/or package members and find all rust crates that are either
a static (staticlib
) or shared (cdylib
) library or a bin
target and create CMake targets matching the
crate name. Additionally, a build target is created for each imported target, containing the required build
command to create the imported artifact. This build command can be influenced by various arguments to
corrosion_import_crate
as well as corrosion specific target properties which are documented int the
Usage chapter.
Corrosion adds the necessary dependencies and also copies the target artifacts out of the cargo build tree
to standard CMake locations, even respecting OUTPUT_DIRECTORY
target properties if set.
Linking
Depending on the type of the crate the linker will either be invoked by CMake or by rustc
.
Rust staticlib
s are linked into C/C++ code via target_link_libraries()
and the linker is
invoked by CMake.
For rust cdylib
s and bin
s, the linker is invoked via rustc
and CMake just gets the final artifact.
CMake invokes the linker
When CMake invokes the linker, everything is as usual. CMake will call the linker with
the compiler as the linker driver and users can just use the regular CMake functions to
modify linking behaviour. corrosion_set_linker()
has no effect.
As a convenience, corrosion_link_libraries()
will forward its arguments to target_link_libraries()
.
Rustc invokes the linker
Rust cdylib
s and bin
s are linked via rustc
. Corrosion provides several helper functions
to influence the linker invocation for such targets.
corrosion_link_libraries()
is a limited version of target_link_libraries()
for rust cdylib
or bin
targets.
Under the hood this function passes -l
and -L
flags to the linker invocation and
ensures the linked libraries are built first.
Much of the advanced functionality available in target_link_libraries()
is not implemented yet,
but pull-requests are welcome! In the meantime, users may want to use
corrosion_add_target_local_rustflags()
to pass customized linking flags.
corrosion_set_linker()
can be used to specify a custom linker, in case the default one
chosen by corrosion is not what you want.
Corrosion currently instructs rustc
to use the C/C++ compiler as the linker driver.
This is done because:
- For C++ code we must link with
libstdc++
orlibc++
(depending on the compiler), so we must either specify the library on the link line or use ac++
compiler as the linker driver. Rustc
s default linker selection currently is not so great. For a number of platformsrustc
will fallback tocc
as the linker driver. When cross-compiling, this leads to linking failures, since the linker driver is for the host architecture. Corrosion avoids this by specifying the C/C++ compiler as the linker driver.
In some cases, especially in older rust versions (pre 1.68), the linker flavor detection
of rustc
is also not correct, so when setting a custom linker you may want to pass the
-C linker-flavor
rustflag via corrosion_add_target_local_rustflags()
.
FFI bindings
For interaction between Rust and other languages there need to be some FFI bindings of some sort. For simple cases manually defining the interfaces may be sufficient, but in many cases users wish to use tools like bindgen, cbindgen, cxx or autocxx to automate the generating of bindings.
In principle there are two different ways to generate the bindings:
- use a
build.rs
script to generate the bindings when cargo is invoked, using library versions of the tools to generate the bindings. - use the cli versions of the tools and setup custom CMake targets/commands to generate the bindings. This approach should be preferred if the bindings are needed by the C/C++ side.
Corrosion currently provides 2 experimental functions to integrate cbindgen and cxx into the build process. They are not 100% production ready yet, but should work well as a template on how to integrate generating bindings into your build process.
Todo: expand this documentation and link to other resources.
Integrating Automatically Generated FFI Bindings
There are a number of tools to automatically generate bindings between Rust and different foreign languages.
bindgen
bindgen is a tool to automatically generate Rust bindings from C headers. As such, integrating bindgen via a build-script works well and their doesn't seem to be a need to create CMake rules for generating the bindings.
cbindgen integration
⚠️⚠️⚠️ EXPERIMENTAL ⚠️⚠️⚠️
cbindgen is a tool that generates C/C++ headers from Rust code. When compiling C/C++
code that #include
s such generated headers the buildsystem must be aware of the dependencies.
Generating the headers via a build-script is possible, but Corrosion offers no guidance here.
Instead, Corrosion offers an experimental function to add CMake rules using cbindgen to generate the headers. This is not available on a stable released version yet, and the details are subject to change.
corrosion_cbindgen(
TARGET <imported_target_name>
HEADER_NAME <output_header_name>
[CARGO_PACKAGE <cargo_package_name>]
[MANIFEST_DIRECTORY <package_manifest_directory>]
[CBINDGEN_VERSION <version>]
[FLAGS <flag1> ... <flagN>]
)
A helper function which uses cbindgen to generate C/C++ bindings for a Rust crate.
If cbindgen
is not in PATH
the helper function will automatically try to download
cbindgen
and place the built binary into CMAKE_BINARY_DIR
. The binary is shared
between multiple invocations of this function.
-
TARGET: The name of an imported Rust library target, for which bindings should be generated. If the target was not previously imported by Corrosion, because the crate only produces an
rlib
, you must additionally specifyMANIFEST_DIRECTORY
. -
MANIFEST_DIRECTORY: Directory of the package defining the library crate bindings should be generated for. If you want to avoid specifying
MANIFEST_DIRECTORY
you could add astaticlib
target to your package manifest as a workaround to make corrosion import the crate. -
HEADER_NAME: The name of the generated header file. This will be the name which you include in your C/C++ code (e.g.
#include "myproject/myheader.h" if you specify
HEADER_NAME "myproject/myheader.h"`. -
CBINDGEN_VERSION: Version requirement for cbindgen. Exact semantics to be specified. Currently not implemented.
-
FLAGS: Arbitrary other flags for
cbindgen
. Runcbindgen --help
to see the possible flags.
Current limitations
- Cbindgens (optional) macro expansion feature internally actually builds the crate / runs the build script. For this to work as expected in all cases, we probably need to set all the same environment variables as when corrosion builds the crate. However the crate is a library, so we would need to figure out which target builds it - and if there are multiple, potentially generate bindings per-target? Alternatively we could add support of setting some environment variables on rlibs, and pulling that information in when building the actual corrosion targets Alternatively we could restrict corrosions support of this feature to actual imported staticlib/cdylib targets.
Current limitations
- The current version regenerates the bindings more often then necessary to be on the safe side, but an upstream PR is open to solve this in a future cbindgen version.
cxx integration
⚠️⚠️⚠️ EXPERIMENTAL ⚠️⚠️⚠️
cxx is a tool which generates bindings for C++/Rust interop.
corrosion_add_cxxbridge(cxx_target
CRATE <imported_target_name>
[FILES <file1.rs> <file2.rs>]
)
Adds build-rules to create C++ bindings using the cxx crate.
Arguments:
cxxtarget
: Name of the C++ library target for the bindings, which corrosion will create.- FILES: Input Rust source file containing #[cxx::bridge].
- CRATE: Name of an imported Rust target. Note: Parameter may be renamed before release
Currently missing arguments
The following arguments to cxxbridge currently have no way to be passed by the user:
--cfg
--cxx-impl-annotations
--include
The created rules approximately do the following:
- Check which version of
cxx
the Rust crate specified by theCRATE
argument depends on. - Check if the exact same version of
cxxbridge-cmd
is installed (available inPATH
) - If not, create a rule to build the exact same version of
cxxbridge-cmd
. - Create rules to run
cxxbridge
and generate- The
rust/cxx.h
header - A header and source file for each of the files specified in
FILES
- The
- The generated sources (and header include directories) are added to the
cxxtarget
CMake library target.
Limitations
We currently require the CRATE
argument to be a target imported by Corrosion, however,
Corrosion does not import rlib
only libraries. As a workaround users can add
staticlib
to their list of crate kinds. In the future this may be solved more properly,
by either adding an option to also import Rlib targets (without build rules) or by
adding a MANIFEST_PATH
argument to this function, specifying where the crate is.
Contributing
Specifically some more realistic test / demo projects and feedback about limitations would be welcome.
Commonly encountered (Non-Corrosion) Issues
Table of Contents
- Linking Debug C/C++ libraries into Rust fails on Windows MSVC targets
- Linking Rust static libraries into Debug C/C++ binaries fails on Windows MSVC targets
- Missing
soname
on Linux forcdylibs
- Missing
install_name
on MacOS forccdylibs
/ Hardcoded references to the build-directory
Linking Debug C/C++ libraries into Rust fails on Windows MSVC targets
rustc
always links against the non-debug Windows runtime on *-msvc
targets.
This is tracked in this issue
and could be fixed upstream.
A typical error message for this issue is:
Compiling rust_bin v0.1.0 (D:\a\corrosion\corrosion\test\cxxbridge\cxxbridge_cpp2rust\rust)
error: linking with `link.exe` failed: exit code: 1319
[ redacted ]
= note: cxxbridge-cpp.lib(lib.cpp.obj) : error LNK2038: mismatch detected for '_ITERATOR_DEBUG_LEVEL': value '2' doesn't match value '0' in libcxx-bafec361a1a30317.rlib(cxx.o)
cxxbridge-cpp.lib(lib.cpp.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MDd_DynamicDebug' doesn't match value 'MD_DynamicRelease' in libcxx-bafec361a1a30317.rlib(cxx.o)
cpp_lib.lib(cpplib.cpp.obj) : error LNK2038: mismatch detected for '_ITERATOR_DEBUG_LEVEL': value '2' doesn't match value '0' in libcxx-bafec361a1a30317.rlib(cxx.o)
cpp_lib.lib(cpplib.cpp.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MDd_DynamicDebug' doesn't match value 'MD_DynamicRelease' in libcxx-bafec361a1a30317.rlib(cxx.o)
msvcrt.lib(initializers.obj) : warning LNK4098: defaultlib 'msvcrtd.lib' conflicts with use of other libs; use /NODEFAULTLIB:library
Solutions
One solution is to also use the non-debug version when building the C/C++ libraries.
You can set the MSVC_RUNTIME_LIBRARY target properties of your C/C++ libraries to the non-debug variants.
By default you will probably want to select the MultiThreadedDLL
variant, unless you specified
-Ctarget-feature=+crt-static
in your
RUSTFLAGS
.
Linking Rust static libraries into Debug C/C++ binaries fails on Windows MSVC targets
This issue is quite similar to the previous one, except that this time it's a Rust library being linked into a C/C++ target. If it's 100% only Rust code you likely won't even have any issues. However, if somewhere in the dependency graph C/C++ code is built and linked into your Rust library, you will likely encounter this issue. Please note, that using cxx counts as using C++ code and will lead to this issue.
The previous solution should also work for this case, but additionally you may also
have success by using
corrosion_set_env_vars(your_rust_lib "CFLAGS=-MDd" "CXXFLAGS=-MDd")
(or -MTd
for a statically linked
runtime).
For debug builds, this is likely to be the preferable solution. It assumes that downstream C/C++ code
is built by the cc
crate, which respects the CFLAGS
and CXXFLAGS
environment variables.
Missing soname
on Linux for cdylibs
Cargo doesn't support setting the soname
field for cdylib, which may cause issues.
You can set the soname manually by passing a linker-flag such as -Clink-arg=-Wl,-soname,libyour_crate.so
to the linker via corrosion_add_target_local_rustflags()
and additionally seting the IMPORTED_SONAME
property on the import CMake target:
set_target_properties(your_crate-shared PROPERTIES IMPORTED_SONAME libyour_crate.so)
Replace your_crate
with the name of your shared library as defined in the [lib]
section of your Cargo.toml
Manifest file.
Attention: The Linux section may not be entirely correct, maybe $ORIGIN
needs to be added to the linker arguments.
Feel free to open a pull-request with corrections.
Missing install_name
on MacOS for ccdylibs
/ Hardcoded references to the build-directory
The solution here is essentially the same as in the previous section.
corrosion_add_target_local_rustflags(your_crate -Clink-arg=-Wl,-install_name,@rpath/libyour_crate.dylib,-current_version,${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR},-compatibility_version,${PROJECT_VERSION_MAJOR}.0)
set_target_properties(your_crate-shared PROPERTIES IMPORTED_NO_SONAME 0)
set_target_properties(your_crate-shared PROPERTIES IMPORTED_SONAME libyour_crate.dylib)
When building binaries using this shared library, you should set the build rpath to the output directory of
your shared library, e.g. by setting set(CMAKE_BUILD_RPATH ${YOUR_CUSTOM_OUTPUT_DIRECTORY})
before adding
executables.
For a practical example, you may look at Slint PR 2455.