1use anyhow::{Context, Result, ensure};
2use serde_json::Value;
3use sha2::{Digest, Sha256};
4use std::{
5 fs,
6 io::Read,
7 path::{Path, PathBuf},
8 process::{Command, Output},
9};
10
11pub fn root() -> PathBuf {
12 Path::new(env!("CARGO_MANIFEST_DIR"))
13 .parent()
14 .unwrap()
15 .to_owned()
16}
17
18pub fn absolute(path: &Path) -> Result<PathBuf> {
19 Ok(if path.is_absolute() {
20 path.to_owned()
21 } else {
22 std::env::current_dir()?.join(path)
23 })
24}
25
26pub fn command(args: &[&str]) -> Command {
27 let mut command = Command::new(args[0]);
28
29 command.args(&args[1..]).current_dir(root());
30
31 command
32}
33
34pub fn checked(output: Output) -> Result<String> {
35 ensure!(
36 output.status.success(),
37 "command failed: {}",
38 String::from_utf8_lossy(&output.stderr)
39 );
40
41 Ok(String::from_utf8(output.stdout)?.trim().to_owned())
42}
43
44pub fn output(args: &[&str]) -> Result<String> {
45 checked(
46 command(args)
47 .output()
48 .with_context(|| format!("running {}", args[0]))?,
49 )
50}
51
52pub fn digest(path: &Path) -> Result<String> {
53 let mut file = fs::File::open(path)?;
54 let mut hash = Sha256::new();
55 let mut buffer = [0u8; 64 * 1024];
56
57 loop {
58 let n = file.read(&mut buffer)?;
59
60 if n == 0 {
61 break;
62 }
63
64 hash.update(&buffer[..n]);
65 }
66
67 Ok(hash.finalize().iter().map(|b| format!("{b:02x}")).collect())
68}
69
70pub fn files(path: &Path) -> Result<Vec<PathBuf>> {
71 let mut result = Vec::new();
72
73 for entry in fs::read_dir(path)? {
74 let entry = entry?;
75
76 if entry.file_type()?.is_dir() {
77 result.extend(files(&entry.path())?);
78 } else {
79 result.push(entry.path());
80 }
81 }
82
83 result.sort();
84
85 Ok(result)
86}
87
88pub fn source_digest() -> Result<String> {
89 let root = root();
90 let mut sources = files(&root.join("src"))?;
91
92 sources.extend(files(&root.join("kernels"))?);
93 sources.sort();
94 sources.extend([root.join("Cargo.toml"), root.join("Cargo.lock")]);
95
96 let mut hash = Sha256::new();
97
98 for path in sources {
99 hash.update(path.strip_prefix(&root)?.to_string_lossy().as_bytes());
100 hash.update(fs::read(path)?);
101 }
102
103 Ok(hash.finalize().iter().map(|b| format!("{b:02x}")).collect())
104}
105
106pub fn json(path: &Path) -> Result<Value> {
107 serde_json::from_slice(&fs::read(path)?).with_context(|| path.display().to_string())
108}
109
110pub fn write_json(path: &Path, value: &Value) -> Result<()> {
111 let mut file = tempfile::NamedTempFile::new_in(path.parent().context("output parent")?)?;
112
113 use std::io::Write;
114
115 writeln!(file, "{}", serde_json::to_string_pretty(value)?)?;
116 file.persist(path)?;
117
118 Ok(())
119}
120
121pub fn utc() -> Result<String> {
122 output(&["date", "-u", "+%Y%m%dT%H%M%SZ"])
123}
124
125pub fn build() -> Result<()> {
126 ensure!(
127 command(&[
128 "cargo",
129 "build",
130 "--release",
131 "--offline",
132 "-p",
133 "cherenkov"
134 ])
135 .status()?
136 .success(),
137 "release build failed"
138 );
139
140 Ok(())
141}