使用Workspace组织复杂Rust项目

发布时间:2024-10-17 08:45
最后更新:2024-10-18 09:43
所属分类:
Rust

一个功能复杂的项目不会把所有代码都编写在一个项目里,有很多可以共享,或者与主项目隶属关系不大的项目,往往会独立成一个新的项目。但是这一系列相互独立又相互联系的项目在管理上就存在了一定的困难。Workspace(工作区)是Rust提供的一种方便的项目组织方式,允许将多个相关的包和项目组织在一起。通过Workspace,这些项目可以在一个项目中的共享依赖和配置文件,而且还可以完成对多个crate的同时管理。

工作区的目录组织形式

工作区也只是一个普通的目录,其中包括着其他的几个子项目。工作区目录中只有一个Cargo.toml,但是没有src这个源代码目录。

以下是工作区到的目录组织形式。

Rust工作区目录组织形式
Rust工作区目录组织形式

创建顶层Cargo.toml

工作区目录可以直接手动创建,然后在其中创建一个空白的Cargo.toml文件即可。工作区的Cargo.toml文件中定义的主要是工作区的子crate组成和由各个子crate共享的依赖库。例如创建上面示例中的工作区结构,工作区的Cargo.toml内容可以入以下所示。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[workspace]
members = [
  "crate_a",
  "crate_b",
  "crate_c",
]
default-members = ["crate_a"]
resolver = "2"

[workspace.dependencies]
serde = "1.0"
tokio = { version = "1", features = ["full"]}

Workspace的Cargo.toml中不需要写项目的信息,而是使用[workspace]项目来声明工作区的组成,其中members声明了工作区中的各个子crate,default-member声明了在没有特定指定所要编译的子crate时,默认使用哪一个或者哪几个子crate。members中列举的内容是各个子crate的目录名称,不可以是子crate的Cargo.tomlpackage.name的内容。

[worksapce.dependencies]就很好理解了,这里声明的是所有子crate共享的依赖库内容。注意在子crate的Cargo.tomldependencies中必须显式使用library = { workspace = true }来显式引入工作区中定义的共享依赖。

子crate可以直接使用cargo来创建,可执行的crate可以使用cargo new --bin,提供支持功能的可以使用cargo new --lib创建一个库。在工作区目录里使用cargo new创建的子crate会自动被添加进工作区的Cargo.toml

工作区的Cargo.toml中还有一个resolver配置项,这个配置项用来指示使用哪种依赖解析器,它会控制和影响Cargo如何解析和处理依赖关系。设定为"1"表示使用Cargo 1.51之前的旧版本依赖解析器,"2"表示使用Cargo 1.52以后的新版本解析器。关于两种解析器的区别可以参考Cargo文档,对于比较新的项目,推荐使用新版本依赖解析器。

工作区之内的依赖实际上是分为了共享依赖和子项目自己的依赖,这两种依赖的管理是相同的,只是运行cargo命令的目录位置不同。在工作区目录下使用cargo命令可以管理工作区中的共享依赖,在各个子crate目录中的可以管理各个子crate自身的依赖。

工作区内的子crate之间还可以相互依赖,例如上面实例中如果crate_a依赖crate_bcrate_c,那么可以在crate_aCargo.toml中这样配置。

1
2
3
4
5
6
7
8
[package]
name = "crate_a"
version = "0.1.0"
edition = "2021"

[dependencies]
crate_b = { path = "../crate_b" }
crate_c = { path = "../crate_c" }

在配置子crate之间的依赖关系时,需要注意不要配置子crate之间的循环依赖。

工作区Cargo.toml中其他可用的设置。

在工作区的Cargo.toml(工作区的root配置)里,除了上面示例中使用的几个配置项,还可以使用以下配置内容。

  • workspace.resolver,设定要使用的依赖解析器。
  • workspace.members,要包含在工作区里的子crate。
  • workspace.exclude,要中工作区中排除的子crate。
  • workspace.default-members,在没有选定特定子crate时的默认操作目标。
  • workspace.package,所有子crate默认继承的包(Package)说明,子crate中可以使用{key}.workspace=true来继承这些值。可设定内容包括:
    • author
    • description
    • edition
    • homepage
    • keywords
    • license-file,文件路径为相对于工作区根目录的相对路径
    • readme,文件路径为相对于工作区根目录的相对路径
    • rust-version
  • workspace.dependencies,设定所有子crate共享的依赖库。子crate中可以使用{ workspace=true, features = [] }来使用工作区中定义的依赖并重新设定所需的共享依赖库的功能。
  • workspace.lints,设定可以被子crate继承的项目Lint配置,子crate里可以使用lints.workspace=true来继承使用。
  • patch,用来设定依赖库的替代,可以使用git指定依赖库的其他fork,或者使用path指定其在本地磁盘上的目录。

将子项目配置为动态链接库

在默认情况下,使用cargo new --lib创建的库项目在编译的时候是采用静态链接的,也就是会被编译到依赖这个库的项目的二进制文件里。

将依赖库编译为动态链接库可以进一步降低最终打包二进制文件的大小,还可以简化发布文件的更新,优化程序启动时间并提升性能。

要将一个Rust项目编译为动态链接库,可以直接在这个项目的Cargo.toml中增加以下配置。

1
2
[lib]
crate-type = ["dylib"]

这样Rust就会将这个项目编译为一个动态链接库。

Cargo.toml中的lib.crate-type除了可以设置为dylib以外,还可以设置为以下这些值。

  • lib,用于生成标准Rust库,可以被其他Rust crate静态链接使用,是Rust库项目的默认配置。
  • rlib,用于生成Rust特有的编译库文件,可以用于静态链接Rust库并供其他Rust crate使用。
  • dylib,用于生成Rust特有的动态链接库,只能用于Rust crate之间的动态链接。
  • cdylib,用于生成C兼容的动态链接库,可以用于与其他语言(如C、C++、Python等)的交互。
  • staticlib,用于生成C兼容的静态链接库。
  • proc-macro,用于生成Rust专用的过程宏库。

lib.crate-type可以被指定多个值,这样Rust会按照要求同时生成所需要的库文件。

将Tauri项目目录转变为工作区

使用create-tauri-app命令行创建的Tauri 2.0项目,实际上是以前端项目为基础的。项目的根目录中也基本上都是前端使用配置文件,只有在src-tauri目录中才是Rust的项目目录。

要将一个Tauri项目目录转变成工作区也是很简单的,只需要在项目根目录中创建一个Cargo.toml文件,然后在其中按照上文的说明定义[workspace]的配置即可。例如:

1
2
3
4
5
[workspace]
members = [
  "src-tauri"
]
default-members = ["src-tauri"]

如果需要给Tauri项目增加协同开发的依赖库,那么可以在Tauri项目目录下建立一个与src-tauri平行的目录来放置库项目。

工作区的编译

在工作区目录下使用cargo build命令,可以直接构建工作区中的所有crate。

如果需要构建指定的某一个crate,比如crate_a,可以使用命令cargo build -p crate_a

运行的时候也是一样,可以通过命令cargo run -p crate_a来运行指定的crate。如果使用default-members指定了默认crate,那么可以直接使用cargo run命令,非默认成员是不会被启动的。


索引标签
Rust
工作区
项目结构
Tauri