use super::cargo::{absolute, account_session, inherited_environment, run_cargo};
use crate::config::{CliSettings, Config};
use crate::session::{self, CacheSession};
use crate::util::is_checkout_root;
use eyre::Result;
use std::collections::BTreeMap;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
#[derive(usage_rs::Args)]
pub(super) struct ExecArgs {
#[usage(long, value_name = "DIR")]
project_root: Option<String>,
#[usage(value_name = "COMMAND", required = true, double_dash = "automatic")]
pub(super) command: Vec<String>,
}
pub(super) fn run(config: &Config, settings: &CliSettings, args: &ExecArgs) -> Result<ExitCode> {
let Some((program, arguments)) = args.command.split_first() else {
eyre::bail!("exec needs a command to run");
};
let program: OsString = program.into();
let working_dir = std::env::current_dir()?;
let project_root = match &args.project_root {
Some(root) => absolute(&working_dir, root),
None => discover_project_root(&working_dir),
};
let mut config = config.clone();
config.apply_workspace_policy(&project_root)?;
let config = &config;
if !config.cc {
log::warn!("the C and C++ cache is disabled, so this command is not cached");
return run_cargo(&program, arguments, BTreeMap::new());
}
crate::storage::check_cache(config)?;
let session_dir = tempfile::Builder::new().prefix("mbx-session-").tempdir()?;
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?;
let session_outcome = runtime.block_on(async {
let mut arguments: Vec<OsString> = arguments.iter().map(OsString::from).collect();
let shims = session::install_path_shims(&config.shims_dir)?;
if shims.is_none() && !session::cmake::is_cmake(&program) {
log::warn!("no C or C++ compiler was found on PATH, so this command is not cached");
return Ok((run_cargo(&program, &arguments, BTreeMap::new()), None));
}
let session = CacheSession::start_with_events_limit_and_gc(
session_dir.path(),
config,
settings.events_max_size,
settings.retention.min_free,
)
.await?;
let mut environment = inherited_environment(|name| std::env::var(name).ok(), &working_dir);
let run = session
.begin_exec(
&project_root,
&args.command,
shims.as_ref(),
&mut environment,
)
.await;
session.prepare_exec_cmake(&program, &mut arguments, &mut environment);
let status = run_cargo(&program, &arguments, environment);
if let Some(run) = run
&& let Err(error) = run.commit().await
{
log::warn!("the completed build was not fully recorded: {error}");
}
let stats = match session.finish().await {
Ok(stats) => {
crate::session::display_stats(&stats, config, settings.summary);
Some(stats)
}
Err(error) => {
log::warn!("the cache session did not shut down cleanly: {error}");
None
}
};
Ok((status, stats))
});
account_session(config, settings, session_outcome, None)
}
pub(super) fn discover_project_root(working_dir: &Path) -> PathBuf {
let mut directory = working_dir;
loop {
if is_checkout_root(directory) {
return directory.to_path_buf();
}
match directory.parent() {
Some(parent) => directory = parent,
None => return working_dir.to_path_buf(),
}
}
}