http://wiki.jikexueyuan.com/project/rust-primer/cargo-detailed-cfg/cargo-detailed-cfg.html
cargo简介
曾几何时,对于使用惯了C/C++语言的猿们来说,项目代码的组织与管理绝对是一场噩梦。为了解决C/C++项目的管理问题,猿神们想尽了各种办法,开发出了各种五花八门的项目管理工具,从一开始的automake到后来的cmake、qmake等等,但结果并不如人意,往往是解决了一些问题,却引入了更多的问题,C/C++猿们经常会陷入在掌握语言本身的同时,还要掌握复杂的构建工具语法的窘境。无独有偶,java的项目代码组织与管理工具ant和maven也存在同样的问题。复杂的项目管理配置参数,往往让猿们不知所措。
作为一门现代语言,rust自然要摒弃石器时代项目代码管理的方法和手段。rust项目组为各位猿提供了超级大杀器cargo,以解决项目代码管理所带来的干扰和困惑。用过node.js的猿们,应该对node.js中的神器npm、grunt、gulp等工具印象深刻。作为新一代静态语言中的翘楚,rust官方参考了现有语言管理工具的优点,于是就产生了cargo。
言而总之,作为rust的代码组织管理工具,cargo提供了一系列的工具,从项目的建立、构建到测试、运行直至部署,为rust项目的管理提供尽可能完整的手段。同时,与rust语言及其编译器rustc本身的各种特性紧密结合,可以说既是语言本身的知心爱人,又是rust猿们的贴心小棉袄,谁用谁知道。 废话就不多说了,直接上例子和各种高清无马图。
cargo入门
首先,当然还是废话,要使用cargo,自然首先要安装cargo。安装cargo有三种方法,前两种方法请参见rust的安装方法,因为cargo工具是官方正统出身,当然包含在官方的分发包中。第三种方法即从cargo项目的源码仓库进行构建。Oh,My God。的确是废话。
好了,假设各位已经安装好了cargo,大家和我一起学一下起手式。当然了,猿的世界,起手式一般都千篇一律——那就是hello world大法。 在终端中输入
$ cargo new hello_world --bin
上述命令使用cargo new在当前目录下新建了基于cargo项目管理的rust项目,项目名称为hello_world,–bin表示该项目将生成可执行文件。具体生成的项目目录结构如下:
$ cd hello_world
$ tree .
.
├── Cargo.toml
└── src
└── main.rs
1 directory, 2 files
大家可以在终端中输入上述命令,敲出回车键之后即可看到上述结果,或者直接去编辑器或文件管理器中去观察即可。 打开main.rs文件,可以看到,cargo new命令为我们自动生成了hello_world运行所必须的所有代码:
fn main() {
println!("Hello, world!");
}
好了,心急的猿们可能已经迫不及待的脱裤子了,好吧,我们先来构建并看看cargo有多神奇,在终端中输入:
$ cargo build
稍等片刻,cargo会自动为我们构建好高清应用所需的一切,对于这个起手式来说,缓冲不会超过5秒,12秒88的选手要憋住了。
$ cargo run
Running target/debug/hello_world
Hello, world!
看到了什么,看到了什么,吓尿了有木有,吓尿了有木有。好了,cargo就是这么简单。
当然了,说cargo美,并不仅仅是简单这么简单,cargo虽然简单,但是很强大。有多么强大??可以说,基本上rust开发管理中所需的手段,cargo都有。很小很强大,既强又有节操,不带马,学习曲线几乎为零。
基于cargo的rust项目组织结构
这次不说废话了,先上高清无马图:
cargo项目组织结构
对上述cargo默认的项目结构解释如下:
cargo.toml和cargo.lock文件总是位于项目根目录下。
源代码位于src目录下。
默认的库入口文件是src/lib.rs。
默认的可执行程序入口文件是src/main.rs。
其他可选的可执行文件位于src/bin/*.rs(这里每一个rs文件均对应一个可执行文件)。
外部测试源代码文件位于tests目录下。
示例程序源代码文件位于examples。
基准测试源代码文件位于benches目录下。
好了,大家一定谨记这些默认规则,最好按照这种模式来组织自己的rust项目。
cargo.toml和cargo.lock
cargo.toml和cargo.lock是cargo项目代码管理的核心两个文件,cargo工具的所有活动均基于这两个文件。
cargo.toml是cargo特有的项目数据描述文件,对于猿们而言,cargo.toml文件存储了项目的所有信息,它直接面向rust猿,猿们如果想让自己的rust项目能够按照期望的方式进行构建、测试和运行,那么,必须按照合理的方式构建’cargo.toml’。
而cargo.lock文件则不直接面向猿,猿们也不需要直接去修改这个文件。lock文件是cargo工具根据同一项目的toml文件生成的项目依赖详细清单文件,所以我们一般不用不管他,只需要对着cargo.toml文件撸就行了。
[package]
name = "hello_world"
version = "0.1.0"
authors = ["fuying"]
[dependencies]
toml文件是由诸如[package]或[dependencies]这样的段落组成,每一个段落又由多个字段组成,这些段落和字段就描述了项目组织的基本信息,例如上述toml文件中的[package]段落描述了hello_world项目本身的一些信息,包括项目名称(对应于name字段)、项目版本(对应于version字段)、作者列表(对应于authors字段)等;[dependencies]段落描述了hello_world项目的依赖项目有哪些。
下面我们来看看toml描述文件中常用段落和字段的意义。
package段落
[package]段落描述了软件开发者对本项目的各种元数据描述信息,例如[name]字段定义了项目的名称,[version]字段定义了项目的当前版本,[authors]定义了该项目的所有作者,当然,[package]段落不仅仅包含这些字段,[package]段落的其他可选字段详见cargo参数配置章节。
定义项目依赖
使用cargo工具的最大优势就在于,能够对该项目的各种依赖项进行方便、统一和灵活的管理。这也是使用cargo对rust 的项目进行管理的重要目标之一。在cargo的toml文件描述中,主要通过各种依赖段落来描述该项目的各种依赖项。toml中常用的依赖段落包括一下几种:
基于rust官方仓库crates.io,通过版本说明来描述:
基于项目源代码的git仓库地址,通过URL来描述:
基于本地项目的绝对路径或者相对路径,通过类Unix模式的路径来描述: 这三种形式具体写法如下:
[dependencies]
typemap = "0.3"
plugin = "0.2*"
hammer = { version = "0.5.0"}
color = { git = "https://github.com/bjz/color-rs" }
geometry = { path = "crates/geometry" }
上述例子中,2-4行为方法一的写法,第5行为方法二的写法,第6行为方法三的写法。 这三种写法各有用处,如果项目需要使用crates.io官方仓库来管理项目依赖项,推荐使用第一种方法。如果项目开发者更倾向于使用git仓库中最新的源码,可以使用方法二。方法二也经常用于当官方仓库的依赖项编译不通过时的备选方案。方法三主要用于源代码位于本地的依赖项。
定义集成测试用例
cargo另一个重要的功能,即将软件开发过程中必要且非常重要的测试环节进行集成,并通过代码属性声明或者toml文件描述来对测试进行管理。其中,单元测试主要通过在项目代码的测试代码部分前用#[test]属性来描述,而集成测试,则一般都会通过toml文件中的[[test]]段落进行描述。 例如,假设集成测试文件均位于tests文件夹下,则toml可以这样来写:
[[test]]
name = "testinit"
path = "tests/testinit.rs"
[[test]]
name = "testtime"
path = "tests/testtime.rs"
上述例子中,name字段定义了集成测试的名称,path字段定义了集成测试文件相对于本toml文件的路径。 看看,定义集成测试就是如此简单。 需要注意的是:
如果没有在Cargo.toml里定义集成测试的入口,那么tests目录(不包括子目录)下的每个rs文件被当作集成测试入口.
如果在Cargo.toml里定义了集成测试入口,那么定义的那些rs就是入口,不再默认指定任何集成测试入口.
定义项目示例和可执行程序
上面我们介绍了cargo项目管理中常用的三个功能,还有两个经常使用的功能:example用例的描述以及bin用例的描述。其描述方法和test用例描述方法类似。不过,这时候段落名称’[[test]]‘分别替换为:’[[example]]‘或者’[[bin]]’。例如:
[[example]]
name = "timeout"
path = "examples/timeout.rs"
[[bin]]
name = "bin1"
path = "bin/bin1.rs"
对于’[[example]]‘和’[[bin]]'段落中声明的examples和bins,需要通过’cargo run --example NAME’或者’cargo run --bin NAME’来运行,其中NAME对应于你在name字段中定义的名称。
构建、清理、更新以及安装
领会了toml描述文件的写法,是一个重要的方面。另一个重要的方面,就是cargo工具本身为我们程序猿提供的各种好用的工具。如果大家感兴趣,自己在终端中输入’cargo --help’查看即可。其中开发时最常用的命令就是’cargo build’,用于构建项目。此外,'cargo clean’命令可以清理target文件夹中的所有内容;'cargo update’根据toml描述文件重新检索并更新各种依赖项的信息,并写入lock文件,例如依赖项版本的更新变化等等;'cargo install’可用于实际的生产部署。这些命令在实际的开发部署中均是非常有用的。
筒子们好,我们又见面了。之前第5章,我们一起探讨了cargo的一些常用的基本技能。通过第5章的学习,大家基本能解决日常项目开发中遇到的大多数问题。但实际上,cargo提供给我们所使用的功能不仅限于此。我只想说一个字:cargo很好很强大,而且远比你想象的强大。 本章将深入探讨cargo的一些细节问题,这包括以下几个方面:
基于语义化版本的项目版本声明与管理
cargo的toml描述文件配置字段详细参考
基于语义化版本的项目版本声明与管理
我们在使用toml描述文件对项目进行配置时,经常会遇到项目版本声明及管理的问题,比如:
[package]
name = "libevent_sys"
version = "0.1.0"
[dependencies]
libc = "0.2"
这里package段落中的version字段的值,以及dependencies段落中的libc字段的值,这些值的写法,都涉及到语义化版本控制的问题。语义化版本控制是用一组简单的规则及条件来约束版本号的配置和增长。这些规则是根据(但不局限于)已经被各种封闭、开放源码软件所广泛使用的惯例所设计。简单来说,语义化版本控制遵循下面这些规则:
版本格式:主版本号.次版本号.修订号,版本号递增规则如下:
主版本号:当你做了不兼容的 API 修改,
次版本号:当你做了向下兼容的功能性新增,
修订号:当你做了向下兼容的问题修正。
先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。
关于语义化版本控制的具体细节问题,大家可以参考这里,我不再赘述。
cargo的toml描述文件配置字段详细参考
[package]段落
啥也不多说了,直接上例子,大家注意我在例子中的中文解释,个人觉得这样比较一目了然:
[package]
# 软件包名称,如果需要在别的地方引用此软件包,请用此名称。
name = "hello_world"
# 当前版本号,这里遵循semver标准,也就是语义化版本控制标准。
version = "0.1.0" # the current version, obeying semver
# 软件所有作者列表
authors = ["you@example.com"]
# 非常有用的一个字段,如果要自定义自己的构建工作流,
# 尤其是要调用外部工具来构建其他本地语言(C、C++、D等)开发的软件包时。
# 这时,自定义的构建流程可以使用rust语言,写在"build.rs"文件中。
build = "build.rs"
# 显式声明软件包文件夹内哪些文件被排除在项目的构建流程之外,
# 哪些文件包含在项目的构建流程中
exclude = ["build/**/*.o", "doc/**/*.html"]
include = ["src/**/*", "Cargo.toml"]
# 当软件包在向公共仓库发布时出现错误时,使能此字段可以阻止此错误。
publish = false
# 关于软件包的一个简短介绍。
description = "..."
# 下面这些字段标明了软件包仓库的更多信息
documentation = "..."
homepage = "..."
repository = "..."
# 顾名思义,此字段指向的文件就是传说中的ReadMe,
# 并且,此文件的内容最终会保存在注册表数据库中。
readme = "..."
# 用于分类和检索的关键词。
keywords = ["...", "..."]
# 软件包的许可证,必须是cargo仓库已列出的已知的标准许可证。
license = "..."
# 软件包的非标许可证书对应的文件路径。
license-file = "..."
依赖的详细配置
最直接的方式在之前第五章探讨过,这里不在赘述,例如这样:
[dependencies]
hammer = "0.5.0"
color = "> 0.6.0, < 0.8.0"
与平台相关的依赖定义格式不变,不同的是需要定义在[target]字段下。例如:
# 注意,此处的cfg可以使用not、any、all等操作符任意组合键值对。
# 并且此用法仅支持cargo 0.9.0(rust 1.8.0)以上版本。
# 如果是windows平台,则需要此依赖。
[target.'cfg(windows)'.dependencies]
winhttp = "0.4.0"
[target.'cfg(unix)'.dependencies]
openssl = "1.0.1"
#如果是32位平台,则需要此依赖。
[target.'cfg(target_pointer_width = "32")'.dependencies]
native = { path = "native/i686" }
[target.'cfg(target_pointer_width = "64")'.dependencies]
native = { path = "native/i686" }
# 另一种写法就是列出平台的全称描述
[target.x86_64-pc-windows-gnu.dependencies]
winhttp = "0.4.0"
[target.i686-unknown-linux-gnu.dependencies]
openssl = "1.0.1"
# 如果使用自定义平台,请将自定义平台文件的完整路径用双引号包含
[target."x86_64/windows.json".dependencies]
winhttp = "0.4.0"
[target."i686/linux.json".dependencies]
openssl = "1.0.1"
native = { path = "native/i686" }
openssl = "1.0.1"
native = { path = "native/x86_64" }
# [dev-dependencies]段落的格式等同于[dependencies]段落,
# 不同之处在于,[dependencies]段落声明的依赖用于构建软件包,
# 而[dev-dependencies]段落声明的依赖仅用于构建测试和性能评估。
# 此外,[dev-dependencies]段落声明的依赖不会传递给其他依赖本软件包的项目
[dev-dependencies]
iron = "0.2"
自定义编译器调用方式模板详细参数
cargo内置五种编译器调用模板,分别为dev、release、test、bench、doc,分别用于定义不同类型生成目标时的编译器参数,如果我们自己想改变这些编译模板,可以自己定义相应字段的值,例如(注意:下述例子中列出的值均为此模板字段对应的系统默认值):
# 开发模板, 对应`cargo build`命令
[profile.dev]
opt-level = 0 # 控制编译器的 --opt-level 参数,也就是优化参数
debug = true # 控制编译器是否开启 `-g` 参数
rpath = false # 控制编译器的 `-C rpath` 参数
lto = false # 控制`-C lto` 参数,此参数影响可执行文件和静态库的生成,
debug-assertions = true # 控制调试断言是否开启
codegen-units = 1 # 控制编译器的 `-C codegen-units` 参数。注意,当`lto = true`时,此字段值被忽略
# 发布模板, 对应`cargo build --release`命令
[profile.release]
opt-level = 3
debug = false
rpath = false
lto = false
debug-assertions = false
codegen-units = 1
# 测试模板,对应`cargo test`命令
[profile.test]
opt-level = 0
debug = true
rpath = false
lto = false
debug-assertions = true
codegen-units = 1
# 性能评估模板,对应`cargo bench`命令
[profile.bench]
opt-level = 3
debug = false
rpath = false
lto = false
debug-assertions = false
codegen-units = 1
# 文档模板,对应`cargo doc`命令
[profile.doc]
opt-level = 0
debug = true
rpath = false
lto = false
debug-assertions = true
codegen-units = 1
需要注意的是,当调用编译器时,只有位于调用最顶层的软件包的模板文件有效,其他的子软件包或者依赖软件包的模板定义将被顶层软件包的模板覆盖。
[features]段落
[features]段落中的字段被用于条件编译选项或者是可选依赖。例如:
[package]
name = "awesome"
[features]
# 此字段设置了可选依赖的默认选择列表,
# 注意这里的"session"并非一个软件包名称,
# 而是另一个featrue字段session
default = ["jquery", "uglifier", "session"]
# 类似这样的值为空的feature一般用于条件编译,
# 类似于`#[cfg(feature = "go-faster")]`。
go-faster = []
# 此feature依赖于bcrypt软件包,
# 这样封装的好处是未来可以对secure-password此feature增加可选项目。
secure-password = ["bcrypt"]
# 此处的session字段导入了cookie软件包中的feature段落中的session字段
session = ["cookie/session"]
[dependencies]
# 必要的依赖
cookie = "1.2.0"
oauth = "1.1.0"
route-recognizer = "=2.1.0"
# 可选依赖
jquery = { version = "1.0.2", optional = true }
uglifier = { version = "1.5.3", optional = true }
bcrypt = { version = "*", optional = true }
civet = { version = "*", optional = true }
如果其他软件包要依赖使用上述awesome软件包,可以在其描述文件中这样写:
[dependencies.awesome]
version = "1.3.5"
default-features = false # 禁用awesome 的默认features
features = ["secure-password", "civet"] # 使用此处列举的各项features
使用features时需要遵循以下规则:
feature名称在本描述文件中不能与出现的软件包名称冲突
除了default feature,其他所有的features均是可选的
features不能相互循环包含
开发依赖包不能包含在内
features组只能依赖于可选软件包
features的一个重要用途就是,当开发者需要对软件包进行最终的发布时,在进行构建时可以声明暴露给终端用户的features,这可以通过下述命令实现:
$ cargo build --release --features "shumway pdf"
关于测试
当运行cargo test命令时,cargo将会按做以下事情:
编译并运行软件包源代码中被#[cfg(test)] 所标志的单元测试
编译并运行文档测试
编译并运行集成测试
编译examples
配置构建目标
所有的诸如[[bin]], [lib], [[bench]], [[test]]以及 [[example]]等字段,均提供了类似的配置,以说明构建目标应该怎样被构建。例如(下述例子中[lib]段落中各字段值均为默认值):
[lib]
# 库名称,默认与项目名称相同
name = "foo"
# 此选项仅用于[lib]段落,其决定构建目标的构建方式,
# 可以取dylib, rlib, staticlib 三种值之一,表示生成动态库、r库或者静态库。
crate-type = ["dylib"]
# path字段声明了此构建目标相对于cargo.toml文件的相对路径
path = "src/lib.rs"
# 单元测试开关选项
test = true
# 文档测试开关选项
doctest = true
# 性能评估开关选项
bench = true
# 文档生成开关选项
doc = true
# 是否构建为编译器插件的开关选项
plugin = false
# 如果设置为false,`cargo test`将会忽略传递给rustc的--test参数。
harness = true