mod toolchain;
use super::family::{CHROMIUM_ROW, GECKO_ROW, WEBKIT_ROW};
use super::{Detection, Need, PerPlatform, Pin, Platform, Recipe, Requirement, Tier, Upstream};
use kernel::{AxCode, AxError};
use wire::DoctorUnread;
const PYTHON_WASM_VARIABLE: &str = "SPRAWLING_PYTHON_WASM";
const PYTHON_WASM_FILE: &str = "python.wasm";
pub(crate) const CHROMEDRIVER: &str = "chromedriver";
pub(crate) const MSEDGEDRIVER: &str = "msedgedriver";
pub(crate) const PYTHON_WASI: &str = "python-wasi";
pub(crate) const SHELL: &str = "shell";
pub(crate) const PWSH: &str = "pwsh";
pub(crate) const SANDBOX_ENGINE: &str = "sandbox-engine";
pub(crate) const FFMPEG: &str = "ffmpeg";
pub(crate) fn recipe_for(item: &str) -> Result<&'static Recipe, AxError> {
let requirement = REQUIREMENTS
.iter()
.find(|requirement| requirement.name == item)
.ok_or_else(|| {
AxError::failure(
AxCode::InvalidArgs,
"install a tool",
format!("{item}: this city checks for no such item"),
)
.with_recovery("ask this machine again and install one of the items it answered with")
})?;
let Some(platform) = Platform::current() else {
return Err(AxError::failure(
AxCode::ToolUnavailable,
"install a tool",
format!("{item}: this platform has no recipe in this project"),
)
.with_recovery("install it the way this operating system installs software"));
};
Ok(requirement.recipe.at(platform))
}
#[cfg(test)]
pub(crate) fn prereqs() -> String {
let header = concat!(
"# generated from crates/sprawling/src/doctor/table.rs by `table::prereqs`; ",
"edit the table, not this file\n"
);
REQUIREMENTS
.iter()
.filter(|requirement| requirement.tier == Tier::Develop)
.fold(header.to_owned(), |mut file, requirement| {
let class = match requirement.need {
Need::Required | Need::OneOf(_) => "required",
Need::Optional => "optional",
};
let recipe = |platform| requirement.recipe.at(platform).spelled();
file.push_str(&format!(
"{class}\t{}\t{}\t{}\t{}\t{}\t{}\n",
requirement.name,
shell_probe(&requirement.detect),
recipe(Platform::Windows),
recipe(Platform::MacOs),
recipe(Platform::Linux),
requirement.enables
));
file
})
}
#[cfg(test)]
fn shell_probe(detect: &Detection) -> String {
match detect {
Detection::Program { program, .. } => format!("command -v {program}"),
Detection::Listed {
program,
args,
line,
} => match *line {
"" => format!("{program} {} | grep -q .", args.join(" ")),
pin => format!("{program} {} | grep -q '^{pin}'", args.join(" ")),
},
Detection::Component { .. }
| Detection::Interpreter { .. }
| Detection::Built { .. }
| Detection::Family(_) => "-".to_owned(),
}
}
const NOWHERE: PerPlatform<&[&str]> = PerPlatform {
windows: &[],
macos: &[],
linux: &[],
};
pub(crate) const REQUIREMENTS: &[Requirement] = &[
GECKO_ROW,
CHROMIUM_ROW,
WEBKIT_ROW,
Requirement {
name: CHROMEDRIVER,
tier: Tier::Use,
need: Need::OneOf(super::Group::BrowserEngine),
enables: "the browser tool against Chrome, Brave, Chromium or Vivaldi",
detect: Detection::Program {
program: "chromedriver",
version_arg: "--version",
places: NOWHERE,
},
homepage: Some("https://googlechromelabs.github.io/chrome-for-testing/"),
recipe: PerPlatform {
windows: Recipe::Manual(
"take the build matching your Chromium's major version from \
https://googlechromelabs.github.io/chrome-for-testing/",
),
macos: Recipe::Command {
program: "brew",
args: &["install", "chromedriver"],
},
linux: Recipe::Print("sudo apt install chromium-chromedriver"),
},
pin: Pin::Unpinned,
upstream: Upstream::Unread(DoctorUnread::MatchesBrowser),
pack: None,
},
Requirement {
name: MSEDGEDRIVER,
tier: Tier::Use,
need: Need::OneOf(super::Group::BrowserEngine),
enables: "the browser tool against Edge, which every Windows machine already has",
detect: Detection::Program {
program: "msedgedriver",
version_arg: "--version",
places: NOWHERE,
},
homepage: Some("https://developer.microsoft.com/microsoft-edge/tools/webdriver/"),
recipe: PerPlatform {
windows: Recipe::Manual(
"take the build matching your Edge's major version from \
https://developer.microsoft.com/microsoft-edge/tools/webdriver/",
),
macos: Recipe::Command {
program: "brew",
args: &["install", "--cask", "microsoft-edge-driver"],
},
linux: Recipe::Manual(
"take the build matching your Edge's major version from \
https://developer.microsoft.com/microsoft-edge/tools/webdriver/",
),
},
pin: Pin::Unpinned,
upstream: Upstream::Unread(DoctorUnread::MatchesBrowser),
pack: None,
},
Requirement {
name: SANDBOX_ENGINE,
tier: Tier::Use,
need: Need::Optional,
enables: "the exec tool's program and python arms, fuel-metered and with no socket",
detect: Detection::Built {
carried: super::host::ENGINE_CARRIED,
},
homepage: Some("https://wasmtime.dev/"),
recipe: PerPlatform {
windows: ENGINE_BY_BUILD,
macos: ENGINE_BY_BUILD,
linux: ENGINE_BY_BUILD,
},
pin: Pin::Unpinned,
upstream: Upstream::Unread(DoctorUnread::ThisProject),
pack: None,
},
Requirement {
name: PYTHON_WASI,
tier: Tier::Use,
need: Need::Optional,
enables: "the exec tool's python arm, which runs in the sandbox with no socket",
detect: Detection::Component {
variable: PYTHON_WASM_VARIABLE,
file: PYTHON_WASM_FILE,
},
homepage: Some("https://github.com/python/cpython/blob/main/Tools/wasm/README.md"),
recipe: PerPlatform {
windows: PYTHON_WASI_BY_HAND,
macos: PYTHON_WASI_BY_HAND,
linux: PYTHON_WASI_BY_HAND,
},
pin: Pin::Unpinned,
upstream: Upstream::Unread(DoctorUnread::NoSource),
pack: None,
},
Requirement {
name: SHELL,
tier: Tier::Use,
need: Need::Optional,
enables: "the exec tool's shell arm, where a building's CONFIG.toml asks for it",
detect: Detection::Interpreter {
variable: PerPlatform {
windows: "COMSPEC",
macos: "SHELL",
linux: "SHELL",
},
fallback: PerPlatform {
windows: "cmd.exe",
macos: "/bin/sh",
linux: "/bin/sh",
},
},
homepage: None,
recipe: PerPlatform {
windows: Recipe::Manual("set COMSPEC to a command interpreter"),
macos: Recipe::Manual("set SHELL to a shell, or restore /bin/sh"),
linux: Recipe::Manual("set SHELL to a shell, or restore /bin/sh"),
},
pin: Pin::Unpinned,
upstream: Upstream::Unread(DoctorUnread::NoSource),
pack: None,
},
Requirement {
name: PWSH,
tier: Tier::Use,
need: Need::Optional,
enables: "the exec tool's shell arm under PowerShell 7, where a building's CONFIG.toml \
sets `[sandbox] interpreter = \"pwsh\"`",
detect: Detection::Program {
program: "pwsh",
version_arg: "--version",
places: NOWHERE,
},
homepage: Some("https://github.com/PowerShell/PowerShell"),
recipe: PerPlatform {
windows: Recipe::Command {
program: "winget",
args: &[
"install",
"--id",
"Microsoft.PowerShell",
"-e",
"--scope",
"user",
],
},
macos: Recipe::Command {
program: "brew",
args: &["install", "powershell"],
},
linux: Recipe::Manual("install PowerShell 7 from your distribution's packages"),
},
pin: Pin::Unpinned,
upstream: Upstream::Unread(DoctorUnread::NoSource),
pack: None,
},
Requirement {
name: FFMPEG,
tier: Tier::Use,
need: Need::Optional,
enables: "an mp4 from the desktop connector's recorder; without it, a frame sequence",
detect: Detection::Program {
program: "ffmpeg",
version_arg: "-version",
places: NOWHERE,
},
homepage: Some("https://ffmpeg.org/"),
recipe: PerPlatform {
windows: Recipe::Command {
program: "winget",
args: &["install", "--id", "Gyan.FFmpeg", "-e", "--scope", "user"],
},
macos: Recipe::Command {
program: "brew",
args: &["install", "ffmpeg"],
},
linux: Recipe::Print("sudo apt install ffmpeg"),
},
pin: Pin::Unpinned,
upstream: Upstream::Unread(DoctorUnread::NoSource),
pack: None,
},
toolchain::GIT,
toolchain::BASH,
toolchain::RUSTUP,
toolchain::RUST,
toolchain::RUSTFMT,
toolchain::CLIPPY,
toolchain::JUST,
toolchain::CARGO_NEXTEST,
toolchain::BUN,
toolchain::RENDER_BROWSER,
toolchain::CARGO_DENY,
toolchain::ELAN,
toolchain::LEAN,
toolchain::ZIG,
toolchain::UV,
toolchain::PYTHON,
toolchain::CARGO_MUTANTS,
toolchain::CARGO_FUZZ,
toolchain::KANI,
];
const PYTHON_WASI_BY_HAND: Recipe =
Recipe::Manual("put a CPython wasi build at ~/.sprawling/components/python-wasi/python.wasm");
const ENGINE_BY_BUILD: Recipe =
Recipe::Manual("install a build of sprawling with the `sandbox` feature");