本文將介紹如何使用cargo workspace來管理多個package,並透過實踐介紹workspace的一些基礎場景下的使用、配置方式。
在rust中編寫某些中小型專案時,我們通常不會將一個工程拆分為多個package,而是透過一個package下不同的目錄模組來實現模組拆分,儘管大部分場景下這種開發方式已經足夠,然而一旦專案膨脹或是需要遵循模組化的工程設計,我們就不得不將多個模組拆分為獨立的package來維護。維護多個package一般有兩種方式:1、將多個package拆分為不同的倉庫,獨立釋出crate;2、將多個package存放在同一倉庫下,透過cargo workspace來管理,本文主要介紹後者的使用方式。
基礎配置
假設我們編寫了一個rust應用。它分為兩個部分:
- 應用app本身(my_app)。
- 一個獨立的庫lib(my_lib)。這個獨立的庫可能是一個提取出來的工具庫,它被my_app專案所依賴。
我們首先建立一個空專案:
$ mkdir workspace-demo && cd workspace-demo
$ cargo init
該命令執行完成後,我們會在當前目錄下生成一個名為workspace-demo的目錄,並在該目錄下生成一個名為Cargo.toml的檔案,該檔案包含了當前工程的基本資訊,包括工程名、版本、依賴等:
接著,我們在專案根目錄執行如下命令,分別建立兩個package:
$ cargo init my_app --bin
$ cargo init my_lib --lib
執行完成以後,cargo幫助我們在專案根目錄下建立了兩個package:
並且,cargo貼心的幫助我們在專案根目錄下的Cargo.toml加入下這段配置:
+ workspace = { members = ["my_app", "my_lib"] }
這段配置意味著,我們剛剛建立的my_app
和my_lib
作為了的當前這個專案工作空間的成員(members)。
接下來,讓我們刪除專案根目錄下的src資料夾,然後使用命令(cargo build
)編譯專案下的兩個package。
執行命令後會發現一個報錯:
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]
欄位:
這個欄位的存在意味著根目錄包含的內容是一個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
,會發現新的報錯:
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]
欄位也一併移除。於是,現在的專案狀態如下:
最後,我們再次執行cargo build
,會發現編譯成功。
子package依賴配置
當然,目前我們僅僅是建立了兩個不相干的package。但是實際的場景下,my_app
會依賴my_lib
這個crate。為了達到這個目的,我們只需要在my_app
下的Cargo.toml按照如下方式來定義對my_lib
的依賴:
為了讓子package依賴到工作空間中其他的package,只需要提供一個檔案路徑即可,該路徑是相對於當前package的路徑。
workspace共享依賴
除了workspace內部之間的依賴以外,我們還可能面臨這樣的場景:my_app
和my_lib
都用到了一個相同的外部依賴庫(例如,serde庫)。為了讓這兩個庫都能依賴到。一種方式是將my_app
和my_lib
下的Cargo.toml都按如下方式定義:
這種方式雖然簡單,但是存在一個問題:如果我們將my_lib
的serde
升級為一個新的版本,那麼我們需要將my_app
下的serde
庫也升級為新的版本。隨著子package的增多,這樣的維護方式心智負擔會越來越大。那麼有沒有更優雅的方式呢?答案是肯定的。workspace為我們提供了依賴共享的能力,具體方式如下:
首先,我們在專案根目錄下Cargo.toml中增加一個名為[workspace.dependencies]
的欄位,並且在裡面定義serde
的依賴:
[workspace]
members = ["my_app", "my_lib"]
+ [workspace.dependencies]
+ serde = { version = "1.0.201" }
其次,修改my_app
和my_lib
下的Cargo.toml的[dependencies]
欄位中關於serde
庫的依賴,改為如下定義方式:
...
[dependencies]
- serde = { version = "1.0.201" }
+ serde = { workspace = true }
整體如下:
按照上述配置以後,my_app
和my_lib
不僅都依賴到了serde
庫,而且他們的版本始終保持了一致。如果我們將serde
升級為一個新的版本,那麼my_app
和my_lib
都會自動升級。
workspace還能共享什麼?
實際上,除了上述的依賴共享外,還有其他很多的屬性可以共享。例如,上述的my_app
和my_lib
都是各自在維護自己的版本:
有的場景下,我們希望它們的版本能夠保持一致。這個時候,我們同樣可以在根目錄下的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}
整體如下:
寫在最後
本文簡單介紹了rust的cargo workspace的使用方式。當然,本文主要是使用虛擬清單型別(virtual manifest)的工作空間,即,根目錄下Cargo.toml不指定任何package。當然,還有一種場景則是:根目錄下Cargo.toml可以指定當前目錄也是一個package包(通常是bin型別的可執行package),然後將該可執行package依賴的各種二方庫透過workspace來配置。本文就不再贅述這塊的內容,讀者可以自行嘗試。
參考
http://course.rs/cargo/reference/workspaces.html