Skip to content

Commit 70687b3

Browse files
feat(system): represent the host OS as a typed Os enum with per-executor support gates
Replaces the loose `os` + `os_version` strings on `SystemInfo` with an `Os` enum (`SupportedLinux { distro, version }`, `OtherLinux`, `MacOs`, `Unsupported`) and the global `SUPPORTED_SYSTEMS` tuple lookup with a single `match` in `check_system`. Adds `Executor::support_level(&Os)` so each executor declares its own OS matrix: `run_executor` bails on `Unsupported`, skips `setup()` on `RequiresManualSetup`, and runs it on `FullySupported`. This removes the dirty macOS best-effort branch previously living in `check.rs`. The wire format is preserved: `Os` serializes via an `OsSerde` shim and is flattened into `SystemInfo` via `#[serde(flatten)]`, so the JSON sent to the API is byte-identical (verified by the existing upload-metadata snapshot tests). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 14a89b2 commit 70687b3

13 files changed

Lines changed: 331 additions & 102 deletions

File tree

src/cli/setup.rs

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::executor::{ToolInstallStatus, get_all_executors};
1+
use crate::executor::{ExecutorSupport, ToolInstallStatus, get_all_executors};
22
use crate::prelude::*;
33
use crate::system::SystemInfo;
44
use clap::{Args, Subcommand};
@@ -22,10 +22,7 @@ enum SetupCommands {
2222
pub async fn run(args: SetupArgs, setup_cache_dir: Option<&Path>) -> Result<()> {
2323
match args.command {
2424
None => setup(setup_cache_dir).await,
25-
Some(SetupCommands::Status) => {
26-
status();
27-
Ok(())
28-
}
25+
Some(SetupCommands::Status) => status(),
2926
}
3027
}
3128

@@ -34,20 +31,43 @@ async fn setup(setup_cache_dir: Option<&Path>) -> Result<()> {
3431
let executors = get_all_executors();
3532
start_group!("Setting up the environment for all executors");
3633
for executor in executors {
37-
info!(
38-
"Setting up the environment for the executor: {}",
39-
executor.name()
40-
);
41-
executor.setup(&system_info, setup_cache_dir).await?;
34+
match executor.support_level(&system_info.os) {
35+
ExecutorSupport::Unsupported => {
36+
info!(
37+
"Skipping setup for the {} executor: not supported on {}",
38+
executor.name(),
39+
system_info.os
40+
);
41+
}
42+
ExecutorSupport::RequiresManualSetup => {
43+
info!(
44+
"Skipping automatic setup for the {} executor on {}; install required tooling manually.",
45+
executor.name(),
46+
system_info.os
47+
);
48+
}
49+
ExecutorSupport::FullySupported => {
50+
info!(
51+
"Setting up the environment for the executor: {}",
52+
executor.name()
53+
);
54+
executor.setup(&system_info, setup_cache_dir).await?;
55+
}
56+
}
4257
}
4358
info!("Environment setup completed");
4459
end_group!();
4560
Ok(())
4661
}
4762

48-
pub fn status() {
63+
pub fn status() -> Result<()> {
64+
let system_info = SystemInfo::new()?;
4965
info!("{}", style("Tools").bold());
5066
for executor in get_all_executors() {
67+
// Don't probe for tooling that can't be used on this OS anyway.
68+
if executor.support_level(&system_info.os) == ExecutorSupport::Unsupported {
69+
continue;
70+
}
5171
let tool_status = executor.tool_status();
5272
match &tool_status.status {
5373
ToolInstallStatus::Installed { version } => {
@@ -71,4 +91,5 @@ pub fn status() {
7191
}
7292
}
7393
}
94+
Ok(())
7495
}

src/cli/status.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,14 @@ pub async fn run(api_client: &CodSpeedAPIClient) -> Result<()> {
2222
info!("");
2323

2424
// Setup/tools status
25-
super::setup::status();
25+
super::setup::status()?;
2626
info!("");
2727

2828
// System info
2929
info!("{}", style("System").bold());
3030
info!(" codspeed {VERSION}");
3131
let system_info = SystemInfo::new()?;
32-
info!(
33-
" {} {} ({})",
34-
system_info.os, system_info.os_version, system_info.arch
35-
);
32+
info!(" {} ({})", system_info.os, system_info.arch);
3633
info!(
3734
" {} ({}C / {}GB)",
3835
system_info.cpu_brand, system_info.cpu_cores, system_info.total_memory_gb

src/executor/helpers/apt.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use super::run_with_sudo::run_with_sudo;
22
use crate::prelude::*;
3-
use crate::system::SystemInfo;
3+
use crate::system::{Os, SystemInfo};
44
use std::path::Path;
55
use std::process::Command;
66

77
const METADATA_FILENAME: &str = "./tmp/codspeed-cache-metadata.txt";
88

99
fn is_system_compatible(system_info: &SystemInfo) -> bool {
10-
system_info.os == "ubuntu" || system_info.os == "debian"
10+
matches!(system_info.os, Os::SupportedLinux { .. })
1111
}
1212

1313
/// Installs packages with caching support.

src/executor/memory/executor.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::executor::ExecutorName;
2+
use crate::executor::ExecutorSupport;
23
use crate::executor::ToolStatus;
34
use crate::executor::helpers::command::CommandBuilder;
45
use crate::executor::helpers::env::get_base_injected_env;
@@ -11,7 +12,7 @@ use crate::executor::{ExecutionContext, Executor};
1112
use crate::instruments::mongo_tracer::MongoTracer;
1213
use crate::prelude::*;
1314
use crate::runner_mode::RunnerMode;
14-
use crate::system::SystemInfo;
15+
use crate::system::{Os, SystemInfo};
1516
use async_trait::async_trait;
1617
use ipc_channel::ipc;
1718
use memtrack::MemtrackIpcClient;
@@ -76,6 +77,14 @@ impl Executor for MemoryExecutor {
7677
get_memtrack_status()
7778
}
7879

80+
fn support_level(&self, os: &Os) -> ExecutorSupport {
81+
match os {
82+
Os::SupportedLinux { .. } => ExecutorSupport::FullySupported,
83+
Os::OtherLinux { .. } => ExecutorSupport::RequiresManualSetup,
84+
Os::MacOs { .. } | Os::Unsupported { .. } => ExecutorSupport::Unsupported,
85+
}
86+
}
87+
7988
async fn setup(
8089
&self,
8190
_system_info: &SystemInfo,

src/executor/mod.rs

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ mod wall_time;
1515
use crate::instruments::mongo_tracer::{MongoTracer, install_mongodb_tracer};
1616
use crate::prelude::*;
1717
use crate::runner_mode::RunnerMode;
18-
use crate::system::SystemInfo;
18+
use crate::system::{Os, SystemInfo};
1919
use async_trait::async_trait;
2020
pub use config::{BenchmarkTarget, ExecutorConfig, OrchestratorConfig};
2121
pub use execution_context::ExecutionContext;
@@ -73,13 +73,30 @@ pub enum ToolInstallStatus {
7373
NotInstalled,
7474
}
7575

76+
/// How well a given executor runs on a given [`Os`].
77+
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
78+
pub enum ExecutorSupport {
79+
/// The executor cannot run on this OS at all — `run_executor` hard-bails.
80+
Unsupported,
81+
/// The executor runs on this OS, but the user is responsible for installing the required
82+
/// tooling themselves; `setup()` is skipped.
83+
RequiresManualSetup,
84+
/// The executor runs on this OS and `setup()` knows how to auto-install tooling.
85+
FullySupported,
86+
}
87+
7688
#[async_trait(?Send)]
7789
pub trait Executor {
7890
fn name(&self) -> ExecutorName;
7991

8092
/// Report the installation status of the tool(s) this executor depends on.
8193
fn tool_status(&self) -> ToolStatus;
8294

95+
/// Declare how well this executor runs on the host OS. Drives whether `setup()` is invoked
96+
/// (only when [`ExecutorSupport::FullySupported`]) and whether we bail out of running the
97+
/// executor at all (on [`ExecutorSupport::Unsupported`]).
98+
fn support_level(&self, os: &Os) -> ExecutorSupport;
99+
83100
async fn setup(
84101
&self,
85102
_system_info: &SystemInfo,
@@ -107,11 +124,33 @@ pub async fn run_executor(
107124
execution_context: &ExecutionContext,
108125
setup_cache_dir: Option<&Path>,
109126
) -> Result<()> {
110-
if !execution_context.config.skip_setup {
111-
executor
112-
.setup(&orchestrator.system_info, setup_cache_dir)
113-
.await?;
127+
match executor.support_level(&orchestrator.system_info.os) {
128+
ExecutorSupport::Unsupported => {
129+
bail!(
130+
"The {} executor is not supported on {}",
131+
executor.name(),
132+
orchestrator.system_info.os
133+
);
134+
}
135+
ExecutorSupport::RequiresManualSetup => {
136+
if !execution_context.config.skip_setup {
137+
info!(
138+
"Skipping automatic setup for the {} executor on {}; ensure required tooling is installed manually.",
139+
executor.name(),
140+
orchestrator.system_info.os
141+
);
142+
}
143+
}
144+
ExecutorSupport::FullySupported => {
145+
if !execution_context.config.skip_setup {
146+
executor
147+
.setup(&orchestrator.system_info, setup_cache_dir)
148+
.await?;
149+
}
150+
}
151+
}
114152

153+
if !execution_context.config.skip_setup {
115154
// TODO: refactor and move directly in the Instruments struct as a `setup` method
116155
if execution_context.config.instruments.is_mongodb_enabled() {
117156
install_mongodb_tracer().await?;

src/executor/valgrind/executor.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ use std::path::Path;
33

44
use crate::executor::Executor;
55
use crate::executor::ToolStatus;
6-
use crate::executor::{ExecutionContext, ExecutorName};
6+
use crate::executor::{ExecutionContext, ExecutorName, ExecutorSupport};
77
use crate::instruments::mongo_tracer::MongoTracer;
88
use crate::prelude::*;
9-
use crate::system::SystemInfo;
9+
use crate::system::{Os, SystemInfo};
1010

1111
use super::setup::{get_valgrind_status, install_valgrind};
1212
use super::{helpers::perf_maps::harvest_perf_maps, helpers::venv_compat, measure};
@@ -23,6 +23,14 @@ impl Executor for ValgrindExecutor {
2323
get_valgrind_status()
2424
}
2525

26+
fn support_level(&self, os: &Os) -> ExecutorSupport {
27+
match os {
28+
Os::SupportedLinux { .. } => ExecutorSupport::FullySupported,
29+
Os::OtherLinux { .. } => ExecutorSupport::RequiresManualSetup,
30+
Os::MacOs { .. } | Os::Unsupported { .. } => ExecutorSupport::Unsupported,
31+
}
32+
}
33+
2634
async fn setup(&self, system_info: &SystemInfo, setup_cache_dir: Option<&Path>) -> Result<()> {
2735
install_valgrind(system_info, setup_cache_dir).await?;
2836

src/executor/valgrind/setup.rs

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::cli::run::helpers::download_file;
22
use crate::executor::helpers::apt;
33
use crate::executor::{ToolInstallStatus, ToolStatus};
44
use crate::prelude::*;
5-
use crate::system::SystemInfo;
5+
use crate::system::{Os, SupportedLinuxDistro, SystemInfo};
66
use crate::{
77
VALGRIND_CODSPEED_DEB_VERSION, VALGRIND_CODSPEED_VERSION, VALGRIND_CODSPEED_VERSION_STRING,
88
};
@@ -11,22 +11,25 @@ use std::{env, path::Path, process::Command};
1111
use url::Url;
1212

1313
fn get_codspeed_valgrind_filename(system_info: &SystemInfo) -> Result<String> {
14-
let (version, architecture) = match (
15-
system_info.os.as_str(),
16-
system_info.os_version.as_str(),
17-
system_info.arch.as_str(),
18-
) {
19-
("ubuntu", "22.04", "x86_64") | ("debian", "12", "x86_64") => ("22.04", "amd64"),
20-
("ubuntu", "24.04", "x86_64") => ("24.04", "amd64"),
21-
("ubuntu", "22.04", "aarch64") | ("debian", "12", "aarch64") => ("22.04", "arm64"),
22-
("ubuntu", "24.04", "aarch64") => ("24.04", "arm64"),
23-
_ => bail!("Unsupported system"),
14+
let Os::SupportedLinux { distro, version } = &system_info.os else {
15+
bail!("Unsupported system");
2416
};
2517

18+
let (deb_ubuntu_version, architecture) =
19+
match (distro, version.as_str(), system_info.arch.as_str()) {
20+
(SupportedLinuxDistro::Ubuntu, "22.04", "x86_64")
21+
| (SupportedLinuxDistro::Debian, "12", "x86_64") => ("22.04", "amd64"),
22+
(SupportedLinuxDistro::Ubuntu, "24.04", "x86_64") => ("24.04", "amd64"),
23+
(SupportedLinuxDistro::Ubuntu, "22.04", "aarch64")
24+
| (SupportedLinuxDistro::Debian, "12", "aarch64") => ("22.04", "arm64"),
25+
(SupportedLinuxDistro::Ubuntu, "24.04", "aarch64") => ("24.04", "arm64"),
26+
_ => bail!("Unsupported system"),
27+
};
28+
2629
Ok(format!(
2730
"valgrind_{}_ubuntu-{}_{}.deb",
2831
VALGRIND_CODSPEED_DEB_VERSION.as_str(),
29-
version,
32+
deb_ubuntu_version,
3033
architecture
3134
))
3235
}
@@ -175,8 +178,7 @@ mod tests {
175178
#[test]
176179
fn test_system_info_to_codspeed_valgrind_version_ubuntu() {
177180
let system_info = SystemInfo {
178-
os: "ubuntu".to_string(),
179-
os_version: "22.04".to_string(),
181+
os: Os::from_id("ubuntu", "22.04"),
180182
arch: "x86_64".to_string(),
181183
..SystemInfo::test()
182184
};
@@ -189,8 +191,7 @@ mod tests {
189191
#[test]
190192
fn test_system_info_to_codspeed_valgrind_version_ubuntu_24() {
191193
let system_info = SystemInfo {
192-
os: "ubuntu".to_string(),
193-
os_version: "24.04".to_string(),
194+
os: Os::from_id("ubuntu", "24.04"),
194195
arch: "x86_64".to_string(),
195196
..SystemInfo::test()
196197
};
@@ -203,8 +204,7 @@ mod tests {
203204
#[test]
204205
fn test_system_info_to_codspeed_valgrind_version_debian() {
205206
let system_info = SystemInfo {
206-
os: "debian".to_string(),
207-
os_version: "12".to_string(),
207+
os: Os::from_id("debian", "12"),
208208
arch: "x86_64".to_string(),
209209
..SystemInfo::test()
210210
};
@@ -217,8 +217,7 @@ mod tests {
217217
#[test]
218218
fn test_system_info_to_codspeed_valgrind_version_ubuntu_arm() {
219219
let system_info = SystemInfo {
220-
os: "ubuntu".to_string(),
221-
os_version: "22.04".to_string(),
220+
os: Os::from_id("ubuntu", "22.04"),
222221
arch: "aarch64".to_string(),
223222
..SystemInfo::test()
224223
};

src/executor/wall_time/executor.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ use crate::executor::helpers::get_bench_command::get_bench_command;
1010
use crate::executor::helpers::run_command_with_log_pipe::run_command_with_log_pipe;
1111
use crate::executor::helpers::run_with_env::wrap_with_env;
1212
use crate::executor::helpers::run_with_sudo::wrap_with_sudo;
13-
use crate::executor::{ExecutionContext, ExecutorName};
13+
use crate::executor::{ExecutionContext, ExecutorName, ExecutorSupport};
1414
use crate::instruments::mongo_tracer::MongoTracer;
1515
use crate::prelude::*;
1616
use crate::runner_mode::RunnerMode;
17-
use crate::system::SystemInfo;
17+
use crate::system::{Os, SystemInfo};
1818
use async_trait::async_trait;
1919
use std::fs::canonicalize;
2020
use std::io::Write;
@@ -122,7 +122,23 @@ impl Executor for WallTimeExecutor {
122122
}
123123

124124
fn tool_status(&self) -> ToolStatus {
125-
super::perf::setup::get_perf_status()
125+
if self.perf.is_some() {
126+
super::perf::setup::get_perf_status()
127+
} else {
128+
// No profiler tool wired up on this OS yet — report as not installed.
129+
ToolStatus {
130+
tool_name: "perf".to_string(),
131+
status: crate::executor::ToolInstallStatus::NotInstalled,
132+
}
133+
}
134+
}
135+
136+
fn support_level(&self, os: &Os) -> ExecutorSupport {
137+
match os {
138+
Os::SupportedLinux { .. } | Os::MacOs { .. } => ExecutorSupport::FullySupported,
139+
Os::OtherLinux { .. } => ExecutorSupport::RequiresManualSetup,
140+
Os::Unsupported { .. } => ExecutorSupport::Unsupported,
141+
}
126142
}
127143

128144
async fn setup(&self, system_info: &SystemInfo, setup_cache_dir: Option<&Path>) -> Result<()> {

0 commit comments

Comments
 (0)