Build System Integrations
crubit.rs/overview/build_systems
[TOC]
CMake
cpp_api_from_rust has support for building with CMake, allowing C++ code built by CMake to call Rust code built with Cargo through Crubit generated bindings. This support builds on Corrosion, the premier marriage between Rust and CMake. To set up a CMake project using Crubit you’ll need two pieces:
- A CMake Project
- A Cargo Project (embedded within the CMake project)
You can find an example setup project at
examples/build_systems/cmake.
We’ll walk through what that project does
below.
Build Configuration
Crubit uses Cargo to build your Rust library. Modifications to your Cargo.toml
will be respected and reflected when the Crubit CMake targets are built. We only
require that your project provides a library package (specifically we need it to
provide an .rlib or .rmeta file).
On the CMake side, Corrosion provides a suite of options to tune the Cargo build. See their usage docs for details. These are threaded through to the underlying cargo build as expected, with some notable exceptions:
CRATESwill not work. We do not support Cargo Workspaces yet.CRATE_TYPES– We always import a lib/rlib with CrubitOVERRIDE_CRATE_TYPE– We must use--crate-type=lib
The Corrosion-generated CMake target is an interface library containing the generated header and a static library containing the Rust code and generated FFI bindings.
Example Setup
To setup Crubit with CMake, first we’ll need a CMake project, see their their
documentation
for help configuring one. At minimum, you’ll need a directory (we’ll call ours
cmake) with a CMakeLists.txt at its root containing:
cmake_minimum_required(VERSION 3.22)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER clang++)
project(example_project
VERSION 0.1.0
LANGUAGES CXX)
From there we’ll need a Cargo project. We can create one using the shell command:
cmake/$ cargo new --lib rust_lib
Because we’re using Crubit to call our Rust code, our cargo project must produce a normal Rust library (and not a binary, static library, cdylib, etc.).
Our two projects are combined through Corrosion. In our CMakeLists.txt, we
fetch Corrosion and enable it for our Cargo project rust_lib:
include(FetchContent)
FetchContent_Declare(
Corrosion
# NB: We temporarily require a fork of corrossion as we work to upstream our changes.
GIT_REPOSITORY https://github.com/thunderseethe/corrosion
GIT_TAG main
)
FetchContent_MakeAvailable(Corrosion)
We use Corrosion, a Cargo CMake
integration, to add our Cargo project to our CMake project:
corrosion_experimental_crubit(rust_lib)
corrosion_import_crate(MANIFEST_PATH rust_lib/Cargo.toml)
rust_lib is the name of our library package in our Cargo project, which
defaults to crate name. If we were to rename our package, we’d have to update
this to reflect the package name rather than the crate or directory name.
Corrosion takes a path to the Cargo.toml of our project and uses that to extract
the necessary metadata.
Under the hood, Corrosion generates a CMake library target named rust_lib,
after our Rust package. We can use that target to depend on Rust from our C++
code. If we have a CMake executable main, we can add a dependency on Rust by
adding the following to our CMakeLists.txt:
add_executable(main main.cpp)
target_link_libraries(main PRIVATE rust_lib)
Crubit generates a C++ header for Rust library based on the library package
name, as well. In main.cpp, we can import our crubit/rust_lib.h header:
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <iostream>
#include <string_view>
#include "crubit/rust_lib.h"
#include "crubit/std.h"
int main() {
// Test we can successfully use a type from a Rust dependency.
base64::engine::GeneralPurpose engine = rust_lib::make_engine();
std::cout << "Successfully linked crubit generated C++ api: "
<< rust_lib::add(1, 2) << std::endl;
// Test we can use bindings generated from the Rust standard library.
rs::std::path::PathBuf temp_dir = rs::std::env::temp_dir();
rust_lib::Gymnastics g = rust_lib::Gymnastics::new_(temp_dir);
std::string_view temp_dir_str = g.as_str();
std::cout << "Successfully used Rust std::env::temp_dir(): " << temp_dir_str
<< std::endl;
return 0;
}
With that we’re calling Rust code from C++! Our CMake integration is subject to the same limitations as our other build integrations.