Hi,
I've created a C binding to the wav2letter c++ framework. The Cmake build works fine.
Next I want to build a Rust executable with it. I link the Cmake output of the static library (libw2l_api.a) in the build.rs. When using cargo build I encounter the first error: the C++ library with the C binding doesn't support PIE.
So I either compile the C library with PIE or I disable PIE via a Rustflag. No matter what I choose I receive countless undefined reference errors (see below). I think the errors are largely the same for both options.
Where does the linking go wrong? From my noob understanding I assumed that the linking is already done (the internal one, from C API to the C++ library etc). That's why I built it with CMake.
Is it correct that Cargo does the linking again?
In more detail the steps I followed:
- build C library with -fPIE compile option and then
cargo build-> undefined reference errors
see here:
https://gist.github.com/nihiluis/5d681865331ce42b08c06d625a884cee
- take C lib without PIE enabled and then run cargo build with pie disabled
RUSTFLAGS='-C link-args=-no-pie' cargo build
see here:
https://gist.github.com/nihiluis/4139c8b1027e7219952dc16ce4645c34
I also checked whether a missing reference is included in the static library by example via
nm -C ../w2l/build/libw2l_api.a | grep w2l::streaming::IOBuffer::debug
U w2l::streaming::IOBuffer::debugString[abi:cxx11]() const
U w2l::streaming::IOBuffer::debugString[abi:cxx11]() const
to me it seems that the function is included. So it seems that the C(++) library is fine.
I can provide the source repo if needed/it helps.
This is my build.rs
use std::env;
use std::path::PathBuf;
fn main() {
println!("cargo:rustc-link-lib=static=stdc++");
println!("cargo:rustc-link-lib=static=w2l_api");
println!("cargo:rerun-if-changed=../w2l/src/api.h");
println!("cargo:rustc-link-search=native=../w2l/build");
let bindings = bindgen::Builder::default()
// The input header we would like to generate
// bindings for.
.header("../w2l/src/api.h")
// Tell cargo to invalidate the built crate whenever any of the
// included header files changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Unable to generate bindings");
// Write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
This is my cmake file.
cmake_minimum_required(VERSION 3.5.1)
cmake_policy(VERSION 3.5.1)
set(project_name w2l) ## rename your project here
project(${project_name})
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" )
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(cereal_INCLUDE_DIRS /root/wav2letter/build/inference/cereal/src/cereal/include)
find_package(Wav2Letter REQUIRED)
find_package(Threads REQUIRED)
find_package(GFlags REQUIRED)
add_subdirectory(3rdparty)
add_library(${project_name}_api
STATIC
src/api.cpp
src/api.h
${CMAKE_CURRENT_LIST_DIR}/src/w2l.cpp
${CMAKE_CURRENT_LIST_DIR}/src/AudioToWords.cpp
${CMAKE_CURRENT_LIST_DIR}/src/Util.cpp
)
target_link_libraries(
${project_name}_api
PUBLIC
streaming_inference_common
streaming_inference_modules_nn_backend
streaming_inference_decoder
streaming_inference_modules_feature
decoder-library
Threads::Threads
)
#target_compile_options(${project_name}_api PRIVATE -fpie)
target_include_directories(
${project_name}_api
PUBLIC
${CMAKE_CURRENT_LIST_DIR}
${cereal_INCLUDE_DIRS}
${wav2letter-inference_SOURCE_DIR}
${wav2letter++_SOURCE_DIR}/src
)