This post outlines an example on how to integrate a library written in rust
into a cmake based C/C++
project using the ExternalProject
module.
I created that post mainly as reference for myself but hopefully it is helpful
to someone else who stumbles across it :^)
The rust library is compiled as static library and then linked into the executable written in C. The top-level cmake file to build the executable just includes the rust crate via add_subdirectory and then adds a link dependency.
cmake_minimum_required(VERSION 3.14)
project(calc)
# Add libcalc (rust).
add_subdirectory(libcalc)
# Build calc add_executable.
add_executable(calc calc.c)
target_compile_options(calc PRIVATE -Wall -Wextra)
# Link against rust lib.
target_link_libraries(calc libcalc)
The more interesting bits are included in the rust crate.
First we have to tell cargo to output a static library. This can be done with the following configuration in the Cargo.toml file.
[lib]
crate-type = ["staticlib"]
A header file with the C definitions is automatically generated using the cbindgen crate invoked from the build.rs script.
Finally we can write a CMakeLists.txt
file which exposes a pseudo target
libcalc
to cmake which consumers can depend on. The pseudo target depends on
the ext_libcalc
(ExternalProject) target.
The BUILD_COMMAND
contains an generator expression to build the rust crate in
release mode in case CMAKE_BUILD_TYPE=Release
is configured.
Additionally, the properties of the libcalc
pseudo target are setup to
automatically add the correct static library and add the include path
accordingly for consumers.
With this setup, the rust crate is nicely build out of source. This is achieved by the following two configurations:
- Pass
--target-dir
to cargo to specify the build directory. - Setup
LIBCALC_BUILD_DIR
before invoking cargo to tell build.rs where to generate the C header.
cmake_minimum_required(VERSION 3.14)
set(BDIR ${CMAKE_BINARY_DIR}/libcalc)
# Define external project to build rust lib with cargo.
include(ExternalProject)
ExternalProject_Add(
ext_libcalc
# Always trigger build, let cargo decide if we want to rebuild.
BUILD_ALWAYS ON
CONFIGURE_COMMAND ""
# Can not pass arguments to build.rs and was not able to set env variables
# for the external build command, therefore use 'env' to setup env var.
BUILD_COMMAND env LIBCALC_BUILD_DIR=${BDIR}
cargo build --target-dir ${BDIR} "$<IF:$<CONFIG:Release>,--release,>"
BUILD_BYPRODUCTS ${BDIR}/$<IF:$<CONFIG:Release>,release,debug>/libcalc.a
${BDIR}/libcalc.h
INSTALL_COMMAND ""
# Location of sources (since we don't download).
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}"
# Build dir location used as CWD for build commands.
BINARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}"
# Root directory for external project in cmake build dir.
PREFIX "libcalc"
# Log directory (relative to PREFIX).
LOG_DIR "log"
# Log build step.
LOG_BUILD ON
# In case of error output log on terminal.
LOG_OUTPUT_ON_FAILURE ON
)
# Define pseudo target (import lib) for usage in cmake and let it depend on
# the cargo build.
add_library(libcalc STATIC IMPORTED GLOBAL)
add_dependencies(libcalc ext_libcalc)
# Configure the import locations (libs) for the import lib.
set_target_properties(libcalc PROPERTIES
IMPORTED_CONFIGURATIONS "Debug;Release"
IMPORTED_LOCATION "${BDIR}/release/libcalc.a"
IMPORTED_LOCATION_DEBUG "${BDIR}/debug/libcalc.a"
)
# Configure the additional interface for they pseudo target.
target_include_directories(libcalc INTERFACE "${BDIR}")
The sources referenced in this post are available here. This repository includes the following Makefile to build and run the calc executable.
TYPE ?= Debug
BDIR := BUILD/$(TYPE)
calc: config
cmake --build $(BDIR) --target calc
config: CMakeLists.txt
cmake -B $(BDIR) -DCMAKE_BUILD_TYPE=$(TYPE) .
run: calc
$(BDIR)/calc
clean:
$(RM) -r BUILD