Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  • CRATES will not work. We do not support Cargo Workspaces yet.
  • CRATE_TYPES – We always import a lib/rlib with Crubit
  • OVERRIDE_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.