Ostatnimi czasy zastanawiałem się nad tym, czy można w prosty sposób zintegrować projekt napisany w C++
przy użyciu CMake
i Conan z projektem napisanym w Rust
.
Okazało się, że już są takie biblioteki w Rust
jak Conan
i CMake
, więc nie zostało mi nic innego jak zrobić przykładowy projekt i poskładać to w całość.
Zaczynamy od projektu C++
z CMake
, mój projekt nazywa się regexp_pcre
Plik CMakeLists.txt
u mnie wygląda tak:
cmake_minimum_required(VERSION 3.16) project(regexp_pcre) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_COMPILER clang++) include_directories(include) execute_process(COMMAND conan install ${CMAKE_CURRENT_SOURCE_DIR}/ -if=${CMAKE_BINARY_DIR}/ -pr=default) include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) conan_basic_setup(TARGETS) include_directories(${CONAN_INCLUDE_DIRS}) add_executable(regexp_pcre main.cpp ) target_link_libraries(regexp_pcre -lstdc++ CONAN_PKG::jsoncpp CONAN_PKG::pcre) add_library(regexp_pcre_lib lib.cpp ) target_link_libraries(regexp_pcre_lib -lstdc++ CONAN_PKG::jsoncpp CONAN_PKG::pcre CONAN_PKG::glog ) install(TARGETS ${PROJECT_NAME}_lib DESTINATION .) file(GLOB HEADERS include/*.h) install(FILES ${HEADERS} DESTINATION ../../../../include/)
Dodałem sobie execute_process(..)
żeby przy każdym odświeżeniu CMake
budowały mi się zależności z Conan
jak również żeby nie było problemu gdy projekt będzie kompilowany przez Cargo
Nagłówki do projektu również umieszczam w dość egzotycznym miejscu, ale wynika to z tego, że chce mieć nagłówki w katalogu target w projekcie Rust
, tak żebym miał do nich łatwy dostęp z poziomu Rust
Kolejnym niezbędnym elementem jest plik: conanfile.txt
który u mnie wygląda tak:
[requires] jsoncpp/1.9.4 pcre/8.44 glog/0.4.0 [generators] cmake [options] pcre:with_jit=True
Pozostaje jeszcze stworzyć profil Conan
, ja używam default
, ale jak to woli, nie ma to znaczenia, natomiast trzeba pamiętać o zmianie w CMakeLists.txt
Moja definicja profilu wygląda tak:
[settings] os=Linux os_build=Linux arch=x86_64 arch_build=x86_64 compiler=gcc compiler.version=10 compiler.libcxx=libstdc++11 build_type=Release [options] [build_requires] [env]
Plik nagłówka wygląda następująco:
#ifndef REGEXP_PCRE_LIB_H #define REGEXP_PCRE_LIB_H void example(const std::string& s); #endif //REGEXP_PCRE_LIB_H
Logiki funkcji nie będę omawiać ponieważ nie ma ona żadnego znaczenia w naszym przykładzie
Więc mając już kompletną część związaną z C++
przechodzimy do części związanej z Rust
Mój projekt nazywa się cxx_test
Zaczynamy od zależności jakie potrzebujemy, dopisujemy je do pliku Cargo.toml
Blok build-dependencies
niezbędne do wykonania operacji kompilacji podczas budowania projektu
[build-dependencies] cxx-build = "1.0" cmake = "0.1" conan = "0.1"
Dodatkowo potrzebujemy jeden zależności w dependencies
[dependencies] cxx = "1.0"
Mając już niezbędne zależności możemy przejść do utworzenia pliku: build.rs
– pliku z definicją kompilacji zależności
U mnie wygląda on tak:
use cmake::Config; extern crate conan; use conan::*; use std::env; use std::path::Path; fn main() { let conan_profile = "default"; let command = InstallCommandBuilder::new() .with_profile(&conan_profile) .build_policy(BuildPolicy::Missing) .recipe_path(Path::new("../../cpp/regexp_pcre/conanfile.txt")) .build(); let build_info = command.generate().unwrap(); let json_include = build_info.get_dependency("jsoncpp").unwrap().get_include_dir().unwrap(); let glog_include = build_info.get_dependency("glog").unwrap().get_include_dir().unwrap(); let gflag_include = build_info.get_dependency("gflags").unwrap().get_include_dir().unwrap(); let dst = Config::new("../../cpp/regexp_pcre") .cxxflag("-O3") .build(); cxx_build::bridge("src/main.rs") .file("src/blodstone.cpp") .include("include") .include("target/cxxbridge/rust") .include("target/cxxbridge/cxx_test/src") .include(json_include) .include(glog_include) .include(gflag_include) .opt_level_str("fast") .flag("-std=c++20") .compiler("clang++") .compile("cxx-test"); println!("cargo:rustc-link-search=native={}", dst.display()); println!("cargo:rustc-link-lib=regexp_pcre_lib"); build_info.cargo_emit(); }
Szczegóły jak zbudować plik znajdują się na następujących stronach:
Poniżej fragment budujący zależności pobrane z conanfile.txt
projektu w CMake
let conan_profile = "default"; let command = InstallCommandBuilder::new() .with_profile(&conan_profile) .build_policy(BuildPolicy::Missing) .recipe_path(Path::new("../../cpp/regexp_pcre/conanfile.txt")) .build(); let build_info = command.generate().unwrap();
Następnie wyciągamy sobie niezbędne lokalizacje katalogów include
, po to żeby móc użyć w naszym projekcie zależności z conan
let json_include = build_info.get_dependency("jsoncpp").unwrap().get_include_dir().unwrap(); let glog_include = build_info.get_dependency("glog").unwrap().get_include_dir().unwrap(); let gflag_include = build_info.get_dependency("gflags").unwrap().get_include_dir().unwrap();
Kompilacja projektu CMake
let dst = Config::new("../../cpp/regexp_pcre") .cxxflag("-O3") .build();
Nasz projekt łączący świat Rust
z C++
przy użyciu biblioteki cxx
cxx_build::bridge("src/main.rs") .file("src/blodstone.cpp") .include("include") .include("target/cxxbridge/rust") .include("target/cxxbridge/cxx_test/src") .include(json_include) .include(glog_include) .include(gflag_include) .opt_level_str("fast") .flag("-std=c++20") .compiler("clang++") .compile("cxx-test");
Ostatni element to jest dodanie zależności do bibliotek z conan
i projektu CMake
do linkera
println!("cargo:rustc-link-search=native={}", dst.display()); println!("cargo:rustc-link-lib=regexp_pcre_lib"); build_info.cargo_emit();
Następnie tworzymy katalog include
w katalogu głównym projektu, w nim tworzymy plik blodstone.h
z definicjami funkcje które będziemy wykonywać w C++
Następnie w src
tworzymy plik blodstone.cpp
z kodem źródłowym, więcej informacji o przykładzie można znaleźć na stronie: https://cxx.rs/build/cargo.html
Potem przechodzimy do naszego głównego pliku projektu Rust
main.rs
lub lib.rs
Dodajemy w nim taki fragment kody:
#[cxx::bridge] mod bridge { unsafe extern "C++" { include!("cxx_test/include/blodstone.h"); include!("cxx_test/target/include/lib.h"); pub(crate) fn format_stream(); pub(crate) fn example(s: &CxxString); } }
Gdzie pierwsza funkcja to proxy do naszego wewnętrznego projektu a druga do projektu w CMake
Dzięki temu że nagłówki projektu CMake
instalowaliśmy w takim dziwnym miejscu to teraz możemy się do nich dostać z takiej lokalizacji
include!("cxx_test/target/include/lib.h");
To co nam pozostaje to np, w funkcji main()
wywołać wcześniej zdefiniowane funkcje w C++
fn main() { unsafe { // funckaj z projektu wewnętrznego format_stream(); // funkcja z projektu CMake let_cxx_string!(s = "hello world"); example(&s); } }
Kompilujemy, ja kompiluje i uruchamiamy z opcją -vv
żeby wiedzieć dokładnie co się dzieje
cargo run --release -vv
W wyniku dostajemy
I0327 15:30:10.541882 90404 blodstone.cpp:73] json_file {"action":"run","data":{"number":1}} I0327 15:30:11.542809 90404 lib.cpp:26] pcre found: 0 I0327 15:30:11.542840 90404 lib.cpp:43] { "action" : "run", "data" : { "number" : 1 } }
Oczywiście jest to zupełnie przykładowy wynik działania, ale widać, że odpalił się kod z blodstone.cpp
oraz z lib.cpp
Przykładowy kod można znaleźć tutaj:
Hi! Do you know if they make any plugins to protect against hackers?
I’m kinda paranoid about losing everything I’ve worked hard on. Any tips?
Thanks for one’s marvelous posting! I truly enjoyed reading it, you will be a great author. I will make sure to bookmark your blog and will come back at some point. I want to encourage you to continue your great writing, have a nice day!