[root@memzero]# ls

2022/05/30 - Example: integrate cargo into cmake

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:

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