前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Rust工作空间(workspace)实践

Rust工作空间(workspace)实践

作者头像
w4ngzhen
发布2024-05-10 10:27:12
570
发布2024-05-10 10:27:12
举报
文章被收录于专栏:编译思想编译思想

本文将介绍如何使用cargo workspace来管理多个package,并通过实践介绍workspace的一些基础场景下的使用、配置方式。

在rust中编写某些中小型项目时,我们通常不会将一个工程拆分为多个package,而是通过一个package下不同的目录模块来实现模块拆分,尽管大部分场景下这种开发方式已经足够,然而一旦项目膨胀或是需要遵循模块化的工程设计,我们就不得不将多个模块拆分为独立的package来维护。维护多个package一般有两种方式:1、将多个package拆分为不同的仓库,独立发布crate;2、将多个package存放在同一仓库下,通过cargo workspace来管理,本文主要介绍后者的使用方式。

基础配置

假设我们编写了一个rust应用。它分为两个部分:

  1. 应用app本身(my_app)。
  2. 一个独立的库lib(my_lib)。这个独立的库可能是一个提取出来的工具库,它被my_app项目所依赖。

我们首先创建一个空项目:

代码语言:javascript
复制
$ mkdir workspace-demo && cd workspace-demo
$ cargo init

该命令执行完成后,我们会在当前目录下生成一个名为workspace-demo的目录,并在该目录下生成一个名为Cargo.toml的文件,该文件包含了当前工程的基本信息,包括工程名、版本、依赖等:

010-basic-project
010-basic-project

接着,我们在项目根目录执行如下命令,分别创建两个package:

代码语言:javascript
复制
$ cargo init my_app --bin
$ cargo init my_lib --lib

执行完成以后,cargo帮助我们在项目根目录下创建了两个package:

020-init-2-packages
020-init-2-packages

并且,cargo贴心的帮助我们在项目根目录下的Cargo.toml加入下这段配置:

代码语言:javascript
复制
+ workspace = { members = ["my_app", "my_lib"] }

这段配置意味着,我们刚刚创建的my_appmy_lib作为了的当前这个项目工作空间的成员(members)。

接下来,让我们删除项目根目录下的src文件夹,然后使用命令(cargo build)编译项目下的两个package。

执行命令后会发现一个报错:

030-no-package-err
030-no-package-err
代码语言:javascript
复制
Caused by:
  no targets specified in the manifest
  either src/lib.rs, src/main.rs, a [lib] section, or [[bin]] section must be present

这段报错指出:项目根目录下没有src/lib.rs,或src/main.rs,或者是在Cargo.toml中没有[[bin]][[lib]]字段指定当前根目录下的package。

报错原因在于:首先,项目根目录下的Cargo.toml存在[package]字段:

040-package-field
040-package-field

这个字段的存在意味着根目录包含的内容是一个package包,那么这个目录需要符合rust的package结构:目录下存在src/main.rs(bin类型包),或存在src/lib.rs(lib类型包),或是通过[[bin]][[lib]]字段配置指定该package的入口代码文件。

由于我们已经将src目录删除了,也没有额外的配置,所以rust认为我们的目录结构不合法,于是出现上述报错。

所以,为了避免上述的报错,我们将这个[package]字段内容移除。

代码语言:javascript
复制
workspace = { members = ["my_app", "my_lib"] }

- [package]
- name = "workspace-demo"
- version = "0.1.0"
- edition = "2021"

[dependencies]

再次进行cargo build,会发现新的报错:

050-dep-section-err
050-dep-section-err
代码语言:javascript
复制
Caused by:
  this virtual manifest specifies a `dependencies` section, which is not allowed

这段报错指出:不允许在虚拟清单类型的工作空间中存在dependencies字段。

这里我们需要明白什么是virtual manifest?根据rust圣经提到的

若一个 Cargo.toml 有 [workspace] 但是没有 [package] 部分,则它是虚拟清单类型的工作空间。

这种场景下,我们根目录下的Cargo.toml完全作为整个工作空间下子crate的管理文件,本身并不包含package包。

