Skip to content

Commit 75dfa26

Browse files
authored
simple build summary (#1030)
1 parent 88e61b8 commit 75dfa26

File tree

6 files changed

+225
-2
lines changed

6 files changed

+225
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ hemtt-workspace = { path = "../libs/workspace" }
3333
hemtt-wss = { path = "../libs/wss" }
3434

3535
arma3-wiki = { workspace = true }
36+
byteorder = { workspace = true }
3637
clap = { workspace = true, features = ["derive"] }
3738
dialoguer = "0.11.0"
3839
dirs = { workspace = true }

bin/src/commands/build.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{
22
context::{self, Context},
33
error::Error,
44
executor::Executor,
5-
modules::{Binarize, Files, Rapifier, pbo::Collapse},
5+
modules::{pbo::Collapse, summary::Summary, Binarize, Files, Rapifier},
66
report::Report,
77
};
88

@@ -111,6 +111,7 @@ pub fn executor(ctx: Context, args: &BuildArgs) -> Executor {
111111
executor.add_module(Box::<Binarize>::default());
112112
}
113113
executor.add_module(Box::<Files>::default());
114+
executor.add_module(Box::<Summary>::default());
114115

115116
executor.init();
116117
executor.check();

bin/src/commands/dev.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
context::Context,
66
error::Error,
77
executor::Executor,
8-
modules::{Binarize, FilePatching, Files, Rapifier, pbo::Collapse},
8+
modules::{pbo::Collapse, summary::Summary, Binarize, FilePatching, Files, Rapifier},
99
report::Report,
1010
};
1111

