Rust工作空間(workspace)實踐

w4ngzhen發表於2024-05-10

本文將介紹如何使用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專案所依賴。

我們首先建立一個空專案:

$ mkdir workspace-demo && cd workspace-demo
$ cargo init

該命令執行完成後,我們會在當前目錄下生成一個名為workspace-demo的目錄,並在該目錄下生成一個名為Cargo.toml的檔案,該檔案包含了當前工程的基本資訊,包括工程名、版本、依賴等:

010-basic-project

接著,我們在專案根目錄執行如下命令,分別建立兩個package:

$ cargo init my_app --bin
$ cargo init my_lib --lib

執行完成以後,cargo幫助我們在專案根目錄下建立了兩個package:

020-init-2-packages

並且,cargo貼心的幫助我們在專案根目錄下的Cargo.toml加入下這段配置:

+ workspace = { members = ["my_app", "my_lib"] }

這段配置意味著,我們剛剛建立的my_appmy_lib作為了的當前這個專案工作空間的成員(members)。

接下來,讓我們刪除專案根目錄下的src資料夾,然後使用命令(cargo build)編譯專案下的兩個package。

執行命令後會發現一個報錯:

030-no-package-err

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

這個欄位的存在意味著根目錄包含的內容是一個package包,那麼這個目錄需要符合rust的package結構:目錄下存在src/main.rs(bin型別包),或存在src/lib.rs(lib型別包),或是透過[[bin]][[lib]]欄位配置指定該package的入口程式碼檔案。

由於我們已經將src目錄刪除了,也沒有額外的配置,所以rust認為我們的目錄結構不合法,於是出現上述報錯。

所以,為了避免上述的報錯,我們將這個[package]欄位內容移除。

workspace = { members = ["my_app", "my_lib"] }

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

[dependencies]

再次進行cargo build,會發現新的報錯:

050-dep-section-err

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

最後,我們再次執行cargo build,會發現編譯成功。

子package依賴配置

當然,目前我們僅僅是建立了兩個不相干的package。但是實際的場景下,my_app會依賴my_lib這個crate。為了達到這個目的,我們只需要在my_app下的Cargo.toml按照如下方式來定義對my_lib的依賴:

070-workspace-lib-dep-path

為了讓子package依賴到工作空間中其他的package,只需要提供一個檔案路徑即可,該路徑是相對於當前package的路徑。

workspace共享依賴

除了workspace內部之間的依賴以外,我們還可能面臨這樣的場景:my_appmy_lib都用到了一個相同的外部依賴庫(例如,serde庫)。為了讓這兩個庫都能依賴到。一種方式是將my_appmy_lib下的Cargo.toml都按如下方式定義:

080-simple-dep-serde

這種方式雖然簡單,但是存在一個問題:如果我們將my_libserde升級為一個新的版本,那麼我們需要將my_app下的serde庫也升級為新的版本。隨著子package的增多,這樣的維護方式心智負擔會越來越大。那麼有沒有更優雅的方式呢?答案是肯定的。workspace為我們提供了依賴共享的能力,具體方式如下:

首先,我們在專案根目錄下Cargo.toml中增加一個名為[workspace.dependencies]的欄位,並且在裡面定義serde的依賴:

[workspace]
members = ["my_app", "my_lib"]
+ [workspace.dependencies]
+ serde = { version = "1.0.201" }

其次,修改my_appmy_lib下的Cargo.toml的[dependencies]欄位中關於serde庫的依賴,改為如下定義方式:

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

整體如下:

090-workspace-dep

按照上述配置以後,my_appmy_lib不僅都依賴到了serde庫,而且他們的版本始終保持了一致。如果我們將serde升級為一個新的版本,那麼my_appmy_lib都會自動升級。

workspace還能共享什麼?

實際上,除了上述的依賴共享外,還有其他很多的屬性可以共享。例如,上述的my_appmy_lib都是各自在維護自己的版本:

independent-version

有的場景下,我們希望它們的版本能夠保持一致。這個時候,我們同樣可以在根目錄下的Cargo.toml定義工作空間的版本資訊:

[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中,將相關欄位做如下修改:

[package]
name = "my_lib"

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

整體如下:

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

相關文章