在本例中,我们希望整个项目下,所有的package都存放到crates目录下,而根目录下不需要放任何的src文件。所以,我们也需要将根目录下的Cargo.toml中的[dependencies]字段也一并移除。于是,现在的项目状态如下:

060-simple-virtual-manifest
060-simple-virtual-manifest

最后,我们再次执行cargo build,会发现编译成功。

子package依赖配置

当然,目前我们仅仅是创建了两个不相干的package。但是实际的场景下,my_app会依赖my_lib这个crate。为了达到这个目的,我们只需要在my_app下的Cargo.toml按照如下方式来定义对my_lib的依赖:

070-workspace-lib-dep-path
070-workspace-lib-dep-path

为了让子package依赖到工作空间中其他的package,只需要提供一个文件路径即可,该路径是相对于当前package的路径。

workspace共享依赖

除了workspace内部之间的依赖以外,我们还可能面临这样的场景:my_appmy_lib都用到了一个相同的外部依赖库(例如,serde库)。为了让这两个库都能依赖到。一种方式是将my_appmy_lib下的Cargo.toml都按如下方式定义:

080-simple-dep-serde
080-simple-dep-serde

这种方式虽然简单,但是存在一个问题:如果我们将my_libserde升级为一个新的版本,那么我们需要将my_app下的serde库也升级为新的版本。随着子package的增多,这样的维护方式心智负担会越来越大。那么有没有更优雅的方式呢?答案是肯定的。workspace为我们提供了依赖共享的能力,具体方式如下:

首先,我们在项目根目录下Cargo.toml中增加一个名为[workspace.dependencies]的字段,并且在里面定义serde的依赖:

代码语言:javascript
复制
[workspace]
members = ["my_app", "my_lib"]
+ [workspace.dependencies]
+ serde = { version = "1.0.201" }

其次,修改my_appmy_lib下的Cargo.toml的[dependencies]字段中关于serde库的依赖,改为如下定义方式:

代码语言:javascript
复制
...
[dependencies]
- serde = { version = "1.0.201" }
+ serde = { workspace = true }

整体如下:

090-workspace-dep
090-workspace-dep

按照上述配置以后,my_appmy_lib不仅都依赖到了serde库,而且他们的版本始终保持了一致。如果我们将serde升级为一个新的版本,那么my_appmy_lib都会自动升级。

workspace还能共享什么?

实际上,除了上述的依赖共享外,还有其他很多的属性可以共享。例如,上述的my_appmy_lib都是各自在维护自己的版本:

independent-version
independent-version

有的场景下,我们希望它们的版本能够保持一致。这个时候,我们同样可以在根目录下的Cargo.toml定义工作空间的版本信息:

代码语言:javascript
复制
[workspace]
members = ["my_app", "my_lib"]

+ [workspace.package]
+ version = "0.1.0"
+ edition = "2021"
+ license = "MIT OR Apache-2.0"
+ authors = ["w4ngzhen"]

[workspace.dependencies]
serde = { version = "1.0.201" }

然后,在各自的package下的Cargo.toml中,将相关字段做如下修改:

代码语言:javascript
复制
[package]
name = "my_lib"

- version = "0.1.0"
+ version = { workspace = true}
+ # 或
+ # version.workspace = true
+ edition = { workspace = true}
+ authors = {workspace = true}

整体如下:

share-package-config
share-package-config

写在最后

本文简单介绍了rust的cargo workspace的使用方式。当然,本文主要是使用虚拟清单类型(virtual manifest)的工作空间,即,根目录下Cargo.toml不指定任何package。当然,还有一种场景则是:根目录下Cargo.toml可以指定当前目录也是一个package包(通常是bin类型的可执行package),然后将该可执行package依赖的各种二方库通过workspace来配置。本文就不再赘述这块的内容,读者可以自行尝试。

参考

http://course.rs/cargo/reference/workspaces.html

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-05-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基础配置
  • 子package依赖配置
  • workspace共享依赖
  • workspace还能共享什么?
  • 写在最后
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档