Rust cargo.toml指定自定义路径用于C链接器和编译器

12

由于对货物设置和大量文档的无知,我遇到了一些问题。

cargo.toml 文件目前为:

[package]
name = "hello"
version = "0.1.0"
authors = ["PC4\\Author"]

[dependencies]
sdl2 = { version = "0.34.1", features = ["bundled", "static-link"] }
SDL2依赖项已经编译,但实际上它正在使用Visual Studio。我想要做的是在编译板条箱依赖项时使用另一个文件夹中的自定义编译器。

目前为止,我发现要使用C编译器,我需要创建一个名为cc-rs的文件,它基本上创建了一个可执行文件,用于构建C二进制文件(一种类似于CMAKE的工具),我猜SDL2正在使用它,所以实际上我需要找到如何将参数传递给SDL2 crate,对吧? - CoffeDeveloper
你可以尝试使用 cc-rs 来构建库,自己编写一个构建脚本。 - Chase
Sdl2太复杂了,如果不使用它们的crate和构建脚本,就无法构建它,除非我花费大量时间,最终才能修补它。 - CoffeDeveloper
所以你只想使用另一个编译器?还是需要使用传递到编译器的自定义参数? - Chase
你是在使用Windows系统,对吗?你打算使用mingw的gcc编译器吗? - Chase
显示剩余2条评论
1个回答

10
你可以在构建依赖时指定Rust使用gcc编译器,只要你已经正确安装了针对mingw的Rust。为了确保你的Rust正确配置为mingw,请使用这个线程。请记住,默认情况下,Windows版Rust将被配置为MSVC,而不是mingw。
以下步骤最初在官方rust-sdl2文档中提到。
完成上述步骤后,您需要一个构建脚本来链接库到依赖项。但首先,您需要这些库。从官方libsdl网站下载mingw特定的库。
现在,你需要将这些文件放在与cargo.toml相同的文件夹中,按正确的顺序排列。
SDL2-devel-2.0.x-mingw.tar.gz\SDL2-2.0.x\i686-w64-mingw32\bin       ->  gnu-mingw\dll\32
SDL2-devel-2.0.x-mingw.tar.gz\SDL2-2.0.x\x86_64-w64-mingw32\bin     ->  gnu-mingw\dll\64
SDL2-devel-2.0.x-mingw.tar.gz\SDL2-2.0.x\i686-w64-mingw32\lib       ->  gnu-mingw\lib\32
SDL2-devel-2.0.x-mingw.tar.gz\SDL2-2.0.x\x86_64-w64-mingw32\lib     ->  gnu-mingw\lib\64

gnu-mingw 应该是与 cargo.toml 文件在同一目录下的文件夹。

现在,您需要构建脚本本身,创建一个名为 build.rs 的文件,并将其放置在 cargo.toml[package] 中。

build = "build.rs"

有关构建脚本的更多信息,请参见此处

这是脚本内容-

use std::env;
use std::path::PathBuf;

fn main() {
    let target = env::var("TARGET").unwrap();
    if target.contains("pc-windows") {
        let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        let mut lib_dir = manifest_dir.clone();
        let mut dll_dir = manifest_dir.clone();
        lib_dir.push("gnu-mingw");
        dll_dir.push("gnu-mingw");
        lib_dir.push("lib");
        dll_dir.push("dll");
        if target.contains("x86_64") {
            lib_dir.push("64");
            dll_dir.push("64");
        }
        else {
            lib_dir.push("32");
            dll_dir.push("32");
        }
        println!("cargo:rustc-link-search=all={}", lib_dir.display());
        for entry in std::fs::read_dir(dll_dir).expect("Can't read DLL dir")  {
            let entry_path = entry.expect("Invalid fs entry").path();
            let file_name_result = entry_path.file_name();
            let mut new_file_path = manifest_dir.clone();
            if let Some(file_name) = file_name_result {
                let file_name = file_name.to_str().unwrap();
                if file_name.ends_with(".dll") {
                    new_file_path.push(file_name);
                    std::fs::copy(&entry_path, new_file_path.as_path()).expect("Can't copy from DLL dir");
                }
            }
        }
    }
}

注意: 这个意味着故意省略了 MSVC 特定的内容。

现在,在你的构建配置,也就是 cargo.toml 中的 [build] 部分,你需要加入以下内容-

target = "x86_64-pc-windows-gnu"

可以在 cargo build 文档 中找到可用目标的列表。

更多有关构建配置的信息可以在 配置文档 中找到。

作为额外的奖励,如果你想使用其他编译器(而不是 gcc),你只需要确保必要的库与可执行文件在同一个目录中,并将其放入 [target.TARGET_NAME] 中即可。

linker = "path\\to\\c\\linker"
ar = "path\\to\\c\\ar"

