use sim_kernel::{Args, Cx, ExportKind, ExportRecord, ExportState, Symbol};
use crate::{
CliBoot, CliEnvelope, CliError, LoadReceipt, LoadReceiptRole, LoadSession,
envelope::cli_envelope_value, exit::value_to_exit_code,
};
pub const CLI_MAIN_ENTRYPOINT: &str = "cli/main";
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CliEntrypoint {
pub lib: Symbol,
pub symbol: Symbol,
}
impl LoadSession {
pub fn run_loaded_boot(&mut self, boot: &CliBoot) -> Result<i32, CliError> {
if boot.config_report.is_some() {
print!("{}", self.run_config_report(boot)?);
return Ok(0);
}
if boot.list || boot.inspect.is_some() {
print!("{}", self.run_loaded_introspection(boot)?);
return Ok(0);
}
self.load_boot(boot)?;
self.run_loaded_handoff(&boot.envelope())
}
pub fn run_loaded_handoff(&mut self, envelope: &CliEnvelope) -> Result<i32, CliError> {
let entrypoint = select_cli_entrypoint(self.receipts(), envelope.verb.as_deref())?;
run_loaded_cli(self.cx_mut(), &entrypoint, envelope)
}
}
pub fn cli_main_entrypoint_symbol(name: &str) -> Symbol {
Symbol::qualified("cli", format!("main/{name}"))
}
pub fn select_cli_entrypoint(
receipts: &[LoadReceipt],
verb: Option<&str>,
) -> Result<CliEntrypoint, CliError> {
select_from_role(
receipts,
|role| matches!(role, LoadReceiptRole::Library),
verb,
)
.or_else(|| {
select_from_role(
receipts,
|role| matches!(role, LoadReceiptRole::BootCodec { .. }),
verb,
)
})
.ok_or_else(|| no_entrypoint_error(receipts, verb))
}
pub fn run_loaded_cli(
cx: &mut Cx,
entrypoint: &CliEntrypoint,
envelope: &CliEnvelope,
) -> Result<i32, CliError> {
let envelope = cli_envelope_value(cx, envelope)?;
let result = cx
.call_function(&entrypoint.symbol, Args::new(vec![envelope]))
.map_err(|err| {
CliError::new(format!(
"cli handoff failed for {} from {}: {err}",
entrypoint.symbol, entrypoint.lib
))
})?;
value_to_exit_code(cx, result)
}
fn select_from_role(
receipts: &[LoadReceipt],
role_matches: impl Fn(&LoadReceiptRole) -> bool + Copy,
verb: Option<&str>,
) -> Option<CliEntrypoint> {
if let Some(verb) = verb {
find_entrypoint(receipts, role_matches, |record| {
record_claims_exact_cli_main(record, verb)
})
.or_else(|| find_entrypoint(receipts, role_matches, record_claims_generic_cli_main))
} else {
find_entrypoint(receipts, role_matches, record_claims_cli_main)
}
}
fn find_entrypoint(
receipts: &[LoadReceipt],
role_matches: impl Fn(&LoadReceiptRole) -> bool,
record_matches: impl Fn(&ExportRecord) -> bool + Copy,
) -> Option<CliEntrypoint> {
receipts
.iter()
.filter(|receipt| role_matches(&receipt.role))
.find_map(|receipt| entrypoint_for_receipt(receipt, record_matches))
}
fn entrypoint_for_receipt(
receipt: &LoadReceipt,
record_matches: impl Fn(&ExportRecord) -> bool + Copy,
) -> Option<CliEntrypoint> {
receipt
.exports
.iter()
.find(|record| record_matches(record))
.map(|record| CliEntrypoint {
lib: receipt.manifest.id.clone(),
symbol: record.symbol.clone(),
})
}
fn record_claims_exact_cli_main(record: &ExportRecord, verb: &str) -> bool {
record.kind == ExportKind::named(ExportKind::FUNCTION)
&& matches!(record.state, ExportState::Resolved { .. })
&& record.symbol == cli_main_entrypoint_symbol(verb)
}
fn record_claims_generic_cli_main(record: &ExportRecord) -> bool {
record.kind == ExportKind::named(ExportKind::FUNCTION)
&& matches!(record.state, ExportState::Resolved { .. })
&& record.symbol == Symbol::new(CLI_MAIN_ENTRYPOINT)
}
fn record_claims_cli_main(record: &ExportRecord) -> bool {
record.kind == ExportKind::named(ExportKind::FUNCTION)
&& matches!(record.state, ExportState::Resolved { .. })
&& symbol_claims_cli_main(&record.symbol)
}
fn symbol_claims_cli_main(symbol: &Symbol) -> bool {
let symbol = symbol.as_qualified_str();
symbol == CLI_MAIN_ENTRYPOINT || symbol.starts_with(&format!("{CLI_MAIN_ENTRYPOINT}/"))
}
fn no_entrypoint_error(receipts: &[LoadReceipt], verb: Option<&str>) -> CliError {
let loaded = if receipts.is_empty() {
"none".to_owned()
} else {
receipts
.iter()
.map(|receipt| receipt.manifest.id.to_string())
.collect::<Vec<_>>()
.join(", ")
};
let requested = verb
.map(|verb| format!(" {CLI_MAIN_ENTRYPOINT}/{verb} or {CLI_MAIN_ENTRYPOINT}"))
.unwrap_or_else(|| format!(" {CLI_MAIN_ENTRYPOINT}"));
CliError::new(format!(
"no loaded lib claims{requested}; loaded libs: {loaded}; load one with --load"
))
}