用rust 写一个jar包 class冲突检测工具

这篇具有很好参考价值的文章主要介绍了用rust 写一个jar包 class冲突检测工具。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Rust很适合写命令行工具,特别是使用clap crate 更加方便,这篇文章介绍使用rust写一个jar包class冲突检测的工具。项目地址: https://github.com/Aitozi/jar_conflict_detector
首先jar包class冲突的现象是多个jar包中有同名的class,并且class的md5还不一样,那么就意味着该class存在多个版本,那么就存在冲突的可能。
思路比较简单,就是遍历每个jar包,记录ClassName 和 对应 CRC 校验码 及 jar 包的对应关系。
通过clap的derive api就可以快速定义个命令行的参数解析器。

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
    #[arg(
        short,
        long = "jars",
        required = true,
        help = "The jar list joined by semicolon"
    )]
    jar_list: String,

    #[arg(long, help = "Disable the crc check", action = clap::ArgAction::SetTrue)]
    #[arg(default_value_t = false)]
    disable_crc: bool,

    #[arg(short, long, action = clap::ArgAction::Append, help = "The exclude package prefix")]
    exclude: Vec<String>,
}

通过zip读取jar包中的entry, 过滤只处理.class文件,并从zip_file中读取crc32的元数据,这样可以避免读取原始数据生成md5,可以大大加快处理速度。

Borrow checker的问题

中间编写的时候遇到了一个常见的rust borrow checker的问题。
以下代码为例

fn main() {
    let path = "/tmp/a.jar";
    let jar = File::open(path).unwrap();
    let mut zip = ZipArchive::new(jar).unwrap();

    for name in zip.file_names() {
        let entry = zip.by_name(name);
        println!("name: {}, size: {}", name, entry.unwrap().size());
    }
}

我是想通过遍历ZipArchive#file_names然后根据文件名获取ZipFile但是会有如下编译错误
用rust 写一个jar包 class冲突检测工具

pub fn file_names(&self) -> impl Iterator<Item = &str> {
    self.shared.names_map.keys().map(|s| s.as_str())
}
/// Search for a file entry by name
pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>> {
    Ok(self.by_name_with_optional_password(name, None)?.unwrap())
}

但是用以下的方式就没有问题

let path = "/tmp/a.jar";
let jar = File::open(path).unwrap();
let mut zip = ZipArchive::new(jar).unwrap();

for i in 0..zip.len() {
    let entry = zip.by_index(i).unwrap();
    println!("name: {}, size: {}", entry.name(), entry.size());
}

这里我比较奇怪的是从方法签名上看 len()file_names()都会发生immutable borrow,而后面by_indexby_name都会发生mutable borrow。为什么会一个可以通过检查,一个不行。

pub fn len(&self) -> usize {
    self.shared.files.len()
}

len函数实际的签名应该是fn len<'a>(&'a self) -> usize 返回值是usize,所以函数调用完成后就不再和借用有关了。所以 immutable borrow 就结束了。
file_names实际签名是fn file_names<'a>(&'a self) -> impl Iterator<Item = &'a str> {…}返回值的生命周期和 入参的 immutable ref周期相同,所以就要求 zip immutable borrow ref 至少要保持到iterator结束,所以后续就检测出循环中同时存在可变和不可变引用了。
详细解释: https://users.rust-lang.org/t/borrow-check-understanding/94260/2

这个问题实际上和生命周期有关,阅读这篇基本就能想通这个问题 https://course.rs/basic/lifetime.html

命令行频繁被Killed问题

问题现象是当使用cargo build打包出binary后,通过cp 到 /tmp/jcd执行 会出现 Killed的情况,不是必现,但是当出现之后后续就一直会这样,百思不得其解。

$ /tmp/jcd
[1]    16957 killed     /tmp/jcd

后通过在rust user 论坛提问找到答案,不得不说回复效率很高。
https://users.rust-lang.org/t/rust-command-line-tools-keeps-beeing-killed/94179
原因应该是和苹果电脑上的 Code sign机制有关,在苹果没有解决这个问题之前,建议通过ditto替代cp命令来copy程序。
经过检查系统日志确实有出现 Code Signature Invalid的报错
用rust 写一个jar包 class冲突检测工具