TARGET_NAME替换为您选择的目标三元组。

编辑:根据OP的请求,提供如何将CMake与rust结合使用的信息。

使用CMake与rust是可能的,但是编译和构建第三方依赖项几乎肯定需要一个自定义的构建脚本,该脚本将能够替换依赖项自己的构建脚本。

为了说明这一点,让我们使用CMake和rust制作一个自定义的简单C静态库。

以下步骤最初在此flames of code博客中提到

首先,您需要一个C项目,现在不需要太多,只需要一个.c文件,您应该将.c文件放在名为libfoo(或任何您的库可能被称为)的目录中。现在,您可以将此 libfoo 目录放在与您的 rust 项目相同的目录中或任何您喜欢的位置,但请记住路径。

继续在.c文件中放入一个简单的“hello world”程序-

#include <stdio.h>

void testcall(float value)
{
    printf("Hello, world from C! Value passed: %f\n",value);
}

(注意:该函数不应为主函数,因为我们正在构建一个静态库)

现在我们需要在相同的目录中添加一个CMakelists.txt文件-

cmake_minimum_required(VERSION 3.0)
project(LibFoo C)

add_library(foo STATIC foo.c)

install(TARGETS foo DESTINATION .)

这是一个相当简单的脚本,虽然最后一行很重要 - 它确保库的目标是 . - 我们稍后需要从Rust中找到此库。

所以现在,文件结构可能如下所示-

.
├── Cargo.lock
├── Cargo.toml
├── libfoo
│   ├── CMakeLists.txt
│   └── foo.c
└── src
    └── main.rs

现在关于 Rust 的部分,你需要为项目编写一个构建脚本,并且需要使用构建依赖项 cmake
将构建脚本添加到 cargo.toml 中。
[package]
build="build.rs"

还有依赖关系 -

[build-dependencies]
cmake = "0.1.31"

现在在你的build.rs中,你需要调用cmake
extern crate cmake;
use cmake::Config;

fn main()
{
    let dst = Config::new("libfoo").build();       

    println!("cargo:rustc-link-search=native={}", dst.display());
    println!("cargo:rustc-link-lib=static=foo");    
}

.build() 部分很简单,但为什么要加那些 println! 呢?

这些代码将必要的命令写入 stdout,以便 cargo 可以搜索库并进行链接。 这就是你的 c 库名称和目标起作用的地方。

现在,您只需执行 cargo run,它将构建 C 库以及 Rust 项目!

您还可以在详细模式下运行它(-vv),以查看 C 库生成的详细输出。

现在,您所要做的就是从 main.rs 中调用该库-

#[link(name="foo", kind="static")]
extern { 
    // this is rustified prototype of the function from our C library
    fn testcall(v: f32); 
}

fn main() {
    println!("Hello, world from Rust!");

    // calling the function from foo library
    unsafe { 
        testcall(3.14159); 
    };
}

很简单,但是博客的作者留了一条注释给外部函数-
注意,这个原型需要从C原型手动转换到Rust原型。对于操作基本值类型的简单函数而言,这件事很简单,但是当涉及更复杂的数据类型时可能会更加困难。
这使我们回到SDL2箱体,编译它所需的C库,链接它们,然后构建箱体本身肯定需要大量调整 - 但我希望这指引您走向正确的方向。

谢谢你的出色回答,真的整合了大量有用的信息和努力。你拥有我的感激之情 :) - CoffeDeveloper
但这也意味着,Cargo 不能更改 C 编译器,您需要一个不同的“发行版”,在这种情况下,Cargo 仍然远未达到像 Cmake 那样具备某些任务的构建系统的能力。(即使我使用不同的编译器构建某些东西,我仍然需要将其链接到可执行文件,这就像使用 C 构建一样) - CoffeDeveloper
1
@CoffeDeveloper 如果你的意思是指定 C 编译器,你可以通过设置 CC(用于 C)和 CXX(用于 C++)环境变量来实现。请注意,原始答案并没有讨论关于 CMake 的解决方案,因此你必须指定 target 并配置很多东西。使用 CMake,你几乎不需要做什么。 - Chase
是的和不是的,例如,如果我覆盖这两个环境变量,sdl2 crate 仍然会自动使用 msvc 进行构建。要使用另一个编译器进行构建,我需要做一些额外的工作(在 Windows 上)。 - CoffeDeveloper
1
@CoffeDeveloper 我非常确定sdl2使用自己的构建系统,就像我之前提到的那样 - 因此环境变量没有任何作用。你是否用CMake替换了它的所有构建系统?这是一个相当繁重的任务,几乎肯定需要你自己fork crate。如果你不想依赖他们自己的构建系统,你唯一的选择就是使用我的原始答案并链接正确的编译器/链接器。(不使用cmake) - Chase

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接