Rust 项目实践:使用 crates.io 管理和优化你的代码库

背景&为什么要创建一个Rust库

在开发公司桌面端测试工具时,我们采用了Tauri + Rust + Vite + Vue3的组合方案以提高生产效率并优化应用程序打包后的大小。相较于之前使用的Electron,Tauri编译的应用体积显著减小。

在生产线上的测试工具中经常会出现高重复使用率的情况,例如,半成品设备测试完成的设备需要在装成整机后再次进行一部分相同测试。这导致了代码的高度复用,且不同工站的代码量随之增加。

此时,我们可以利用Rust的库管理系统crates.io。通过将通用模块拆分成独立库来进行管理和调用,不仅提高了代码的复用性,减少了代码量,而且便于后续的维护工作。

Rust库管理 - crates.io

crates.io是Rust编程语言的官方包管理服务,用于发布和共享Rust库。它提供了一个中央库存,开发者可以在此找到、下载和使用其他人创建的代码库,同时也能分享自己的库。crates.io与Cargo紧密集成,简化了Rust项目的依赖管理。

crates.io的特点和功能

  • 中心化包管理:为Rust代码提供了一个中心化的位置,方便开发者发现、安装和发布。
  • 社区支持:强调社区贡献和开源分享,鼓励代码重用,同时提供了对包开发者和维护者的可见性。

crates.io的版本管理规范

  • 主版本号:当你做了不兼容的API修改时递增。
  • 次版本号:当你添加了向下兼容的功能时递增。
  • 修订号:当你进行了向下兼容的问题修正时递增。

发布更新时,你需要根据更改的性质来决定是更改主版本号、次版本号或修订号,这也确保了依赖于你的包的用户能够根据版本号的变化判断更新的重要性和性质。

crates.io 发布流程

  1. 准备你的Rust项目
  • 安装cargo和Rust:首先肯定是要安装Rust的环境,这个就不多说了
  • 创建crate:使用cargo new name --lib 创建一个新的项目 - 基于lib
  • 编写你的cargo.toml
toml 复制代码
[package]
name = "" # 你的包名
version = "0.1.0" # 初始版本号
edition = "2018" # Rust 版本
authors = [""] # 作者信息
license = "MIT" # 许可证
description = "" # 描述你的仓库地址
keywords = ["", ""] # 可选:关键词,帮助别人发现你的包
categories = [""] # 可选:分类

[dependencies] #依赖
tokio = { version = "1.0", features = ["full"] }
  • 开发crate:在项目中编写代码,本地测试功能
  • 发布crates:开发完成后,提交git
  • 发布crates:注册你的crates的账号,创建一个库,获取其中的key值(这个key用于你登录)。使用cargo login登录你的账号(cargo login key );key值只能获取一次 ,你刷新或者返回后都不能再看到这个key,注意保留, 使用cargo doc 生产你的项目文档,使用cargo publish 发布到crates.io
  • 引用你的crate:其他项目可以通过在其cargo.toml文件中添加依赖来使用你的crate。
toml 复制代码
[dependencies]
你的crate = "你的版本号"

具体内容

  1. 创建一个adb_rust 的creat。
sql 复制代码
    cargo new adb_rust --lib

获取后的目录结构加上你创建的结构,一个基本的目录结构应该是以下所示。

csharp 复制代码
my_project/
├── Cargo.toml          # 项目的配置文件
├── Cargo.lock          # 自动生成的依赖版本锁文件(在项目构建时生成)
├── src/                # 源代码文件夹
│   ├── lib.rs          # 库的根文件(如果是库项目)
│   ├── main.rs         # 程序的入口文件(如果是二进制项目)
│   └── bin/            # 存放多个二进制文件的源码(可选)
│       └── another_bin.rs # 另一个二进制文件的源码(可选)
├── examples/           # 存放项目示例代码的文件夹(可选)
│   └── simple.rs
├── tests/              # 集成测试文件夹(可选)
│   └── some_integration_tests.rs
├── benches/            # 基准测试文件夹(可选)
│   └── some_bench_test.rs
└── target/             # 编译生成的文件和目录,包括二进制文件和依赖(gitignore中应忽略)

这个结构是Rust项目的一个基本布局。根据项目的不同需求,可能会有一些变化。

  1. 编写一个adb的API
rust 复制代码
// 定义一个trait
pub trait ADBCmdTrait {}
// 定义一个结构体
pub struct ADBCmd{}

impl ADBCmdTrait for ADBCmd{}
rust 复制代码
use tokio::process::Command
// 编写trait的方法
pub trait ADBCmdTrait{
    fn new(cmd:String,is_shell:bool)->Self; // 创建构造函数:传入cmd 命令执行和是否shell的参数
    fn create_cmd(self)->Command; // 返回一个tokio的Command实例
    async fn run_async<F>(&self,args:Vec<String>,fnc:F) // 执行异步调用,使用回调函数(闭包的方式来获取对应的数据)
        where
            F:FnMut(String)->String+'static;
    fn run(&self,args:Vec<String>)->Result<String,String>;//执行同步调用,返回成功失败的Result
    fn get_file_path(path:&str)->Result<String,String>; // 获取文件路径,返回一个成功失败的Result
};

pub struct ADBCmd{
    cmd:String,
    is_shell:bool
};
rust 复制代码
// 编写结构体的方法
impl  ADBCmdTrait for ADBCmd{
    fn new(cmd:String,is_shell:bool)->Self {
        ADBCmd{cmd,is_shell}
    }
    
    fn create_cmd(self)->Command{
        // 这里创建的是使用tokio::process::Command的实例,是一个异步的API;
        let use_command =  Command::new(self.cmd);
        if self.is_shell{
            use_command.arg("shell");
        };
        use_command
    }
    