相同的Class CRC和MD5却不一样

问题是发现在集成这个工具到内部的插件框架中,集成过程中发现一个Jar包被另一个module依赖,经过shade插件打包(没有对相关class进行relocate) 后,生成的class crc32不同,被识别为会冲突的类。通过javap -v 查看两个class对比发现里面的仅仅是一些constant pool 不同。
那么怀疑就是maven-shade-plugin 做了什么操作,翻阅了下代码,查看了shade的处理流程.
看到以下这段,发现这不就是我遇到的问题么。
用rust 写一个jar包 class冲突检测工具
查阅了相应的issue: https://issues.apache.org/jira/browse/MSHADE-391
在3.3.0 才解决,而我使用的版本正好是3.2.4。升级插件重新生成校验码一致了。
因此如果多个包中存在有maven shade 打包的或者不同jvm打包的class可能会存在一些常量池不对之类的情况,所以这个冲突检测可能检测出并不冲突的class,因为没有真实的去比较class的signature.

解决冲突的Class

最后再回到最初的目的,当我们通过工具检测出冲突的class应该怎么解决呢。
首先我们需要判断这个class是否是运行时所需要的。
如果不是所需要的那么我们就应该直接排掉他,排除有两种手段(这里针对的是maven shade的打包方式),如果在dependency tree中可以看到相应package的依赖,那么可以直接通过如下的白名单 include 或者 exclude 掉某个 artifact。

<artifactSet combine.self="override">
  <includes>
    <include>commons-dbcp:commons-dbcp</include>
    <include>commons-pool:commons-pool</include>
    <include>mysql:mysql-connector-java</include>
  </includes>
</artifactSet>

但是不排除这个依赖包本身就是fatjar,那么直接通过这种方式就排不掉这个依赖,可以通过filters 配置文件 粒度的匹配过滤

