Skip to main content

forge/profile/
build.rs

1use std::process::Command;
2
3use wowlab_fs::{
4    directory::{self, EntryKind},
5    path::{Path, PathBuf},
6};
7
8use super::targets::ProfileTarget;
9
10pub(super) fn build_target(
11    target: &ProfileTarget,
12    crates_dir: &Path,
13    quiet: bool,
14) -> Option<PathBuf> {
15    let mut cmd = Command::new("cargo");
16
17    cmd.arg("build")
18        .arg("--profile")
19        .arg("bench-profile")
20        .arg("-p")
21        .arg(&target.package);
22
23    if !target.features.is_empty() {
24        cmd.arg("--features").arg(target.features.join(","));
25    }
26
27    cmd.current_dir(crates_dir);
28
29    if quiet {
30        cmd.stdout(std::process::Stdio::null())
31            .stderr(std::process::Stdio::null());
32    }
33
34    let status = cmd.status().ok()?;
35
36    if !status.success() {
37        return None;
38    }
39
40    let binary = crates_dir
41        .join("target")
42        .join("bench-profile")
43        .join(&target.bin_name);
44
45    directory::inspect(&binary)
46        .ok()
47        .flatten()
48        .is_some_and(|entry| entry.kind() == EntryKind::File)
49        .then_some(binary)
50}
51
52pub(super) fn build_bench(
53    target: &ProfileTarget,
54    crates_dir: &Path,
55    quiet: bool,
56) -> Option<PathBuf> {
57    let mut cmd = Command::new("cargo");
58
59    cmd.arg("bench")
60        .arg("--profile")
61        .arg("bench-profile")
62        .arg("-p")
63        .arg(&target.package)
64        .arg("--bench")
65        .arg(&target.bin_name)
66        .arg("--no-run");
67
68    cmd.current_dir(crates_dir);
69
70    if quiet {
71        cmd.stdout(std::process::Stdio::null())
72            .stderr(std::process::Stdio::null());
73    }
74
75    let status = cmd.status().ok()?;
76
77    if !status.success() {
78        return None;
79    }
80
81    // Criterion hashes binary names, so the newest matching artifact is authoritative.
82
83    let deps_dir = crates_dir.join("target").join("bench-profile").join("deps");
84
85    find_newest_binary(&deps_dir, &target.bin_name)
86}
87
88fn find_newest_binary(dir: &Path, prefix: &str) -> Option<PathBuf> {
89    let dashed = format!("{}-", prefix.replace('-', "_"));
90
91    let mut best: Option<(PathBuf, std::time::SystemTime)> = None;
92    let Ok(entries) = directory::entries(dir) else {
93        return None;
94    };
95
96    for entry in entries {
97        if entry.kind() != EntryKind::File {
98            continue;
99        }
100
101        let path = entry.path();
102        let name = path
103            .file_name()
104            .unwrap_or_default()
105            .to_string_lossy()
106            .to_string();
107
108        if name.contains('.') {
109            continue;
110        }
111
112        if !name.starts_with(&dashed) {
113            continue;
114        }
115
116        let Some(modified) = entry.modified() else {
117            continue;
118        };
119
120        if best.as_ref().is_none_or(|(_, t)| modified > *t) {
121            best = Some((path.to_path_buf(), modified));
122        }
123    }
124
125    best.map(|(p, _)| p)
126}
127
128#[cfg(test)]
129mod tests {
130    use googletest::prelude::*;
131    use wowlab_fs::{file, temporary::Directory};
132
133    use super::find_newest_binary;
134
135    #[gtest]
136    fn benchmark_discovery_ignores_metadata_and_other_targets() -> Result<()> {
137        let directory = Directory::new().or_fail()?;
138        let artifact = directory.path().join("engine_bench-1234");
139
140        file::write_text(&artifact, "").or_fail()?;
141        file::write_text(&directory.path().join("engine_bench-1234.d"), "").or_fail()?;
142        file::write_text(&directory.path().join("other_bench-1234"), "").or_fail()?;
143
144        verify_that!(
145            find_newest_binary(directory.path(), "engine-bench").as_deref(),
146            some(eq(&*artifact))
147        )
148    }
149}