#build-script #package-manager #cmake #build-dependencies #cc #integrating #io

build conan

Rust对C/C++包管理器conan(conan.io)的包装,简化构建脚本中的使用

8个版本

0.4.2 2023年11月2日
0.4.1 2023年11月1日
0.3.0 2022年10月17日
0.2.0 2020年6月2日
0.1.2 2019年5月25日

#48构建工具

Download history 822/week @ 2024-03-13 830/week @ 2024-03-20 190/week @ 2024-03-27 828/week @ 2024-04-03 892/week @ 2024-04-10 779/week @ 2024-04-17 726/week @ 2024-04-24 518/week @ 2024-05-01 561/week @ 2024-05-08 771/week @ 2024-05-15 865/week @ 2024-05-22 736/week @ 2024-05-29 996/week @ 2024-06-05 468/week @ 2024-06-12 660/week @ 2024-06-19 720/week @ 2024-06-26

2,998 每月下载量

MIT/Apache

52KB
1K SLoC

conan-rs

Rust对C/C++包管理器conan(conan.io)的包装,简化构建脚本中的使用

TLDR

将conan添加到构建依赖部分

cargo add conan --build

修改项目build.rs脚本以调用cargo并自动发出conan构建信息。建议使用从cargo目标信息派生的conan配置文件

注意:假设conan可执行文件为conan,除非设置了CONAN环境变量。

use std::path::Path;
use std::env;

use conan::*;

fn main() {
    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
    let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
    let conan_profile = format!("{}-{}", target_os, target_arch);

    let command = InstallCommandBuilder::new()
        .with_profile(&conan_profile)
        .build_policy(BuildPolicy::Missing)
        .with_option("sign", "True")
        .recipe_path(Path::new("conanfile.txt"))
        .build();

    if let Some(build_info) = command.generate() {
        println!("using conan build info");
        build_info.cargo_emit();
    }

    let build_comman = BuildCommandBuilder::new()
        .with_recipe_path(PathBuf::from("../../../conanfile.py"))
        .with_build_path(PathBuf::from("../../../build/"))
        .build();

    if let Some(exit_status) = build_comman.run() {
        println!("conan build exited with {}", exit_status);
    }
}

最简单的方法是在build.rs旁边添加conanfile.txt文件

[requires]
openssl/1.1.1l@devolutions/stable

要测试是否正确导入了conan包,运行cargo -vv build,并查找类似以下输出

[conan-test 0.1.0] using conan build info
[conan-test 0.1.0] cargo:rustc-link-search=native=/Users/mamoreau/.conan/data/openssl/1.1.1l/devolutions/stable/package/ce597277d61571523403b5b500bda70acd77cd8a/lib
[conan-test 0.1.0] cargo:rustc-link-lib=crypto
[conan-test 0.1.0] cargo:rustc-link-lib=ssl
[conan-test 0.1.0] cargo:include=/Users/mamoreau/.conan/data/openssl/1.1.1l/devolutions/stable/package/ce597277d61571523403b5b500bda70acd77cd8a/include
[conan-test 0.1.0] cargo:rerun-if-env-changed=CONAN

此示例conan配方可在此处找到,即使它不在公共conan仓库中。

文档

Conan安装

InstallCommand结构体表示"conan install"命令,便于在Rust项目中安装包和依赖关系管理。InstallCommandBuilder提供了一种流畅的API来构建InstallCommand

示例

use conan::{InstallCommandBuilder, BuildPolicy};
use std::path::Path;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let install_command = InstallCommandBuilder::new()
        .with_profile("default")
        .build_policy(BuildPolicy::Missing)
        .recipe_path(Path::new("conanfile.txt"))
        .output_dir(Path::new("output_directory"))
        .build();

    if install_command.generate().is_some() {
        println!("Packages installed successfully!");
    } else {
        println!("Failed to install packages.");
    }

    Ok(())
}

在这个例子中,InstallCommandBuilder 使用配置文件、构建策略、配方文件路径和输出目录配置了 Conan 安装命令。generate() 执行命令,在成功时返回 Some(BuildInfo),在失败时返回 None

Conan 构建

BuildCommand 结构体表示 "conan build" 命令,简化了 Rust 项目中 Conan 包的构建过程。BuildCommandBuilder 提供了一个流畅的 API 来构建 BuildCommand

示例

use conan::BuildCommandBuilder;
use std::path::PathBuf;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let build_command = BuildCommandBuilder::new()
        .with_recipe_path(PathBuf::from("conanfile.py"))
        .with_build_path(PathBuf::from("build"))
        .should_configure(true)
        .should_build(true)
        .should_install(true)
        .build();

    match build_command.run() {
        Some(status) if status.success() => println!("Build succeeded!"),
        _ => println!("Build failed."),
    }

    Ok(())
}

在这个例子中,使用 BuildCommandBuilder 配置 Conan 构建命令的路径和选项。run() 执行命令,在成功时返回 Some(ExitStatus),在失败时返回 None

Conan 包

PackageCommand 结构体表示 "conan package" 命令,用于创建包。 PackageCommandBuilder 提供了一个流畅的 API 来构建 PackageCommand

ConanPackage 结构体提供了管理生成 C++ 库的 Conan 包的功能,并帮助将这些库与 Rust 链接起来。

示例用法

use conan::{PackageCommandBuilder, PackageComman, ConanPackage};
use std::path::PathBuf;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let package_command = PackageCommandBuilder::new()
        .with_recipe_path(PathBuf::from("conanfile.py"))
        .build();

    if let Some(status) = package_command.run() {
        if !status.success() {
            println!("Package command failed.");
            return Ok(());
        }
    }

    let conan_package = ConanPackage::new(PathBuf::from("./package/"));
    conan_package.emit_cargo_libs_linkage(PathBuf::from("lib"))?;

    Ok(())
}

