Как указать только бинарные зависимости (без исходников) в Cargo для Rust?
Прямая линковка через build.rs и внешние библиотеки
В Rust Cargo не поддерживает «бинарные зависимости» напрямую, как это делается, например, в C++ с заголовочными файлами и .lib/.dll. Однако вы можете подключить предварительно скомпилированную библиотеку через системный линковщик с помощью скрипта сборки build.rs. Этот подход полностью решает вашу задачу.
Шаг 1: Создайте build.rs
В корне вашего проекта (рядом с Cargo.toml) создайте файл build.rs:
// build.rs
fn main() {
// Указываем путь к папке с .so/.dll файлами
println!("cargo:rustc-link-search=/opt/vendor/sdk/lib");
// Указываем имя библиотеки (без префикса lib и расширения)
println!("cargo:rustc-link-lib=my_sdk");
// Если нужно указать версию или дополнительные флаги:
// println!("cargo:rustc-link-arg=-Wl,-rpath,/opt/vendor/sdk/lib");
}
Шаг 2: Объявите внешнюю функцию в Rust
В вашем main.rs или lib.rs добавьте объявление FFI (Foreign Function Interface):
// main.rs
#[link(name = "my_sdk")]
extern "C" {
fn my_sdk_init() -> i32;
fn my_sdk_process(data: *const u8, len: u32) -> i32;
}
fn main() {
unsafe {
let result = my_sdk_init();
println!("SDK init result: {}", result);
}
}
Шаг 3: Настройте Cargo.toml
В Cargo.toml не нужно указывать зависимость через [dependencies]. Вместо этого добавьте build = "build.rs" (если файл называется иначе):
[package]
name = "my_app"
version = "0.1.0"
edition = "2021"
build = "build.rs"
Почему это работает?
cargo:rustc-link-search— добавляет путь, где компилятор ищет библиотеки во время линковки.cargo:rustc-link-lib— указывает имя библиотеки для линковки (например,libmy_sdk.soстанетmy_sdk).extern "C"— объявляет, что функция имеет C-совместимый ABI, что необходимо для работы с нативными библиотеками.
Решение проблемы с запуском (error while loading shared libraries)
Ошибка возникает, потому что динамический линковщик (ld.so) не знает, где искать .so файл во время выполнения. Есть три способа исправить:
- Установить библиотеку в системный путь (например,
/usr/lib). - Использовать rpath — добавьте в build.rs строку:
println!("cargo:rustc-link-arg=-Wl,-rpath,/opt/vendor/sdk/lib"); - Установить переменную окружения
LD_LIBRARY_PATH:export LD_LIBRARY_PATH=/opt/vendor/sdk/lib:$LD_LIBRARY_PATH cargo run
Полный пример
Предположим, у вас есть файл /opt/vendor/sdk/lib/libmy_sdk.so. Тогда ваш build.rs будет:
fn main() {
println!("cargo:rustc-link-search=/opt/vendor/sdk/lib");
println!("cargo:rustc-link-lib=my_sdk");
// Для runtime:
println!("cargo:rustc-link-arg=-Wl,-rpath,/opt/vendor/sdk/lib");
}
После этого cargo build и cargo run будут работать корректно. Если библиотека статическая (.a), то нужно указать static:
println!("cargo:rustc-link-lib=static=my_sdk");
Надеюсь, это поможет! Если у вас есть заголовочные файлы на C, рекомендую использовать bindgen для автоматической генерации Rust-обёрток.
Альтернативный подход: обёртка в виде системного крейта
Если вы не хотите писать build.rs каждый раз, можно создать отдельный крейт-обёртку, который будет представлять вашу бинарную библиотеку. Этот крейт будет содержать только build.rs и Rust-объявления FFI, но не исходники самой библиотеки.
Структура обёртки
my_sdk_sys/
├── Cargo.toml
├── build.rs
└── src/
└── lib.rs
В Cargo.toml обёртки:
[package]
name = "my_sdk_sys"
version = "0.1.0"
edition = "2021"
[lib]
name = "my_sdk_sys"
crate-type = ["lib"]
В build.rs — те же команды для линковки. В lib.rs — только объявления функций. Затем в основном проекте вы просто добавляете зависимость:
[dependencies]
my_sdk_sys = { path = "../my_sdk_sys" }
Плюсы и минусы
- Плюсы: чистота основного проекта, возможность переиспользовать обёртку в нескольких проектах.
- Минусы: нужно поддерживать отдельный крейт, и если библиотека обновится — придётся обновлять объявления вручную.
Этот подход особенно хорош, если вы планируете публиковать свой проект с закрытой бинарной зависимостью: обёртка распространяется как исходный код, а сама библиотека — отдельно.