@@ -181,6 +181,7 @@ pub fn context(
181181
if force_binarize || binarize.binarize {
182182
executor.add_module(Box::<Binarize>::default());
183183
}
184+
executor.add_module(Box::<Summary>::default());
184185

185186
info!("Creating `dev` version");
186187

bin/src/modules/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub mod fnl;
1414
pub mod hook;
1515
pub mod pbo;
1616
pub(crate) mod sign;
17+
pub mod summary;
1718

1819
pub use binarize::Binarize;
1920
pub use file_patching::FilePatching;

bin/src/modules/summary.rs

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
use crate::{modules::Module, utils::bytes_to_human_readable};
2+
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
3+
use walkdir::WalkDir;
4+
5+
#[derive(Debug, Default)]
6+
pub struct Summary;
7+
impl Summary {
8+
#[must_use]
9+
pub const fn new() -> Self {
10+
Self
11+
}
12+
}
13+
14+
impl Drop for Summary {
15+
fn drop(&mut self) {}
16+
}
17+
18+
impl Module for Summary {
19+
fn name(&self) -> &'static str {
20+
"summary"
21+
}
22+
23+
fn priority(&self) -> i32 {
24+
1000
25+
}
26+
27+
fn post_build(&self, ctx: &crate::context::Context) -> Result<crate::report::Report, crate::Error> {
28+
// give a build summary, sizes of pbos, copied files, etc.
29+
let report = crate::report::Report::new();
30+
31+
// Load the last build summary if it exists
32+
let last_build_path = ctx.out_folder().join("last_build.hsb");
33+
let last_build = if last_build_path.exists() {
34+
let mut file = std::fs::File::open(&last_build_path).map_err(|e| {
35+
crate::Error::Io(std::io::Error::new(
36+
std::io::ErrorKind::NotFound,
37+
format!("Failed to open last build summary: {e}"),
38+
))
39+
})?;
40+
SummaryInfo::read(&mut file).map_err(|e| {
41+
crate::Error::Io(std::io::Error::new(
42+
std::io::ErrorKind::InvalidData,
43+
format!("Failed to read last build summary: {e}"),
44+
))
45+
})?
46+
} else {
47+
SummaryInfo::default()
48+
};
49+
50+
let mut files = Vec::new();
51+
let mut pbos = Vec::new();
52+
53+
WalkDir::new(ctx.build_folder().expect("build folder exists"))
54+
.into_iter()
55+
.filter_map(|entry| {
56+
let entry = entry.ok()?;
57+
if entry.file_type().is_file() {
58+
let size = entry.metadata().ok()?.len();
59+
match entry.path().extension() {
60+
Some(ext) if ext == "pbo" => {
61+
pbos.push((entry.file_name().to_string_lossy().to_string(), size));
62+
return Some(());
63+
}
64+
_ => {
65+
files.push((entry.file_name().to_string_lossy().to_string(), size));
66+
return Some(());
67+
}
68+
}
69+
}
70+
None
71+
})
72+
.count();
73+
74+
let pbo_size = pbos.iter().map(|(_, size)| *size).sum::<u64>();
75+
let file_size = files.iter().map(|(_, size)| *size).sum::<u64>();
76+
let total_size = pbo_size + file_size;
77+
let size_diff = total_size.abs_diff(last_build.last.size);
78+
79+
println!("Build Summary:");
80+
println!(" PBOs : {}", bytes_to_human_readable(pbo_size));
81+
println!(" Files : {}", bytes_to_human_readable(file_size));
82+
println!(
83+
" Total : {}{}",
84+
bytes_to_human_readable(total_size),
85+
if total_size == last_build.last.size || last_build.last.size == 0 {
86+
String::new()
87+
} else {
88+
format!(" ({}{} from last build)",
89+
if total_size > last_build.last.size {
90+
"↑"
91+
} else {
92+
"↓"
93+
},
94+
bytes_to_human_readable(size_diff)
95+
)
96+
}
97+
);
98+
println!();
99+
100+
let summary_info = SummaryInfo {
101+
last: LastBuild {
102+
size: total_size,
103+
pbos,
104+
files,
105+
},
106+
};
107+
summary_info.write(&mut std::fs::File::create(last_build_path)?)?;
108+
109+
Ok(report)
110+
}
111+
}
112+
113+
#[derive(Debug, Default)]
114+
struct SummaryInfo {
115+
last: LastBuild,
116+
}
117+
118+
impl SummaryInfo {
119+
pub fn write<W: std::io::Write>(&self, mut writer: W) -> std::io::Result<()> {
120+
// Write the version
121+
writer.write_u32::<LittleEndian>(1u32)?;
122+
// Write the last build summary
123+
self.last.write(&mut writer)?;
124+
Ok(())
125+
}
126+
127+
pub fn read<R: std::io::Read>(mut reader: R) -> std::io::Result<Self> {
128+
// Read the version
129+
let version = reader.read_u32::<LittleEndian>()?;
130+
if version != 1 {
131+
return Err(std::io::Error::new(
132+
std::io::ErrorKind::InvalidData,
133+
"Unsupported version",
134+
));
135+
}
136+
let dev = LastBuild::read(&mut reader)?;
137+
Ok(Self { last: dev })
138+
}
139+
}
140+
141+
#[derive(Debug, Default)]
142+
struct LastBuild {
143+
size: u64,
144+
pbos: Vec<(String, u64)>,
145+
files: Vec<(String, u64)>,
146+
}
147+
148+
impl LastBuild {
149+
#[expect(clippy::cast_possible_truncation, reason="u32 is sufficient for file count and name length")]
150+
pub fn write<W: std::io::Write>(&self, mut writer: W) -> std::io::Result<()> {
151+
// Write the version
152+
writer.write_u32::<LittleEndian>(1u32)?;
153+
// Write the size
154+
writer.write_u64::<LittleEndian>(self.size)?;
155+
// Write the number of PBOs
156+
writer.write_u32::<LittleEndian>(self.pbos.len() as u32)?;
157+
// Write each PBO name and size
158+
for (name, size) in &self.pbos {
159+
let len = name.len() as u32;
160+
writer.write_u32::<LittleEndian>(len)?;
161+
writer.write_all(name.as_bytes())?;
162+
writer.write_u64::<LittleEndian>(*size)?;
163+
}
164+
// Write the number of files
165+
writer.write_u32::<LittleEndian>(self.files.len() as u32)?;
166+
// Write each file name and size
167+
for (name, size) in &self.files {
168+
let len = name.len() as u32;
169+
writer.write_u32::<LittleEndian>(len)?;
170+
writer.write_all(name.as_bytes())?;
171+
writer.write_u64::<LittleEndian>(*size)?;
172+
}
173+
Ok(())
174+
}
175+
176+
pub fn read<R: std::io::Read>(mut reader: R) -> std::io::Result<Self> {
177+
// Read the version
178+
let version = reader.read_u32::<LittleEndian>()?;
179+
if version != 1 {
180+
return Err(std::io::Error::new(
181+
std::io::ErrorKind::InvalidData,
182+
"Unsupported version",
183+
));
184+
}
185+
// Read the size
186+
let size = reader.read_u64::<LittleEndian>()?;
187+
// Read the number of PBOs
188+
let pbo_count = reader.read_u32::<LittleEndian>()? as usize;
189+
let mut pbos = Vec::with_capacity(pbo_count);
190+
for _ in 0..pbo_count {
191+
let len = reader.read_u32::<LittleEndian>()? as usize;
192+
let mut name_bytes = vec![0; len];
193+
reader.read_exact(&mut name_bytes)?;
194+
let name = String::from_utf8(name_bytes).map_err(|_| {
195+
std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid UTF-8 in PBO name")
196+
})?;
197+
let size = reader.read_u64::<LittleEndian>()?;
198+
pbos.push((name, size));
199+
}
200+
// Read the number of files
201+
let file_count = reader.read_u32::<LittleEndian>()? as usize;
202+
let mut files = Vec::with_capacity(file_count);
203+
for _ in 0..file_count {
204+
let len = reader.read_u32::<LittleEndian>()? as usize;
205+
let mut name_bytes = vec![0; len];
206+
reader.read_exact(&mut name_bytes)?;
207+
let name = String::from_utf8(name_bytes).map_err(|_| {
208+
std::io::Error::new(
209+
std::io::ErrorKind::InvalidData,
210+
"Invalid UTF-8 in file name",
211+
)
212+
})?;
213+
let size = reader.read_u64::<LittleEndian>()?;
214+
files.push((name, size));
215+
}
216+
Ok(Self { size, pbos, files })
217+
}
218+
}

0 commit comments

Comments
 (0)