From a93e90cf3c50344a2582acb0e60187dbef90ee28 Mon Sep 17 00:00:00 2001 From: Johannes Stoelp Date: Mon, 30 May 2022 20:44:53 +0200 Subject: cmake: add example how to integrate cargo --- content/2022-05-30-cmake-cargo-example.md | 68 ++++++++++++++++++++++ content/2022-05-30-cmake-cargo-example/.gitignore | 1 + .../2022-05-30-cmake-cargo-example/CMakeLists.txt | 11 ++++ content/2022-05-30-cmake-cargo-example/Makefile | 14 +++++ content/2022-05-30-cmake-cargo-example/calc.c | 19 ++++++ .../libcalc/.gitignore | 3 + .../libcalc/CMakeLists.txt | 48 +++++++++++++++ .../libcalc/Cargo.toml | 10 ++++ .../libcalc/build.rs | 24 ++++++++ .../libcalc/src/lib.rs | 9 +++ 10 files changed, 207 insertions(+) create mode 100644 content/2022-05-30-cmake-cargo-example.md create mode 100644 content/2022-05-30-cmake-cargo-example/.gitignore create mode 100644 content/2022-05-30-cmake-cargo-example/CMakeLists.txt create mode 100644 content/2022-05-30-cmake-cargo-example/Makefile create mode 100644 content/2022-05-30-cmake-cargo-example/calc.c create mode 100644 content/2022-05-30-cmake-cargo-example/libcalc/.gitignore create mode 100644 content/2022-05-30-cmake-cargo-example/libcalc/CMakeLists.txt create mode 100644 content/2022-05-30-cmake-cargo-example/libcalc/Cargo.toml create mode 100644 content/2022-05-30-cmake-cargo-example/libcalc/build.rs create mode 100644 content/2022-05-30-cmake-cargo-example/libcalc/src/lib.rs diff --git a/content/2022-05-30-cmake-cargo-example.md b/content/2022-05-30-cmake-cargo-example.md new file mode 100644 index 0000000..56810e6 --- /dev/null +++ b/content/2022-05-30-cmake-cargo-example.md @@ -0,0 +1,68 @@ ++++ +title = "Example: integrate cargo into cmake" + +[taxonomies] +tags = ["rust", "c++", "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][cmake-ext_prj] +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][cmake-add_sub] and then adds a +link dependency. + +```cmake +{{ include(path="2022-05-30-cmake-cargo-example/CMakeLists.txt") }} +``` + +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][cargo-toml] file. +```toml +[lib] +crate-type = ["staticlib"] +``` +> A header file with the C definitions is automatically generated using the +> [cbindgen][cbindgen] crate invoked from the [build.rs][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][cmake-ext_prj]) 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][build-rs] + where to generate the C header. + +```cmake +{{ include(path="2022-05-30-cmake-cargo-example/libcalc/CMakeLists.txt") }} +``` + +The sources referenced in this post are available [here][post-src]. This +repository includes the following Makefile to build and run the calc +executable. +```make +{{ include(path="2022-05-30-cmake-cargo-example/Makefile") }} +``` + +[post-src]: https://git.memzero.de/johannst/blog/src/branch/main/content/2022-05-30-cmake-cargo-example +[cargo-toml]: https://git.memzero.de/johannst/blog/src/branch/main/content/2022-05-30-cmake-cargo-example/libcalc/Cargo.toml +[build-rs]: https://git.memzero.de/johannst/blog/src/branch/main/content/2022-05-30-cmake-cargo-example/libcalc/build.rs +[make]: https://git.memzero.de/johannst/blog/src/branch/main/content/2022-05-30-cmake-cargo-example/Makefile +[cbindgen]:https://lib.rs/crates/cbindgen +[cmake-add_sub]: https://cmake.org/cmake/help/latest/command/add_subdirectory.html?highlight=add_subdirectory +[cmake-ext_prj]: https://cmake.org/cmake/help/latest/module/ExternalProject.html diff --git a/content/2022-05-30-cmake-cargo-example/.gitignore b/content/2022-05-30-cmake-cargo-example/.gitignore new file mode 100644 index 0000000..f2ce476 --- /dev/null +++ b/content/2022-05-30-cmake-cargo-example/.gitignore @@ -0,0 +1 @@ +/BUILD diff --git a/content/2022-05-30-cmake-cargo-example/CMakeLists.txt b/content/2022-05-30-cmake-cargo-example/CMakeLists.txt new file mode 100644 index 0000000..6444ed0 --- /dev/null +++ b/content/2022-05-30-cmake-cargo-example/CMakeLists.txt @@ -0,0 +1,11 @@ +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) diff --git a/content/2022-05-30-cmake-cargo-example/Makefile b/content/2022-05-30-cmake-cargo-example/Makefile new file mode 100644 index 0000000..e269cb2 --- /dev/null +++ b/content/2022-05-30-cmake-cargo-example/Makefile @@ -0,0 +1,14 @@ +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 diff --git a/content/2022-05-30-cmake-cargo-example/calc.c b/content/2022-05-30-cmake-cargo-example/calc.c new file mode 100644 index 0000000..27ca0a9 --- /dev/null +++ b/content/2022-05-30-cmake-cargo-example/calc.c @@ -0,0 +1,19 @@ +#include +#include + +#include + +#define EVAL(expr) \ + printf("%s = %d\n", #expr, expr); + +int main() { + EVAL(wrap_add(1,2)); + EVAL(wrap_add(INT_MAX,0)); + EVAL(wrap_add(INT_MAX,1)); + + EVAL(sat_add(1,2)); + EVAL(sat_add(INT_MAX,0)); + EVAL(sat_add(INT_MAX,1)); + + return 0; +} diff --git a/content/2022-05-30-cmake-cargo-example/libcalc/.gitignore b/content/2022-05-30-cmake-cargo-example/libcalc/.gitignore new file mode 100644 index 0000000..008b9b4 --- /dev/null +++ b/content/2022-05-30-cmake-cargo-example/libcalc/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +libcalc.h diff --git a/content/2022-05-30-cmake-cargo-example/libcalc/CMakeLists.txt b/content/2022-05-30-cmake-cargo-example/libcalc/CMakeLists.txt new file mode 100644 index 0000000..2abab17 --- /dev/null +++ b/content/2022-05-30-cmake-cargo-example/libcalc/CMakeLists.txt @@ -0,0 +1,48 @@ +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} "$,--release,>" + BUILD_BYPRODUCTS ${BDIR}/$,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}") diff --git a/content/2022-05-30-cmake-cargo-example/libcalc/Cargo.toml b/content/2022-05-30-cmake-cargo-example/libcalc/Cargo.toml new file mode 100644 index 0000000..23ea182 --- /dev/null +++ b/content/2022-05-30-cmake-cargo-example/libcalc/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "calc" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["staticlib"] + +[build-dependencies] +cbindgen = "0.*" diff --git a/content/2022-05-30-cmake-cargo-example/libcalc/build.rs b/content/2022-05-30-cmake-cargo-example/libcalc/build.rs new file mode 100644 index 0000000..acfa6a7 --- /dev/null +++ b/content/2022-05-30-cmake-cargo-example/libcalc/build.rs @@ -0,0 +1,24 @@ +fn main() { + let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + let out_file = format!("{}/libcalc.h", std::env::var("LIBCALC_BUILD_DIR").unwrap_or(String::from("."))); + + let cfg = cbindgen::Config { + cpp_compat: true, + ..Default::default() + }; + + if std::path::Path::new(&out_file).exists() { + std::fs::remove_file(&out_file).unwrap(); + } + + let ok = cbindgen::Builder::new() + .with_config(cfg) + .with_crate(crate_dir) + .with_language(cbindgen::Language::C) + .with_include_guard("LIBCALC_H") + .generate() + .expect("Unable to generate bindings") + .write_to_file(&out_file); + assert!(ok); +} diff --git a/content/2022-05-30-cmake-cargo-example/libcalc/src/lib.rs b/content/2022-05-30-cmake-cargo-example/libcalc/src/lib.rs new file mode 100644 index 0000000..a36aace --- /dev/null +++ b/content/2022-05-30-cmake-cargo-example/libcalc/src/lib.rs @@ -0,0 +1,9 @@ +#[no_mangle] +pub extern "C" fn wrap_add(a: i32, b: i32) -> i32 { + a.wrapping_add(b) +} + +#[no_mangle] +pub extern "C" fn sat_add(a: i32, b: i32) -> i32 { + a.saturating_add(b) +} -- cgit v1.2.3