<filters>
  <filter>
    <artifact>*:*</artifact>
    <excludes>
      <exclude>META-INF/*.SF</exclude>
      <exclude>META-INF/*.DSA</exclude>
      <exclude>META-INF/*.RSA</exclude>
      <exclude>javax/**</exclude>
      <exclde>org/slf4j/**</exclde>
    </excludes>
  </filter>
</filters>

如果这个冲突的class是运行时需要的,那么可以通过relocation的方式给各自的插件包中shade成带特殊前缀的class名,解决同名冲突。

<relocation>
    <pattern>org.apache.http</pattern>
    <shadedPattern>com.aitozi.shaded.org.apache.http</shadedPattern>
</relocation>

发布命令行工具到crates.io

cargo login ${token}
cargo publish 

这样发布之后其他用户就可以通过cargo install来下载并编译, 安装你的程序。不过这样需要安装的用户上进行编译和install的过程。

注意这里我第一次上传的时候有一个typo,而crates.io 上包一经发布就不可以删除了,因此发布包的时候一定要check下包名信息。

发布二进制文件

更好的是直接发布二进制文件,rust-cli book的教程上提供的一个travis-ci的 trust模板已经过时了,很久没在维护了,所以不建议参考参考 trust模板: https://github.com/japaric/trust
我这里参考的是 https://github.com/japaric/trust/issues/136 中提到的一个样例,直接使用github ci来进行发布,这个模板实际上是通过cross 工具来实现编译多个平台的包。

权限问题

用rust 写一个jar包 class冲突检测工具
模板中默认使用了github.token来鉴权采 参考,这个不需要自己去创建,每一个workflow都会注入,但是默认权限好像不够,不知道原作者为啥可以跑, 可能修改了默认的GITHUB_TOKEN的权限。
也有其他人report了这个问题:https://github.com/taiki-e/create-gh-release-action/issues/13
通过添加以下权限声明解决

permissions:
	contents: write

通过Github Actions,以后需要发布一个多平台的binary 就非常简单只需要push 一个tag即可

git tag v0.0.1  
git push origin v0.0.1

自动化流程就会创建出一个release以及多平台的binary
用rust 写一个jar包 class冲突检测工具文章来源地址https://www.toymoban.com/news/detail-456597.html

到了这里,关于用rust 写一个jar包 class冲突检测工具的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Rust】Rust学习 第十二章一个 I/O 项目:构建一个命令行程序

    本章既是一个目前所学的很多技能的概括,也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 技能。 Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择,

    2024年02月12日
    浏览(50)
  • 【Java可执行命令】(五)打包部署工具 jar:深入解析应用程序打包、分发和部署工具jar ~

    Java的可执行命令程序 jar (Java Archive)最早作为一项功能在JDK 1.0中引入。它的 设计目的是为了方便在Java平台上进行打包、分发和部署应用程序 。 jar 文件可以将多个Java类、资源文件和其他依赖项打包成一个单独的归档文件,以实现更简单的应用程序管理和传输。 jar 文件是

    2024年02月11日
    浏览(70)
  • 使用Rust开发命令行工具

    生成二进制文件,将其扔到环境变量的path下即可~ 用rust打造实时天气命令行工具 [1] 找到合适的API 使用 该api [2] 如请求 api.openweathermap.org/data/2.5/weather?q=Beijingappid=your_key : 初始化项目coding 使用 cargo new rust_weather 初始化一个项目。 对于 cargo.toml 文件: 对于 src/main.rs 文件: 使用

    2024年02月11日
    浏览(40)
  • rust学习 - 构建mini 命令行工具

    rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为构建命令行程序的最佳选择。 实现一个命令行搜索工具 grep ,可以在指定文件中搜索指定的字符串。想实现这个功能呢,可以按照以下逻辑流程处理: 获取输入文件路径、需要搜索的字符串 读取文件; 在文件

    2024年02月08日
    浏览(43)
  • 【跟小嘉学 Rust 编程】十二、构建一个命令行程序

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学 Rust 编程】六、枚举

    2024年02月13日
    浏览(41)
  • 【Linux】linux下使用命令修改jar包内某一个文件中的内容并重新运行jar程序

    需求:发现线上的 iotp-irsb-server-v1.0.0.2.jar 包中配置文件的日志级别配置错误,需要在线修改jar包中文件的application-prod.yml的日志级别配置,修改完成后并重启该jar包。 进入到该jar包所在的目录,使用 vi 命令打开 jar 包内文件列表。 1、vi iotp-irsb-server-V1.0.0.2.jar 如下图所示:

    2024年02月08日
    浏览(51)
  • Rust程序语言设计 第十二章 一个 I/O 项目:构建一个命令行程序

    本章既是一个目前所学的很多技能的概括,也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 技能。 Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择,

    2024年02月13日
    浏览(42)
  • rust 使用第三方库构建mini命令行工具

    这是上一篇 rust 学习 - 构建 mini 命令行工具的续作,扩展增加一些 crate 库。这些基础库在以后的编程工作中会常用到,他们作为基架存在于项目中,解决项目中的某个问题。 项目示例还是以上一篇的工程为基础做调整修改ifun-grep 仓库地址 在开发 ifun-grep 项目时,运行项目命

    2024年02月09日
    浏览(58)
  • jar包冲突解决方案

    使用背景 在构建工程中,不可避免的引入多方依赖。从jar包冲突产生结果可大致分为两类: 1.同一个jar包出现了多个不同的版本。应用选择了错误的版本导致jvm加载不到需要的类或者加载了错误版本的类。 2.不同的jar包出现了类路径一致的类,同样的类出现在多个不同的依赖

    2024年02月09日
    浏览(49)
  • 解决 Android 开发过程中 出现 Duplicate class(包冲突)

    1、现在大部分的项目都是支持 Androidx 的,所以出现 Duplicate 的时候 先把 gradle.properties 文件中添加参数,支持使用AndroidX 2、有些 *.jar/*.aar 不支持 AndroidX 的时候,将上面的禁用 然后再排除 AndroidX 的引用   有很多,搜索一下 androidx 就能找到 3、排除了Androidx 的冲突后,包还有

    2023年04月09日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包