生存在不可避免地走向高雅。--王小波
大家好,我是柒八九。
前言
在上一篇Rust 开发命令行工具(上)中咱们从我的项目配置
/参数获取
/解析文件内容
/处理错误信息
/信息输入解决
等方面。一步一步写进去能够构建出在本地,兼容谬误提醒,并且有很好的输入模式的本地搜寻工具。
以防大家忘记,咱们把最终的代码贴到上面。
use anyhow::{Context, Result};use clap::Parser;use indicatif::ProgressBar;use std::fs::File;use std::io::{self, BufRead, Write};use std::path::PathBuf;use std::thread;use std::time::Duration;#[derive(Parser)]struct Cli { /// 要查找的模式 pattern: String, /// 要读取的文件的门路 path: PathBuf,}fn main() -> Result<()> { let args = Cli::parse(); // 关上文件并创立一个 BufReader 来逐行读取 let file = File::open(&args.path).with_context(|| format!("无奈关上文件 {:?}", &args.path))?; let reader = io::BufReader::new(file); let stdout = io::stdout(); let stdout_lock = stdout.lock(); let mut handle = io::BufWriter::new(stdout_lock); let pb = ProgressBar::new(100); for line in reader.lines() { do_hard_work(); pb.println(format!("[+] 查找到了 #{:?}项", line)); pb.inc(1); let line = line.with_context(|| "无奈读取行")?; if line.contains(&args.pattern) { writeln!(handle, "{}", line)?; } } Ok(())}fn do_hard_work() { thread::sleep(Duration::from_millis(250));}
然而,作为一个性能齐备的我的项目,咱们还须要做单元测试/集成测试和打包公布。所以,明天咱们就从这两面来持续欠缺咱们的Rust
我的项目。
你能所学到的知识点
- 前置知识点
- 代码测试
- 打包并公布 Rust 我的项目
好了,天不早了,干点闲事哇。
前置知识点
前置知识点,只是做一个概念的介绍,不会做深度解释。因为,这些概念在上面文章中会有呈现,为了让行文更加的顺畅,所以将本该在文内的概念解释放到后面来。如果大家对这些概念相熟,能够间接疏忽
同时,因为浏览我文章的群体有很多,所以有些知识点可能我视之若瑰宝,尔视只如草芥,弃之如敝履。以下知识点,请酌情应用。
单元测试 VS 黑盒测试
单元测试和黑盒测试(也叫集成测试)是两种不同的软件测试办法,它们旨在检查和验证软件的品质和性能,但它们的关注点、办法和指标有所不同。
单元测试(Unit Testing)
- 焦点:
单元测试
关注测试软件的最小性能单元
,通常是一个函数、办法或模块
。它的指标是验证这个性能单元是否依照预期工作,而不思考其余组件。 - 测试者:通常由开发人员编写和执行。开发人员编写测试用例,用于查看函数、办法或模块的各种输出和边界条件。
- 可见性:单元测试通常具备对代码的白盒拜访权限,测试者能够拜访和查看被测试单元的外部实现细节,以编写更准确的测试用例。
- 指标:次要指标是验证单元的正确性,确保它们依照标准执行,并解决各种输出状况。
黑盒测试(Black Box Testing)
- 焦点:
黑盒测试
关注测试整个软件系统的性能,而不思考外部实现。它的指标是验证零碎是否依照标准的需要和性能标准工作。 - 测试者:能够由测试工程师或独立的测试团队执行。测试者不须要理解零碎的外部实现,只需关注零碎的输出和输入。
- 可见性:黑盒测试没有对系统的外部实现细节的理解。测试者只能拜访零碎的内部接口和性能。
- 指标:次要指标是验证零碎是否满足其标准和需要,以及是否在各种输出和条件下体现失常。
在理论我的项目中,通常须要同时进行单元测试
和黑盒测试
,以确保软件在各个层面上都具备高质量和可靠性。
Rust trait
在Rust
中,trait
是一种非凡的类型,它定义了某些类型的共享行为。trait
提供了一种形式来形象和共享方法,相似于其余编程语言中的接口
。通过实现trait
,你能够为自定义类型定义通用的行为,使其可能与其余类型一起工作,从而进步了Rust
代码的可复用性和灵活性。
上面咱们简略解释一下trait
的应用
定义
trait
:咱们能够应用
trait
关键字来定义一个trait
,而后在其中申明办法签名。trait Printable { fn print(&self);}
这个示例定义了一个名为
Printable
的trait
,它要求实现该trait
的类型必须蕴含一个名为print
的办法。实现
trait
:要使类型实现一个
trait
,咱们须要在类型的定义中应用impl
块来实现trait
中申明的办法。struct MyStruct { data: i32,}impl Printable for MyStruct { fn print(&self) { println!("Data: {}", self.data); }}
在这个示例中,
MyStruct
类型实现了Printable
trait
,提供了print
办法的具体实现。应用
trait
:一旦你实现了一个
trait
,咱们能够在任何实现了该trait
的类型上调用trait
中定义的办法。例如:let my_instance = MyStruct { data: 42 };my_instance.print();
在这里,咱们创立了一个
MyStruct
的实例并调用了print
办法。
总的来说,trait
是Rust
中用于实现形象和共享行为的弱小工具,它有助于编写可复用的代码,同时确保类型的安全性和一致性。通过正当应用trait
,咱们能够编写更清晰、更灵便和更可保护的Rust
代码。
更具体的内容,能够参考咱们之前写的Rust 泛型、trait 与生命周期
Rust的模块零碎
Rust
的<span style="font-weight:800;color:#FFA500;font-size:18px">{模块零碎|the module system}</span>,包含:
- 包(
Packages
):Cargo
的一个性能,它容许你构建、测试和分享crate
。Crates
:一个模块的树形构造,它造成了库或二进制我的项目。- 模块(
Modules
)和use
: 容许你管制作用域和门路的公有性。- 门路(
path
):一个命名例如构造体、函数或模块等项的形式
包和 crate
- 包(
package
) 是提供一系列性能的一个或者多个crate
。一个包会蕴含有一个Cargo.toml
文件,论述如何去构建这些crate
。 crate
是一个二进制项或者库。crate root
是一个源文件,Rust
编译器以它为起始点,并形成你的crate
的根模块。
包中所蕴含的内容由几条规定来确立。
- 一个包中至少只能蕴含一个<span style="font-weight:800;color:#FFA500;font-size:18px">{库 crate|library crate}</span>;
- 包中能够蕴含任意多个<span style="font-weight:800;color:#FFA500;font-size:18px">{二进制 crate|binary crate}</span>;
- 包中至多蕴含一个
crate
,无论是库的还是二进制的。
输出命令 cargo new xxx
:当咱们输出了这条命令,Cargo
会给咱们的包
创立一个 Cargo.toml
文件。查看 Cargo.toml
的内容,会发现并没有提到 src/main.rs
,因为 Cargo
遵循的一个约定:
src/main.rs
就是一个与包同名的<span style="font-weight:800;color:#FFA500;font-size:18px">{二进制 crate|binary crate}</span> 的crate
根。- 同样的,
Cargo
晓得如果包目录中蕴含src/lib.rs
,则包带有与其同名的<span style="font-weight:800;color:#FFA500;font-size:18px">{库 crate|library crate}</span>,且src/lib.rs
是crate
根。
crate
根文件将由 Cargo
传递给 rustc
来理论构建库或者二进制我的项目。
如果一个包同时含有 src/main.rs
和 src/lib.rs
,则它有两个 crate
:一个库和一个二进制项,且名字都与包雷同。
通过将文件放在src/bin
目录下,一个包能够领有多个二进制crate
:每个src/bin
下的文件都会被编译成一个独立的<span style="font-weight:800;color:#FFA500;font-size:18px">{二进制 crate|binary crate}</span>。
一个 crate
会将一个作用域内的相干性能分组到一起,使得该性能能够很不便地在多个我的项目之间共享
。
对于这块的内容,能够参考之前咱们写的Rust之包、Crate和模块
crates.io
是个啥?
crates.io
是 Rust
编程语言社区的官网包治理和散发平台。它相似于其余编程语言中的包管理器,如 Python
的 PyPI
、JavaScript
的 npm
,用于帮忙 Rust
开发者分享、公布和获取 Rust
代码库(也称为 "crates
")。
以下是 crates.io
的一些要害特点和性能:
- 包管理器:
crates.io
提供了一个地方存储库,用于托管Rust crates
。开发者能够应用cargo
,Rust
的包管理工具,轻松地下载、装置和治理这些crates
。 - 包公布:任何
Rust
开发者都能够将本人的Rust
代码库公布到crates.io
上,供其他人应用。这使得代码共享和开源社区单干更加容易。 - 版本控制:每个
crate
都有本人的版本号,容许开发者指定应用特定版本的crate
。这有助于确保代码的稳定性和可靠性。 - 依赖治理:
crates.io
容许crate
之间建设依赖关系,开发者能够在本人的我的项目中引入其余crates
作为依赖项,从而疾速构建功能强大的应用程序。 - 搜寻和浏览:
crates.io
提供了一个易于应用的网站,容许开发者搜寻、浏览和查找他们须要的Rust crates
。网站还提供了无关每个crate
的详细信息、文档和示例代码。 - 社区驱动:
crates.io
是由Rust
社区保护和反对的,任何人都能够为平台的倒退和改良做出奉献。
总之,crates.io
是 Rust
生态系统的外围组成部分,它使 Rust
开发更加便捷,促成了 Rust
社区的增长和分享代码的文化。开发者能够在下面找到各种各样的 Rust crates
,以减速他们的我的项目开发。
2. 代码测试
为了确保咱们的程序依照咱们的冀望工作,最理智的做法是对其进行测试。
一种简略的办法是编写一个README
文件,形容咱们的程序应该执行的操作。当咱们筹备公布新版本时,通过README
能够形容咱们程序的性能和行为。与此同时,咱们还能够通过写下程序应该如何应答谬误输出来让咱们的程序变的更加谨严。
自动化测试
在Rust
中,#[test]
是一个属性(attribute
),用于标记测试函数。Rust
内置了一个测试框架,能够应用这个属性来定义和运行测试。
以下是应用 #[test]
的根本步骤:
- 首先,确保咱们的
Rust
我的项目是一个可测试的我的项目。通常,Rust
我的项目的测试代码寄存在一个名为tests
的目录中,或者在咱们的代码中应用条件编译来辨别测试代码和生产代码。它容许构建零碎发现这些函数并将其作为测试运行,验证它们不会触发panic
。 创立一个测试函数并标记为
#[test]
。测试函数必须返回()
(unit类型),并且通常不带参数。#[test]fn test_example() { // 在这里编写测试代码}
在测试函数中编写测试代码,包含调用咱们要测试的函数,并应用断言来查看函数的输入是否与预期值匹配。咱们能够应用规范库中的
assert!
宏或其余测试断言宏来进行断言。#[test]fn test_addition() { assert_eq!(2 + 2, 4);}#[test]fn test_subtraction() { assert!(5 - 3 > 0);}
运行测试。能够应用
Rust
的测试运行器工具来执行测试。常见的测试命令是cargo test
,它会主动查找和运行我的项目中的所有测试函数。在我的项目根目录下运行以下命令:cargo test
- 测试运行后果会显示在终端中。胜利的测试将显示为
ok
,失败的测试将显示为fail
,并提供失败的详细信息,包含测试函数的名称和失败的断言。咱们能够依据这些信息来调试和修复代码。 如果须要更具体的输入,能够应用
--verbose
标记运行测试cargo test --verbose
咱们应该最终失去相似以下的输入:
通过#[test]
咱们能够测试咱们想测试的外围代码,然而,作为一个CLI
通常不仅仅是一个函数,它须要更多的人机交互,例如须要解决用户输出、读取文件和编写输入等,咱们不可预知的参数和行为。
单元测试
有两种互补的办法来测试性能:
- 单元测试(
unit tests
):测试构建残缺应用程序的小单元。 - 黑盒测试(
black box tests
)或集成测试(integration tests
):测试最终应用程序的“内部”。
让咱们先从单元测试开始。
决定去远方,须要一个目的地,咱们想要测试哪些货色,咱们就须要晓得咱们的程序性能是啥!总的来说,f789
应该打印出与给定模式匹配的行。因而,让咱们为这个编写单元测试:咱们心愿确保咱们最重要的逻辑局部无效,并且咱们心愿以不依赖于咱们四周的任何设置代码(例如解决CLI参数等)的形式来执行此操作。
回到咱们的f789
的第一个实现,咱们在main
函数中增加了这个代码块:
// ...for line in content.lines() { if line.contains(&args.pattern) { println!("{}", line); }}
下面的代码是不容易进行单元测试的。首先,它在main
函数中,因而咱们不能轻松地调用它。所以,咱们须要将它移出main
函数,将这段代码移入一个函数中:
fn find_matches(content: &str, pattern: &str) { for line in content.lines() { if line.contains(pattern) { println!("{}", line); } }}
当初咱们能够在测试中调用这个函数,查看它的输入是什么:
#[test]fn find_a_match() { find_matches("front\n789", "789"); assert_eq!( // 省略了局部代码
目前,find_matches
通过stdout
将内容间接打印到了终端。咱们并不能轻松地在测试中捕捉这个信息,并且它是不可调试的。
咱们须要以某种形式捕捉输入。侥幸的是:Rust
的规范库提供了一些解决I/O
的形式,咱们能够应用其中一个称为std::io::Write
的trait
,它可用于咱们能够写入的货色,包含字符串,还有stdout
。
有了Wirte
的加持,让咱们更改咱们的函数以承受第三个参数。它应该是实现了Write
的任何类型。这样,咱们就能够在测试中提供一个简略的字符串,并对其进行断言。以下是咱们编写的改良版的find_matches
版本:
fn find_matches(content: &str, pattern: &str, mut writer: impl std::io::Write) { for line in content.lines() { if line.contains(pattern) { writeln!(writer, "{}", line); } }}
新参数是mut writer
,也就是说writer
是可变(mutable
)的。它的类型是impl std::io::Write
,咱们能够将其解读为实现了Write trait的任何类型的占位符。还要留神,咱们用writeln!(writer, …)
替换了之前应用的println!(…)
。println!
与writeln!
的工作形式雷同,但始终应用规范输入。
当初咱们能够测试输入:
#[test]fn find_a_match() { let mut result = Vec::new(); find_matches("front\n789", "789", &mut result); assert_eq!(result, b"789\n");}
要在咱们的利用程序代码中应用它,咱们必须更改main
中对find_matches
的调用,通过将&mut std::io::stdout()
作为第三个参数增加。
fn main() -> Result<()> { let args = Cli::parse(); let content = std::fs::read_to_string(&args.path) .with_context(|| format!("无奈读取文件 `{}`", args.path.display()))?; find_matches(&content, &args.pattern, &mut std::io::stdout()); Ok(())}
留神:因为stdout
须要字节(而不是字符串),咱们应用std::io::Write
而不是std::fmt::Write
。因而,在咱们的测试中,咱们给出一个空向量(vector
)作为writer
(其类型将被推断为Vec<u8>
),在assert_eq!
中,咱们应用b"foo"
。(b前缀将其转换为字节字符串文字,因而其类型将为&[u8]
,而不是&str
)。
咱们来看最终被革新后的代码。
use anyhow::{Context, Result};use clap::Parser;use std::path::PathBuf;#[derive(Parser)]struct Cli { /// 要查找的模式 pattern: String, /// 要读取的文件的门路 path: PathBuf,}fn main() -> Result<()> { let args = Cli::parse(); let content = std::fs::read_to_string(&args.path) .with_context(|| format!("无奈读取文件`{}`", args.path.display()))?; find_matches(&content, &args.pattern, &mut std::io::stdout()); Ok(())}fn find_matches(content: &str, pattern: &str, mut writer: impl std::io::Write) { #[allow(unused_must_use)] for line in content.lines() { if line.contains(pattern) { writeln!(writer, "{}", line); } }}#[test]fn find_a_match() { let mut result = Vec::new(); find_matches("front\n789", "789", &mut result); assert_eq!(result, b"789\n");}
应用cargo test
运行下面的代码,运行后果如下:
将代码拆分为库(library
)和二进制(binary
)
到目前为止,咱们把所有代码都放在了src/main.rs
文件中。这意味着咱们以后的我的项目生成一个独自的二进制文件。然而咱们也能够将咱们的代码作为一个库提供,办法如下:
- 将
find_matches
函数放入一个新的src/lib.rs
文件中。 - 在
fn
后面加上pub
(这样它就是pub fn find_matches
),以使其成为咱们库的用户能够拜访的内容。 - 从
src/main.rs
中删除find_matches
。 - 在
fn main
中,在调用find_matches
之前加上f789::
,这样它当初是f789::find_matches(…)
。这意味着它应用了咱们刚刚编写的库中的函数!
咱们能够在之前应用find_matches
中央做一个革新,并且性能也不会受影响。
fn main() -> Result<()> { // .... f789::find_matches(&content, &args.pattern, &mut std::io::stdout()); //....}#[test]fn find_a_match() { //.... f789::find_matches("front\n789", "789", &mut result); // ...}
黑盒测试
到目前为止,咱们测试的次要发力点都是业务逻辑层面,这业务逻辑次要集中在find_matches
函数中。
然而,有很多代码咱们没有测试:也就是咱们须要对外界(人机交互)局部做测试解决。设想一下,如果咱们编写了main
函数,然而意外地留下了一个硬编码的字符串,而不是应用用户提供的门路参数,会产生什么状况。咱们也应该为这些写测试!(这种级别的测试通常称为黑盒测试或零碎测试)。
从实质上讲,咱们依然是在编写函数并应用#[test]
进行正文。然而,咱们会把这些测试代码搁置到新目录中:tests/cli.rs
。(依照约定,cargo
将在tests/
目录中查找集成测试)
回顾一下,f789
是一个在文件中搜寻字符串的小工具。咱们曾经测试了咱们能够找到一个匹配项。让咱们思考一下咱们还能够测试的其余性能。
- 文件不存在时会产生什么?
- 当没有匹配项时输入是什么?
- 当咱们遗记一个(或两个)参数时,咱们的程序是否会以谬误退出?
这些都是无效的测试用例。
为了使这些测试更容易进行,咱们将应用assert_cmd crate。它有许多很好的辅助性能,容许咱们运行咱们的二进制文件并查看它的行为。此外,咱们还将增加predicates crate,它能够帮忙咱们编写断言,assert_cmd
能够对其进行测试(并且具备杰出的谬误音讯)。咱们将这些依赖项增加到Cargo.toml
的dev dependencies
局部,而不是主列表中。它们只在开发crate
时须要,而在应用crate
时不须要。
[dev-dependencies]assert_cmd = "2.0.12"predicates = "3.0.3"
咱们间接进入并创立咱们的tests/cli.rs
文件:
上面,咱们间接用代码正文来阐明外围代码的性能
// 这个crate提供了在运行命令时增加办法的性能,通常用于编写命令行应用程序的测试。use assert_cmd::prelude::*;// 这个crate提供了编写断言(assertions)的性能,能够用来验证测试的预期后果。use predicates::prelude::*;// 这是Rust规范库中的模块,它容许你运行内部程序并与之交互。这通常用于测试执行外部命令时的行为。use std::process::Command;#[test]fn file_doesnt_exist() -> Result<(), Box<dyn std::error::Error>> { // 这行代码创立了一个 Command 对象,它用于执行一个外部命令行程序。 // cargo_bin 办法用于查找并返回通过 Cargo 构建的可执行文件。 // 在这里,它尝试查找名为 "f789" 的可执行文件。 let mut cmd = Command::cargo_bin("f789")?; // 这两行代码向命令增加了两个参数。 // 它们模仿了在命令行中运行 "f789 front text.txt" 命令。 cmd.arg("front").arg("text.txt"); cmd.assert() .failure() .stderr(predicate::str::contains("无奈读取文件")); Ok(())}
咱们能够应用cargo test
运行此测试,就像咱们之前编写的测试一样。第一次运行可能须要更长时间,因为Command::cargo_bin("f789")
须要编译咱们的main二进制文件。
生成测试文件
咱们刚刚看到的测试仅查看当输出文件不存在时,咱们的程序是否会写出谬误音讯。当初让咱们测试一下咱们是否的确会打印出咱们在文件中找到的匹配项!
咱们须要有一个文件,咱们晓得其内容,以便咱们晓得咱们的程序应该返回什么,并在咱们的代码中查看这个冀望。
- 一个想法是向我的项目中增加一个具备自定义内容的文件,并在咱们的测试中应用它。
- 另一个办法是在咱们的测试中创立临时文件。
为了创立这些临时文件,咱们将应用assert_fs crate。让咱们将其增加到Cargo.toml
中的dev-dependencies
中:
assert_fs = "1.0.13"
这是一个新的测试案例(咱们能够在其余测试案例上面编写),它首先创立一个临时文件(一个具名(named)文件,所以咱们能够失去它的门路),而后用一些文本填充它,而后运行咱们的程序,看看咱们是否失去了正确的输入。当文件超出作用域时(在函数的开端),理论的临时文件将主动被删除。
use assert_cmd::prelude::*;use assert_fs::prelude::*;use predicates::prelude::*;use std::process::Command;#[test]fn find_content_in_file() -> Result<(), Box<dyn std::error::Error>> { let file = assert_fs::NamedTempFile::new("sample.txt")?; file.write_str("111\n222\n333\n4444 11")?; let mut cmd = Command::cargo_bin("f789")?; cmd.arg("11").arg(file.path()); cmd.assert() .success() .stdout(predicate::str::contains("111\n4444 11")); Ok(())}
运行cargo test
,代码运行后果如下。
3. 打包并公布 Rust 我的项目
通过,咱们通过单元测试和黑盒测试后,咱们确认,咱们的我的项目曾经性能齐备了。是骡子是马拉进去遛遛当初是打包和公布的时候了!
上面咱们看看公布Rust
的几种形式。
最快:cargo publish
应用cargo
公布应用程序是最简略的办法。咱们还记得咱们如何将内部依赖项增加到我的项目中吗?cargo
会从其默认的包管理器crates.io下载它们。借助cargo publish
,咱们也能够将crate
公布到crates.io
。这实用于所有crate
,包含具备二进制指标的crate
。
将crate
公布到crates.io
相当简略:
- 如果尚未创立
crates.io
帐户,须要创立一个帐户。目前,能够通过在GitHub
上受权来实现 本地计算机上,登录
cargo
登录,为此,咱们须要到crates.io
帐户页面,创立一个新令牌,而后运行cargo login <your-new-token>
确认
Cargo.toml
信息,确保咱们已增加了必要的元数据[package]name = "f789"version = "0.1.0"authors = ["Your Name <your@email.com>"]license = "MIT OR Apache-2.0"description = "文件搜寻工具"readme = "README.md"homepage = "https://github.com/you/f789"repository = "https://github.com/you/f789"keywords = ["cli", "search"]categories = ["command-line-utilities"]
- 应用
cargo publish
进行公布
公布胜利后,就能够在crates.io
中查看
如果你是首次在crates.io
公布,你须要验证一下邮箱
如果想理解能够在cargo
的公布指南中理解更多信息。
如何从crates.io装置二进制文件
咱们曾经理解了如何将crate
公布到crates.io
,咱们可能想晓得如何装置它。与库不同,cargo
会在运行cargo build
(或相似的命令)时为咱们下载和编译库,咱们须要明确通知它要装置二进制文件。
应用cargo install <crate-name>
能够实现这一点。默认状况下,它会下载crate
,编译其中蕴含的所有二进制指标(以release
模式进行,所以可能须要一些工夫),并将它们复制到~/.cargo/bin/
目录中。
还能够从git
存储库装置crate
,仅装置crate
的特定二进制文件,并指定代替目录以进行装置。
何时应用它
cargo install
是一种装置二进制crate的简略办法。对于Rust开发人员来说十分不便,但有一些重要的毛病:因为它总是从头开始编译咱们的源代码,因而应用咱们的工具的用户须要在其计算机上安装Rust
、cargo
和我的项目所需的所有其余零碎依赖项。编译大型Rust
代码库可能也须要一些工夫。
应用cargo install f789
依照
装置胜利,并默认存储到/Users/xxx/.cargo/bin
中
咱们当初能够随便关上一个命令行,并且依照咱们之前代码逻辑,f789 front text.txt
就能够查看运行后果了。
大家能够疏忽下面截图中git
局部的。我为了省事,间接在源代码的目录中,进行了上述的操作。其实上述操作能够在任何终端中运行。
散发二进制文件
Rust
是一种编译为本机代码的语言,并默认状况下动态链接所有依赖项。当咱们在蕴含名为f789
的二进制文件的我的项目上运行cargo build
时,咱们将失去一个名为f789
的二进制文件。
- 应用
cargo build
,它将位于target/debug/f789
, - 当咱们运行
cargo build --release
时,它将位于target/release/f789
。
这意味着,咱们能够将这个文件发送给与咱们运行雷同操作系统的人,他们就能够运行它。
它解决了cargo install
的两个毛病:用户的计算机上不须要装置Rust
,并且不是须要一分钟能力编译,他们能够立刻运行二进制文件。
因而,正如咱们所看到的,cargo build
曾经为咱们构建了二进制文件。惟一的问题是,默认状况下,这些二进制文件不能保障在所有乏味的平台上运行。如果咱们在Windows
计算机上运行cargo build
,咱们不会失去默认状况下在Mac
上运行的二进制文件。
在 CI 上构建二进制版本
如果咱们的工具是开源的并托管在GitHub
上,那么设置收费的CI
(继续集成)服务(如Travis CI)非常容易。这基本上是在虚拟机中每次咱们推送更改到咱们的存储库时运行设置命令。这些命令是什么以及它们运行在哪种类型的机器上是可配置的。
咱们还能够应用此性能构建二进制文件并将其上传到GitHub
!
- 首先,咱们运行
cargo build --release
并将二进制文件上传到某个地位 其次,咱们依然须要确保咱们构建的二进制文件与尽可能多的零碎兼容。
- 例如,在
Linux
上,咱们能够编译而不是为以后零碎编译,而是为x86_64-unknown-linux-musl
指标编译,以防止依赖默认零碎库。 - 在
macOS
上,咱们能够将MACOSX_DEPLOYMENT_TARGET
设置为10.7
,只依赖于版本10.7
及更早版本中存在的零碎性能。
- 例如,在
另一种办法是应用蕴含构建二进制文件所需工具的预构建(Docker
)映像。这容许咱们轻松地针对更多的异构平台进行定位。trust我的项目蕴含能够蕴含在咱们的我的项目中的脚本以及设置的阐明。它还包含应用AppVeyor
的Windows
反对。
如果咱们只想在本地设置并在本人的计算机上生成公布文件,请依然查看trust。它在外部应用cross,它的工作形式相似于cargo
,但将命令转发到Docker
容器外部的cargo
过程。这些映像的定义也可在cross
的存储库中找到。
何时应用它
一般来说,领有二进制公布版本是一个好主见,简直没有任何不利因素。它不能解决用户必须手动装置和更新工具的问题,但他们能够疾速获取最新的公布版本,而无需装置Rust
。
将应用程序放入包存储库
迄今为止,咱们看到的两种办法都不是咱们通常在计算机上安装软件的形式。特地是大多数操作系统上的全局软件包管理器,咱们能够应用这些管理器来装置命令行工具。对用户来说:如果他们能够以与装置其余工具雷同的形式安装程序,那么就无需思考如何装置咱们的程序。这些软件包管理器还容许用户在新版本可用时更新其程序。
难点在于,反对不同的零碎意味着咱们必须查看这些不同的零碎如何工作。对于某些零碎,只需向存储库增加一个文件(例如,为macOS
的brew
增加一个Formula
文件),但对于其余零碎,咱们通常须要本人发送补丁并将咱们的工具增加到它们的存储库中。有一些有用的工具,如cargo-bundle、cargo-deb和cargo-aur,但形容它们的工作原理以及如何正确为这些不同的零碎打包咱们的工具超出了本章的范畴。
代码展现
src/main.rs
use anyhow::{Context, Result};use clap::Parser;use std::fs::File;use std::io::{self, BufRead};use std::path::PathBuf;/// 在文件中搜寻模式并显示蕴含它的行。#[derive(Parser)]struct Cli { /// 要查找的模式 pattern: String, /// 要读取的文件的门路 path: PathBuf,}fn main() -> Result<()> { let args = Cli::parse(); // 关上文件并创立一个 BufReader 来逐行读取 let file = File::open(&args.path).with_context(|| format!("无奈关上文件 {:?}", &args.path))?; let reader = io::BufReader::new(file); let stdout = io::stdout(); let stdout_lock = stdout.lock(); let handle = io::BufWriter::new(stdout_lock); let content = reader .lines() .collect::<io::Result<Vec<String>>>()? .join("\n"); f789::find_matches(&content, &args.pattern, handle)?; Ok(())}
src/lib.rs
use anyhow::Result;use indicatif::ProgressBar;use std::io::Write;pub fn find_matches(content: &str, pattern: &str, mut writer: impl Write) -> Result<()> { let pb = ProgressBar::new(100); for line in content.lines() { do_hard_work(); pb.println(format!("[+] 查找到了 #{:?}项", line)); pb.inc(1); if line.contains(pattern) { writeln!(writer, "{}", line)?; } } Ok(())}fn do_hard_work() { std::thread::sleep(std::time::Duration::from_millis(250));}
后记
分享是一种态度。
全文完,既然看到这里了,如果感觉不错,顺手点个赞和“在看”吧。