乐趣区

关于前端:Rust-开发命令行工具中

生存在不可避免地走向高雅。– 王小波

大家好,我是 柒八九


前言

在上一篇 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 我的项目。


你能所学到的知识点

  1. 前置知识点
  2. 代码测试
  3. 打包并公布 Rust 我的项目

好了,天不早了,干点闲事哇。


前置知识点

前置知识点 ,只是做一个概念的介绍,不会做深度解释。因为,这些概念在上面文章中会有呈现,为了让行文更加的顺畅,所以将本该在文内的概念解释放到后面来。 如果大家对这些概念相熟,能够间接疏忽

同时,因为浏览我文章的群体有很多,所以有些知识点可能 我视之若瑰宝,尔视只如草芥,弃之如敝履 。以下知识点,请 酌情应用

单元测试 VS 黑盒测试

单元测试 黑盒测试 (也叫 集成测试 ) 是两种不同的软件测试办法,它们旨在检查和验证软件的品质和性能,但它们的关注点、办法和指标有所不同。

单元测试(Unit Testing)

  1. 焦点 单元测试 关注测试软件的 最小性能单元 ,通常是一个 函数、办法或模块。它的指标是验证这个性能单元是否依照预期工作,而不思考其余组件。
  2. 测试者:通常由开发人员编写和执行。开发人员编写测试用例,用于查看函数、办法或模块的各种输出和边界条件。
  3. 可见性 :单元测试通常具备对代码的 白盒拜访权限,测试者能够拜访和查看被测试单元的外部实现细节,以编写更准确的测试用例。
  4. 指标:次要指标是验证单元的正确性,确保它们依照标准执行,并解决各种输出状况。

黑盒测试(Black Box Testing)

  1. 焦点 黑盒测试 关注测试 整个软件系统的性能,而不思考外部实现。它的指标是验证零碎是否依照标准的需要和性能标准工作。
  2. 测试者:能够由测试工程师或独立的测试团队执行。测试者不须要理解零碎的外部实现,只需关注零碎的输出和输入。
  3. 可见性:黑盒测试没有对系统的外部实现细节的理解。测试者只能拜访零碎的内部接口和性能。
  4. 指标:次要指标是验证零碎是否满足其标准和需要,以及是否在各种输出和条件下体现失常。

在理论我的项目中,通常须要同时进行 单元测试 黑盒测试,以确保软件在各个层面上都具备高质量和可靠性。

Rust trait

Rust 中,trait 是一种非凡的类型,它定义了某些类型的 共享行为 trait 提供了一种形式来形象和共享方法,相似于其余编程语言中的 接口 。通过实现trait,你能够为自定义类型定义通用的行为,使其可能与其余类型一起工作,从而进步了Rust 代码的可复用性和灵活性。

上面咱们简略解释一下 trait 的应用

  1. 定义trait

    咱们能够应用 trait 关键字来定义一个trait,而后在其中申明办法签名。

    trait Printable {fn print(&self);
    }

    这个示例定义了一个名为 Printabletrait,它要求实现该 trait 的类型必须蕴含一个名为 print 的办法。

  2. 实现trait

    要使类型实现一个 trait,咱们须要在类型的定义中应用impl 块来实现 trait 中申明的办法。

    struct MyStruct {data: i32,}
    
    impl Printable for MyStruct {fn print(&self) {println!("Data: {}", self.data);
        }
    }

    在这个示例中,MyStruct类型实现了 Printable trait,提供了print 办法的具体实现。

  3. 应用trait

    一旦你实现了一个 trait,咱们能够在任何实现了该trait 的类型上调用 trait 中定义的办法。例如:

    let my_instance = MyStruct {data: 42};
    my_instance.print();

    在这里,咱们创立了一个 MyStruct 的实例并调用了 print 办法。

总的来说,traitRust 中用于实现形象和共享行为的弱小工具,它有助于编写可复用的代码,同时确保类型的安全性和一致性。通过正当应用 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 的根模块。

包中所蕴含的内容由几条规定来确立。

  1. 一个包中 至少只能 蕴含一个 <span style=”font-weight:800;color:#FFA500;font-size:18px”>{库 crate|library crate}</span>;
  2. 包中能够蕴含 任意多 个 <span style=”font-weight:800;color:#FFA500;font-size:18px”>{二进制 crate|binary crate}</span>;
  3. 包中 至多蕴含 一个 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.rscrate 根。

crate 根文件将由 Cargo 传递给 rustc 来理论构建库或者二进制我的项目。

如果一个包 同时含有 src/main.rssrc/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.ioRust 编程语言社区的 官网包治理和散发平台 。它相似于其余编程语言中的 包管理器,如 PythonPyPIJavaScriptnpm,用于帮忙 Rust 开发者分享、公布和获取 Rust 代码库(也称为 “crates“)。

以下是 crates.io 的一些要害特点和性能:

  1. 包管理器 crates.io 提供了一个 地方存储库,用于托管 Rust crates。开发者能够应用 cargoRust 的包管理工具,轻松地下载、装置和治理这些 crates
  2. 包公布:任何 Rust 开发者都能够将本人的 Rust 代码库公布到 crates.io 上,供其他人应用。这使得代码共享和开源社区单干更加容易。
  3. 版本控制:每个 crate 都有本人的版本号,容许开发者指定应用特定版本的 crate。这有助于确保代码的稳定性和可靠性。
  4. 依赖治理crates.io 容许 crate 之间建设依赖关系,开发者能够在本人的我的项目中引入其余 crates 作为依赖项,从而疾速构建功能强大的应用程序。
  5. 搜寻和浏览crates.io 提供了一个易于应用的网站,容许开发者搜寻、浏览和查找他们须要的 Rust crates。网站还提供了无关每个 crate 的详细信息、文档和示例代码。
  6. 社区驱动crates.io 是由 Rust 社区保护和反对的,任何人都能够为平台的倒退和改良做出奉献。

