rust_version_info_file/
lib.rs

1#![allow(clippy::needless_doctest_main)]
2/*!
3output rust version info into a file
4
5This crate is the presents, the file output of rustc --version and cargo tree command.
6
7# Features
8
9- minimum support rustc 1.65.0 (897e37553 2022-11-02)
10
11# Examples
12
13Please write the following code in the build.rs:
14
15```rust
16use rust_version_info_file::rust_version_info_file;
17
18fn main() {
19    rust_version_info_file("target/rust-version-info.txt", "Cargo.toml");
20}
21```
22
23And you get the file as result it.
24
25```text
26cat target/rust-version-info-file.txt
27```
28
29# On debian package
30
31In Cargo.toml
32
33```text
34[package.metadata.deb]
35assets = [
36    ["target/rust-version-info.txt", "usr/share/doc/your_package/", "644"],
37    ["README.md", "usr/share/doc/your_package/", "644"],
38]
39```
40
41# Output sample
42
43```text
44$ cat target/rust-version-info-aki-gsub.txt
45```
46
47```text
48rustc 1.89.0 (29483883e 2025-08-04)
49aki-gsub v0.1.34
50├── anyhow v1.0.99
51├── atty v0.2.14
52│   └── libc v0.2.175
53├── flood-tide v0.2.11
54├── memx-cdy v0.1.13
55│   ├── libc v0.2.175
56│   └── memx v0.1.32
57│       └── cpufeatures v0.2.17
58├── regex v1.11.1
59│   ├── aho-corasick v1.1.3
60│   │   └── memchr v2.7.5
61│   ├── memchr v2.7.5
62│   ├── regex-automata v0.4.9
63│   │   ├── aho-corasick v1.1.3 (*)
64│   │   ├── memchr v2.7.5
65│   │   └── regex-syntax v0.8.5
66│   └── regex-syntax v0.8.5
67└── runnel v0.3.19
68[build-dependencies]
69├── rust-version-info-file v0.1.10
70└── rustc_version v0.4.1
71    └── semver v1.0.26
72[dev-dependencies]
73├── exec-target v0.2.9
74└── indoc v1.0.9 (proc-macro)
75```
76*/
77use std::fs::OpenOptions;
78use std::io::{Read, Write};
79use std::path::Path;
80
81/// output rust version info into a file
82pub fn rust_version_info_file<T: AsRef<Path>>(dst: T, cargo_toml_file: &str) {
83    let dst_path: &Path = dst.as_ref();
84    let old_s = read_file(dst_path);
85    let curr_s = format!(
86        "{}\n{}\n",
87        rustc_version_info(),
88        tree_version_info(cargo_toml_file),
89    );
90    if old_s != curr_s {
91        let mut fo = match OpenOptions::new()
92            .create(true)
93            .write(true)
94            .truncate(true)
95            .open(dst_path)
96        {
97            Ok(fo) => fo,
98            Err(_) => return,
99        };
100        let _ = fo.write_fmt(format_args!("{curr_s}"));
101        let _ = fo.flush();
102    }
103}
104
105fn read_file(path: &Path) -> String {
106    match OpenOptions::new().create(false).read(true).open(path) {
107        Ok(mut fi) => {
108            let mut s = String::new();
109            let _ = fi.read_to_string(&mut s);
110            s
111        }
112        Err(_) => String::new(),
113    }
114}
115
116fn rustc_version_info() -> String {
117    let cmd = std::env::var_os("RUSTC").unwrap_or_else(|| std::ffi::OsString::from("rustc"));
118    let out = std::process::Command::new(cmd)
119        .arg("--version")
120        .output()
121        .unwrap();
122    let v = out.stdout;
123    let v_len = v.len();
124    let v = if v_len > 0 && v[v_len - 1] == b'\n' {
125        &v[0..v_len - 1]
126    } else {
127        &v[0..v_len]
128    };
129    String::from_utf8(v.to_vec()).unwrap()
130}
131
132fn tree_version_info(cargo_toml_file: &str) -> String {
133    let cmd = "cargo";
134    let out = std::process::Command::new(cmd)
135        .arg("tree")
136        .arg("--color")
137        .arg("never")
138        .arg("--manifest-path")
139        .arg(cargo_toml_file)
140        .output()
141        .unwrap();
142    let v = out.stdout;
143    let v_len = v.len();
144    let v = if v_len > 0 && v[v_len - 1] == b'\n' {
145        &v[0..v_len - 1]
146    } else {
147        &v[0..v_len]
148    };
149    //
150    let string = String::from_utf8(v.to_vec()).unwrap();
151    //
152    let string = match string.find(" (") {
153        Some(pos1) => {
154            let (s1, s2) = string.split_at(pos1);
155            match s2.find(')') {
156                Some(pos2) => {
157                    let (_s2, s3) = s2.split_at(pos2 + 1);
158                    s1.to_owned() + s3
159                }
160                None => string,
161            }
162        }
163        None => string,
164    };
165    //
166    string
167}