用例:将 Rust 集成到遗留的 c++/conan1 代码库中

将 Rust 集成到遗留的 C++ 代码库中可以是一种战略性的举措,以利用 Rust 的内存安全特性,同时保持现有的 C++ 功能。在本指南中,我们将探讨如何使用 conan-rsautocxx 将 Rust 集成到遗留的 C++/Conan 代码库中。

现有的 C++ Conan 代码库结构

您的现有 C++ 代码库(带有 Conan 和 CMake)可能如下所示

.
├── build
│   ├── bin
│   │   └── target_bin
│   ├── lib
│   │   ├── lib1.a
│   │   ├── lib2.a
│   │   ├── lib3.so
│   │   ├── lib4.so
│   │   ├── ...
│   │   └── libn.a
├── CMakeLists.txt
├── conanfile.py
├── include
│   └── ...
├── profiles
│   ├── ...
├── src
│   ├── target_bin
│   │   ├── ...
│   ├── lib1
│   │   ├── CMakeLists.txt
│   │   ├── include
│   │   │   └── ...
│   │   ├── src
│   │   │   └── ...
│   ├── ...
│   ├── libn
│   │   ├── CMakeLists.txt
│   │   ├── include
│   │   │   └── ...
│   │   ├── src
│   │   │   └── ...

请确保构建后构建目录看起来像这样(您的配置可能不同)

├── build
│   ├── bin
│   │   └── target_bin
│   ├── lib
│   │   ├── lib1.a
│   │   ├── lib2.a
│   │   ├── lib3.so
│   │   ├── lib4.so
│   │   ├── ...
│   │   └── libn.a

此外,您的 conanfile 中的 package() 方法应该将库和相关包含文件组织在一个类似下面的配置中

package
├── conaninfo.txt
├── conanmanifest.txt
├── include
└── lib
├── lib1.a
├── ...
└── libn.so

创建 Rust "桥接" 包

在代码库中创建一个 Rust 库包,作为 C++ 和 Rust 代码之间的 "桥接"。

.
├── build.rs
├── Cargo.lock
├── Cargo.toml
└── src
├── lib.rs
└── main.rs

设置依赖项

安装 conan-rsautocxx

cargo add conan-rs autocxx --build
cargo add autocxx

设置构建脚本

在您的包的构建脚本(build.rs)中,配置集成

use conan::{
    BuildCommandBuilder, BuildPolicy, ConanPackage, InstallCommandBuilder, PackageCommandBuilder,
};
use std::env;
use std::path::{Path, PathBuf};
use std::process;

fn main() {
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=../../path/to/your/conanfile.py");
    println!("cargo:rerun-if-changed=../../path/to/your/build/directory");

    let out_dir = env::var("OUT_DIR").map(PathBuf::from).unwrap_or_else(|_| {
        eprintln!("Error: OUT_DIR environment variable is not set");
        process::exit(1);
    });

    println!("OUT_DIR: {:?}", out_dir);

    let conan_profile = env::var("CONAN_PROFILE").unwrap_or_else(|_| "default".to_string());
    let install_command = InstallCommandBuilder::new()
        .with_profile(&conan_profile)
        .with_remote("your_remote")
        .build_policy(BuildPolicy::Missing)
        .with_profile("../../path/to/your/conan/profile")
        .recipe_path(Path::new("../../path/to/your/conanfile.py"))
        .output_dir(Path::new("../../path/to/your/build/directory"))
        .with_options(&["option1=True", "option2=True"])
        .update_check()
        .build();

    if let Some(build_info) = install_command.generate() {
        println!("using conan build info");
        build_info.cargo_emit();
    } else {
        eprintln!("Error: failed to run conan install");
        process::exit(1);
    }

    BuildCommandBuilder::new()
        .with_recipe_path(PathBuf::from("../../path/to/your/conanfile.py"))
        .with_build_path(PathBuf::from("../../path/to/your/build/directory"))
        .build()
        .run()
        .unwrap_or_else(|| {
            eprintln!("Error: Unable to run conan build");
            process::exit(1);
        });

    let package_command = PackageCommandBuilder::new()
        .with_recipe_path(PathBuf::from("../../path/to/your/conanfile.py"))
        .with_build_path(PathBuf::from("../../path/to/your/build/directory"))
        .with_package_path(out_dir.clone())
        .build();

    if let Some(exit_status) = package_command.run() {
        println!("conan package exited with {}", exit_status);
    }

    let conan_package = ConanPackage::new(out_dir.clone());
    if let Err(err) = conan_package.emit_cargo_libs_linkage("lib".into()) {
        eprintln!("Error: Unable to emit cargo linkage: {:?}", err);
        process::exit(1);
    }

    let include_path = out_dir.join("include");
    let mut builder = autocxx_build::Builder::new("src/lib.rs", &[include_path])
        .build()
        .unwrap_or_else(|err| {
            eprintln!("Error: Unable to generate bindings: {:?}", err);
            process::exit(1);
        });

    builder.flag_if_supported("-std=c++14").compile("foo_bar");
    println!("cargo:rerun-if-changed=src/main.rs");
}

在 Rust 中使用 C++ 库

最后,在 lib.rs 中使用 C++ 库

use autocxx::prelude::*;

include_cpp! {
    #include "path/to/header.h"
    safety!(unsafe_ffi)
    generate!("FunctionFromCpp")
}

pub fn use_cpp_function() {
    let result = ffi::FunctionFromCpp();
    // Use result as needed
}

依赖项

~3.5–5.5MB
~103K SLoC