总之,crates.ioRust 生态系统的外围组成部分,它使 Rust 开发更加便捷,促成了 Rust 社区的增长和分享代码的文化。开发者能够在下面找到各种各样的 Rust crates,以减速他们的我的项目开发。


2. 代码测试

为了确保咱们的程序依照咱们的冀望工作,最理智的做法是对其进行测试。

一种简略的办法是编写一个 README 文件,形容咱们的程序应该执行的操作。当咱们筹备公布新版本时,通过 README 能够形容咱们程序的性能和行为。与此同时,咱们还能够通过写下程序应该如何应答谬误输出来让咱们的程序变的更加谨严。


自动化测试

Rust 中,#[test] 是一个属性(attribute),用于标记测试函数。Rust内置了一个测试框架,能够应用这个属性来定义和运行测试。

以下是应用 #[test] 的根本步骤:

  1. 首先,确保咱们的 Rust 我的项目是一个可测试的我的项目。通常,Rust我的项目的测试代码寄存在一个名为 tests 的目录中,或者在咱们的代码中应用 条件编译 来辨别测试代码和生产代码。它容许构建零碎发现这些函数并将其作为测试运行,验证它们不会触发panic
  2. 创立一个测试函数并标记为 #[test]。测试函数必须返回 ()(unit 类型),并且通常不带参数。

    #[test]
    fn test_example() {// 在这里编写测试代码}
  3. 在测试函数中编写测试代码,包含调用咱们要测试的函数,并 应用断言来查看函数的输入是否与预期值匹配。咱们能够应用规范库中的 assert! 宏或其余测试断言宏来进行断言。

    #[test]
    fn test_addition() {assert_eq!(2 + 2, 4);
    }
    
    #[test]
    fn test_subtraction() {assert!(5 - 3 > 0);
    }
  4. 运行测试。能够应用 Rust 的测试运行器工具来执行测试。常见的测试命令是 cargo test,它会主动查找和运行我的项目中的所有测试函数。在 我的项目根目录下 运行以下命令:

    cargo test
  5. 测试运行后果会显示在终端中。胜利的测试将显示为ok,失败的测试将显示为 fail,并提供失败的详细信息,包含测试函数的名称和失败的断言。咱们能够依据这些信息来调试和修复代码。
  6. 如果须要更具体的输入,能够应用 --verbose 标记运行测试

    cargo test --verbose

咱们应该最终失去相似以下的输入:

通过 #[test] 咱们能够测试咱们想测试的外围代码,然而,作为一个 CLI 通常不仅仅是一个函数,它须要更多的人机交互,例如须要解决用户输出、读取文件和编写输入等,咱们不可预知的参数和行为。


单元测试

有两种互补的办法来测试性能:

  1. 单元测试(unit tests): 测试构建残缺应用程序的小单元。
  2. 黑盒测试 (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::Writetrait,它可用于咱们能够写入的货色,包含字符串,还有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 文件中。这意味着咱们以后的我的项目生成一个 独自的二进制文件。然而咱们也能够将咱们的代码作为一个库提供,办法如下:

  1. find_matches 函数放入一个新的 src/lib.rs 文件中。
  2. fn 后面加上pub(这样它就是pub fn find_matches),以使其成为咱们库的用户能够拜访的内容。
  3. src/main.rs 中删除find_matches
  4. 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.tomldev 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 相当简略:

  1. 如果尚未创立 crates.io 帐户,须要创立一个帐户。目前,能够通过在 GitHub 上受权来实现
  2. 本地计算机上,登录 cargo 登录,为此,咱们须要到 crates.io 帐户页面,创立一个新令牌,而后运行cargo login <your-new-token>

  3. 确认 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"]
  4. 应用 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 开发人员来说十分不便,但有一些重要的毛病:因为它总是从头开始编译咱们的源代码,因而应用咱们的工具的用户须要在其计算机上安装 Rustcargo 和我的项目所需的所有其余零碎依赖项。编译大型 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 我的项目蕴含能够蕴含在咱们的我的项目中的脚本以及设置的阐明。它还包含应用 AppVeyorWindows反对。

如果咱们只想在本地设置并在本人的计算机上生成公布文件,请依然查看 trust。它在外部应用 cross,它的工作形式相似于 cargo,但将命令转发到Docker 容器外部的 cargo 过程。这些映像的定义也可在 cross 的存储库中找到。

何时应用它

一般来说,领有二进制公布版本是一个好主见,简直没有任何不利因素。它不能解决用户必须手动装置和更新工具的问题,但他们能够疾速获取最新的公布版本,而无需装置Rust


将应用程序放入包存储库

迄今为止,咱们看到的两种办法都不是咱们通常在计算机上安装软件的形式。特地是大多数操作系统上的全局软件包管理器,咱们能够应用这些管理器来装置命令行工具。对用户来说:如果他们能够以与装置其余工具雷同的形式安装程序,那么就无需思考如何装置咱们的程序。这些软件包管理器还容许用户在新版本可用时更新其程序。

难点在于,反对不同的零碎意味着咱们必须查看这些不同的零碎如何工作。对于某些零碎,只需向存储库增加一个文件(例如,为 macOSbrew增加一个 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));
}

后记

分享是一种态度

全文完,既然看到这里了,如果感觉不错,顺手点个赞和“在看”吧。

退出移动版