    async run_async<F>(&self,args:Vec<String>,mut fnc:F)
    where
        F:FnMut(String)->String+'static,
    {
        let mut child = ADBCmd::clone(&self)
            .create_cmd()
            .args(args)
            .stdout(std::process::Stdio::piped()) // 将标准输出重定向到管道
            .spawn() // 启动子进程
            .expect("failed to execute command");

        if let Some(stdout) = cmd.stdout.take() {
            let reader = BufReader::new(stdout);
            let mut lines = reader.lines();

            while let Ok(Some(line)) = lines.next_line().await {
                let _result = fnc(line);
                // 这里你可以直接使用 `result`,或者根据需要做进一步的处理
            }
        }
    }
    
    fn run(&self,args:Vec<String>)->Result<String,String>{
        let mut output = std::process:Command::new(&self.cmd);
        if &self.is_shell{
            output.arg("shell");
        };
        output.args(args);
        match output.output(){
              Ok(child) => {
                let out = child.status.success();
                if out {
                    let stdout = String::from_utf8_lossy(&child.stdout).to_string();
                    Ok(stdout)
                } else {
                    let stderr = String::from_utf8_lossy(&child.stderr).to_string();
                    Err(stderr)
                }
            }
            Err(err) => Err(format!("Failed to execute command:{}", err)),            
        }    
    }
    fn get_file_path(path: &str) -> Result<String, String> {
        let mut _custom_path = Path::new(path).canonicalize();
        match _custom_path {
            Ok(p) => Ok(p.as_os_str().to_string_lossy().to_string()),
            Err(_err) => Err(format!("The file path does not exist or is incorrect")),
        }
    }
}
  1. 编写API 或者方法的注释。

编写注释有助于使用者了解你的方法的意义和使用方式。在编译器中hover 这个API时,会显示以下的图片;

rust 复制代码
// 编写一个注释示例

// 这里写你对方法的介绍:这是一个执行同步命令调用的方法,返回一个Result的成功或者失败;

/// no_run
///
/// use ADB::cmd::ADBCmd;
/// let adb_cmd = ADBCmd::new("cmd".to_string(),false);
/// let result = adb_cmd.run(vec!["devices".to_string()]);

fn run(&self, args: Vec<String>) -> Result<String, String> {
    let mut output = std::process::Command::new(&self.cmd);
    if self.is_shell {
        output.arg("shell".to_string());
    }
    output.args(args);
    match output.output() {
        Ok(child) => {
            let out = child.status.success();
            if out {
                let stdout = String::from_utf8_lossy(&child.stdout).to_string();
                Ok(stdout)
            } else {
                let stderr = String::from_utf8_lossy(&child.stderr).to_string();
                Err(stderr)
            }
        }
        Err(err) => Err(format!("Failed to execute command:{}", err)),
    }
}
  1. 编写测试代码
rust 复制代码
#[cfg(test)]
mod tests {
    use crate::cmd::{ADBCmd, ADBCmdTrait};

    use super::*;
    use tokio;

    #[test]

    fn test_adb_cmd() {
        let res = cmd::ADBCmd::get_file_path("./resources/file.xml").unwrap();
        let res = res.replace("\\\\?\\", "");
        let args = vec!["push".to_string(), res, "/data/local/tmp/".to_string()];
        let binding = ADBCmd::new("adb".to_string(), false);
        let child = binding.run(args);
        match child {
            Ok(stdout) => {
                println!("{}", stdout)
            }
            Err(stderr) => {
                println!("{}", stderr)
            }
        }
    }

    #[tokio::test]
    async fn test_run_async() {
        let adb_cmd = ADBCmd::new("adb".to_string(), false);
        let args = ["devices".to_string()];
        adb_cmd
            .run_async(args.to_vec(), |line| {
                println!("{}", line);
                line
            })
            .await;
    }
}
  1. 编写库版本并且发布crate
toml 复制代码
/// 编写配置文件
[package]
name = "adb-rust" # 你的包名
version = "0.2.1" # 初始版本号
edition = "2018" # Rust 版本
authors = [""] # 作者信息
license = "MIT" # 许可证
description = "A common adb operation" # 描述你的仓库地址
keywords = ["adb", "command"] # 可选:关键词,帮助别人发现你的包
categories = ["command-line-utilities"] # 可选:分类

[dependencies]
tokio = { version = "1.0", features = ["full"] }
···
···
cmd 复制代码
    //发布你的crates.io
    cargo publish
    

发布完成后你可以在你的crates.io中看到版本

  1. 如何使用

首先在你的项目中引入这个库,使用命令 cargo add adb-rust; 在调用中使用:

rust 复制代码
use adb_rust::cmd::{ADBCmd, ADBCmdTrait};

pub fn push_case_config_custom() -> Result<String, String> {
    let _custom: &str = "./resources/cases.xml";
    let res = ADBCmd::get_file_path(_custom).unwrap();
    let args = vec!["push".to_string(), res, "/data/local/tmp/".to_string()];
    let _res = use_adb_action("adb", false, args);
    _res
}
相关推荐
JUNAI_Strive_ving4 分钟前
番茄小说逆向爬取
javascript·python
看到请催我学习13 分钟前
如何实现两个标签页之间的通信
javascript·css·typescript·node.js·html5
twins352033 分钟前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky1 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~1 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
哪 吒1 小时前
华为OD机试 - 几何平均值最大子数(Python/JS/C/C++ 2024 E卷 200分)
javascript·python·华为od
安冬的码畜日常1 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
Q_w77422 小时前
一个真实可用的登录界面!
javascript·mysql·php·html5·网站登录
昨天;明天。今天。2 小时前
案例-任务清单
前端·javascript·css
一丝晨光2 小时前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby