#条件编译 #构建脚本 #构建工具 #autoconf #build-rs #library-discovery

build rsconf

Rust 的合理 autoconf。为测试系统头文件、库和符号而设计的 build.rs 辅助工具

5 个版本

0.2.2 2024 年 5 月 13 日
0.2.1 2024 年 5 月 13 日
0.1.2 2024 年 1 月 19 日
0.1.0 2023 年 5 月 18 日

#65 in 构建工具

Download history 442/week @ 2024-04-26 498/week @ 2024-05-03 686/week @ 2024-05-10 799/week @ 2024-05-17 753/week @ 2024-05-24 527/week @ 2024-05-31 388/week @ 2024-06-07 343/week @ 2024-06-14 589/week @ 2024-06-21 427/week @ 2024-06-28 420/week @ 2024-07-05 187/week @ 2024-07-12 294/week @ 2024-07-19 460/week @ 2024-07-26 674/week @ 2024-08-02 612/week @ 2024-08-09

每月 2,060 次下载

MIT/Apache

58KB
1K SLoC

rsconf

crates.io docs.rs

rsconf 是一个最小化、跨平台的构建时辅助工具,用于从 build.rs 测试目标系统,以启用条件编译(通过 rustc 的功能或 cfg 标志)并链接到正确的系统库,特别是用于 ffi 目的。它可以看作是 autoconf 的替代品,但更加合理和易于理解。

使用方法

rsconf 目前由两个独立的组件组成,其中 rsconf::Target 是测试目标系统是否存在库、符号、系统头文件、类型以及提取编译时常量的主要手段;而 rsconf crate 根命名空间中的独立顶级函数简化了对 Cargo 和 rustc 的操作(通常通过包装 println!("cargo:{...}") 消息 影响 crate 的构建方式)。

rsconf::Target 基于 the cc crate 构建,并使用它为目标平台获取一个有效工具链。可以通过 Target::new()(在内部初始化一个具有默认配置的 cc::Build 实例)来初始化 Target,或者您可以通过 Target::new_from() 将配置的自定义 cc::Build 传递给 Target,以便在所有后续测试中使用。

设计注意事项

在区分与交叉编译兼容的特征和不可兼容的特征时,特别注意。目前,大部分 rsconf::Target 测试都是针对交叉编译设计的,但用于从 C 标准库或系统头文件中提取编译时常量的函数家族(例如 Target::get_xxx_value())目前与交叉编译不兼容,因为它们需要执行为目标系统编译的测试可执行文件。有可能随着探索不同的方法,这些功能将来可能会成为交叉编译安全的。

rsconf 故意不提供提取非编译时常量值的设施(例如,Target::has_symbol() 可以用来检查外部库中是否存在符号,但 get_xxx_value() 函数不提供链接到系统库的设施),因为仅在运行时定义的任何东西都不能假设其在执行过程中不会改变其值,或在不同的系统上具有相同的值 - 正确的方法是测试符号(在库或标准 C 库中)的存在,然后在您的代码中将其声明为 extern "C" fn 或作为 #[no_mangle] 静态变量,并在运行时获取其值。

目前,rsconf 没有公开任何用于包发现的函数(与在默认系统路径或由 cc::Build 实例配置并传递给 Target::new_from() 的搜索路径中搜索头文件和库相反)。如果您需要此功能,建议您使用类似 pkg_config 的 crate 来查找库或头文件的路径,然后在使用这些路径配置 cc::Build 实例并将其传递给 Target::new_from() 之前。

使用示例

以下是一个示例,说明如何使用 rsconf 在您的 build.rs 构建脚本中检查多个库中的符号,然后使用这些信息从您的 crate 中有条件地编译代码。这里我们将测试低级 curses 库,并验证我们找到的库具有我们需要的符号,然后再使用 rust。

build.rs

use rsconf::{LinkType, Target};

fn find_curses(system: &Target) -> bool {
    // We need to try different library names depending on the platform
    for lib in [ "tinfo", "terminfo" ] {
        if !system.has_library(lib) {
            continue;
        }
        if system.has_symbol("cur_term", lib)
            && system.has_symbol("setupterm", lib)
        {
            // We found what we need, so make sure we link against it.
            rsconf::link_library(lib, LinkType::Default);
            return true;
        }
    }
    return false;
}

fn main() {
    let system = Target::new();
    if find_curses(&system) {
        rsconf::enable_cfg("curses");
    } else {
        rsconf::warn!("Unable to find a curses library!");
    }
}

然后在 src/main.rs

#[cfg(curses)]
extern "C" {
    static mut cur_term: *const libc::c_void;
    fn setupterm(term: *const libc::c_void, fd: libc::c_int, err: &mut libc::c_int);
}

/// A safe wrapper around the curses `setupterm()` API.
#[cfg(not(curses)]
fn setup_term() -> bool { return false; }

/// A safe wrapper around the curses `setupterm()` API.
#[cfg(curses)]
fn setup_term() -> bool {
    let mut error: i32 = 0;
    let result = unsafe {
        setupterm(std::ptr::null(), libc::STDOUT_FILENO, &mut error)
    };

    result == 0
}

请注意,实际上存在一些方便的方法可以显著减少上述的模板代码,但为了说明目的,使用了更详尽的 API。

许可证

rsconf 在 MIT 和 Apache 2.0 许可证下发布。所有其他权利均保留,版权所有 Mahmoud Al-Qudsi 2024。

依赖项

~205KB