Avatar ·

Как указать только бинарные зависимости (без исходников) в Cargo для Rust?

Всем привет! Я новичок в Rust и пытаюсь разобраться с системой сборки Cargo. У меня есть проект, который использует внешнюю библиотеку, предоставляющую только скомпилированный бинарный файл (например, `.so` или `.dll`). Исходные коды этой библиотеки недоступны — это проприетарный SDK. В моём `Cargo.toml` я прописал зависимость через `path`: ```toml [dependencies] my_sdk = { path = "/opt/vendor/sdk" } ``` Но Cargo выдаёт ошибку, что не может найти `Cargo.toml` в указанной папке. Я пробовал использовать `git` и `crates.io`, но там этой библиотеки нет. Как правильно указать Cargo, что зависимость — это уже скомпилированный бинарник, и исходники не нужны? Может быть, есть какой-то атрибут вроде `bin-only` или `prebuilt`? Я использую Rust 1.70.0 на Ubuntu. Буду благодарен за примеры кода и объяснение, как вообще в Rust работать с такими «бинарными» зависимостями. Заранее спасибо! P.S. Пробовал копировать `.so` файл в `target/debug`, но при запуске программа падает с `error while loading shared libraries`. Видимо, линковщик не находит библиотеку. <<>>

Прямая линковка через 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 файл во время выполнения. Есть три способа исправить:

  1. Установить библиотеку в системный путь (например, /usr/lib).
  2. Использовать rpath — добавьте в build.rs строку:
    println!("cargo:rustc-link-arg=-Wl,-rpath,/opt/vendor/sdk/lib");
  3. Установить переменную окружения 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" }

Плюсы и минусы

  • Плюсы: чистота основного проекта, возможность переиспользовать обёртку в нескольких проектах.
  • Минусы: нужно поддерживать отдельный крейт, и если библиотека обновится — придётся обновлять объявления вручную.

Этот подход особенно хорош, если вы планируете публиковать свой проект с закрытой бинарной зависимостью: обёртка распространяется как исходный код, а сама библиотека — отдельно.