#[cfg(any(target_os = "macos", target_os = "linux"))]
use crate::tools::common::run;
use crate::tools::common::{InstallError, has};
pub const HOMEBREW_INSTALLER_COMMIT: &str = "540da2ca91271886910572df3a50332540ca84e4";
pub const HOMEBREW_INSTALLER_SHA256: &str =
"dfd5145fe2aa5956a600e35848765273f5798ce6def01bd08ecec088a1268d91";
pub fn brew_auto_install_allowed() -> bool {
!crate::sandbox::is_seamless()
}
pub fn pinned_homebrew_installer_command() -> String {
let installer_url = format!(
"https://raw.githubusercontent.com/Homebrew/install/{commit}/install.sh",
commit = HOMEBREW_INSTALLER_COMMIT,
);
format!(
r#"set -euo pipefail
SCRIPT="$(curl -fsSL '{url}')"
ACTUAL=$(printf '%s' "$SCRIPT" | shasum -a 256 | cut -d' ' -f1)
EXPECTED='{expected}'
if [ "$ACTUAL" != "$EXPECTED" ]; then
printf 'jarvy: refusing to run Homebrew installer; sha256 mismatch (got %s, want %s)\n' \
"$ACTUAL" "$EXPECTED" >&2
exit 1
fi
printf '%s' "$SCRIPT" | /bin/bash -s --
"#,
url = installer_url,
expected = HOMEBREW_INSTALLER_SHA256,
)
}
pub fn ensure(_min_hint: &str) -> Result<(), InstallError> {
if has("brew") {
return Ok(());
}
install()
}
pub fn add_handler(min_hint: &str) -> Result<(), InstallError> {
let _ = min_hint;
ensure("")
}
fn install() -> Result<(), InstallError> {
#[cfg(target_os = "macos")]
{
return install_macos();
}
#[cfg(target_os = "linux")]
{
return install_linux();
}
#[cfg(target_os = "windows")]
{
return install_windows();
}
#[allow(unreachable_code)]
Err(InstallError::Unsupported)
}
#[cfg(target_os = "macos")]
fn install_macos() -> Result<(), InstallError> {
install_pinned_brew()
}
#[cfg(target_os = "linux")]
fn install_linux() -> Result<(), InstallError> {
install_pinned_brew()
}
#[cfg(any(target_os = "macos", target_os = "linux"))]
fn install_pinned_brew() -> Result<(), InstallError> {
if !brew_auto_install_allowed() {
return Err(InstallError::Prereq(
"auto-install of brew is refused in CI; pre-install brew on the runner image",
));
}
let script = pinned_homebrew_installer_command();
let _ = run("sh", &["-c", &script])?;
Ok(())
}
#[cfg(target_os = "windows")]
fn install_windows() -> Result<(), InstallError> {
Err(InstallError::Unsupported)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ensure_brew_no_panic() {
let res = ensure("");
assert!(res.is_ok() || res.is_err());
}
#[cfg(target_os = "windows")]
#[test]
fn brew_windows_is_unsupported() {
let res = ensure("");
assert!(
matches!(res, Err(InstallError::Unsupported)),
"brew on Windows should be Unsupported"
);
}
#[cfg(any(target_os = "macos", target_os = "linux"))]
#[test]
fn brew_unix_not_unsupported() {
let res = ensure("");
assert!(
!matches!(res, Err(InstallError::Unsupported)),
"brew on Unix should not return Unsupported"
);
}
#[test]
fn pinned_installer_command_embeds_constants() {
let cmd = pinned_homebrew_installer_command();
assert!(
cmd.contains(HOMEBREW_INSTALLER_COMMIT),
"installer command must include pinned commit"
);
assert!(
cmd.contains(HOMEBREW_INSTALLER_SHA256),
"installer command must include pinned sha256"
);
assert!(!cmd.contains("/HEAD/"));
assert!(!cmd.contains("/master/"));
assert!(!cmd.contains("/main/"));
}
#[test]
fn pinned_constants_are_well_formed() {
assert_eq!(HOMEBREW_INSTALLER_COMMIT.len(), 40);
assert!(
HOMEBREW_INSTALLER_COMMIT
.chars()
.all(|c| c.is_ascii_hexdigit())
);
assert_eq!(HOMEBREW_INSTALLER_SHA256.len(), 64);
assert!(
HOMEBREW_INSTALLER_SHA256
.chars()
.all(|c| c.is_ascii_hexdigit())
);
}
}