//! Install-flow helpers for the installer binary.
//!
//! This module keeps prebuilt-download fallback and metrics recording logic
//! separate from CLI orchestration in `main.rs`.
use camino::Utf8Path;
use camino::Utf8PathBuf;
use std::collections::HashSet;
use std::fs;
use std::io;
use std::io::Write;
use std::time::Duration;
use whitaker_installer::builder::{library_extension, library_prefix};
use whitaker_installer::cli::InstallArgs;
use whitaker_installer::crate_name::CrateName;
use whitaker_installer::deps::{CommandExecutor, DylintToolStatus, SourcePolicy};
use whitaker_installer::dirs::BaseDirs;
use whitaker_installer::error::{InstallerError, Result};
use whitaker_installer::install_metrics::{InstallMode, RecordOutcome, record_install};
use whitaker_installer::output::write_stderr_line;
use whitaker_installer::prebuilt::{PrebuiltConfig, PrebuiltResult, attempt_prebuilt};
use whitaker_installer::prebuilt_path::prebuilt_library_dir;
use whitaker_installer::resolution::{EXPERIMENTAL_LINT_CRATES, LINT_CRATES, SUITE_CRATE};
pub(crate) mod bootstrap_step;
mod dependency_check;
#[cfg(all(test, unix))]
pub(crate) use dependency_check::check_dylint_tools_for_command;
pub(crate) fn ensure_dylint_tools_core(
quiet: bool,
stderr: &mut dyn Write,
is_all_installed: bool,
do_install: impl FnOnce(&mut dyn Write) -> Result<()>,
) -> Result<()> {
if is_all_installed {
return Ok(());
}
if !quiet {
write_stderr_line(stderr, "Installing required Dylint tools...");
}
do_install(stderr)?;
if !quiet {
write_stderr_line(stderr, "Dylint tools installed successfully.");
write_stderr_line(stderr, "");
}
Ok(())
}
/// Query availability once, then delegate missing-tool installation.
///
/// The callback keeps query ordering shared by the CLI and tests while
/// leaving the concrete installation boundary injectable.
pub(crate) fn ensure_dylint_tools_with_executor(
executor: &dyn CommandExecutor,
policy: SourcePolicy,
stderr: &mut dyn Write,
install: impl FnOnce(&DylintToolStatus, &mut dyn Write) -> Result<()>,
) -> Result<()> {
dependency_check::ensure_dylint_tools_with_query(
policy.quiet,
stderr,
|| dependency_check::check_dylint_tools_for_command(executor),
install,
)
}
/// Context needed to attempt prebuilt installation.
pub(crate) struct PrebuiltInstallationContext<'a> {
/// CLI install arguments.
pub(crate) args: &'a InstallArgs,
/// The source-fallback rule, resolved once at the command boundary.
///
/// `InstallArgs::forbids_source_fallback` reads the process environment,
/// so calling it at each decision point would read ambient state three
/// times in one installation and could reach different answers. The
/// environment is read once, into this field, and every decision below
/// consults the answer.
pub(crate) policy: SourcePolicy,
/// Base directory provider.
pub(crate) dirs: &'a dyn BaseDirs,
/// Crates requested for this installation.
pub(crate) requested_crates: &'a [CrateName],
/// Toolchain channel resolved for this install.
pub(crate) toolchain_channel: &'a str,
}
/// Context for recording one successful install in aggregate metrics.
pub(crate) struct MetricsWriteContext<'a> {
/// Whether installer output is suppressed.
pub(crate) quiet: bool,
/// Base directory provider used to locate Whitaker data directory.
pub(crate) dirs: &'a dyn BaseDirs,
/// Terminal install mode (download or build).
pub(crate) install_mode: InstallMode,
/// Elapsed duration for this successful install run.
pub(crate) elapsed: Duration,
}
/// Report a prebuilt failure, or refuse it when a source build is forbidden.
///
/// Every prebuilt failure here used to end the same way: a message, then a
/// local compilation that succeeds. That is a silent success, and in CI it is
/// the defect rather than a degraded mode, so `--no-source-fallback` turns
/// each one into an error naming what was missing and why.
fn prebuilt_unavailable(
policy: SourcePolicy,
artefact: &str,
error: &dyn std::fmt::Display,
stderr: &mut dyn Write,
) -> Result