1use sim_kernel::{Args, Cx, ExportKind, ExportRecord, ExportState, Symbol};
2
3use crate::{
4 CliBoot, CliEnvelope, CliError, LoadReceipt, LoadReceiptRole, LoadSession,
5 envelope::cli_envelope_value, exit::value_to_exit_code,
6};
7
8pub const CLI_MAIN_ENTRYPOINT: &str = "cli/main";
10
11#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct CliEntrypoint {
14 pub lib: Symbol,
16 pub symbol: Symbol,
18}
19
20impl LoadSession {
21 pub fn run_loaded_boot(&mut self, boot: &CliBoot) -> Result<i32, CliError> {
23 if boot.list || boot.inspect.is_some() {
24 print!("{}", self.run_loaded_introspection(boot)?);
25 return Ok(0);
26 }
27 self.load_boot(boot)?;
28 self.run_loaded_handoff(&boot.envelope())
29 }
30
31 pub fn run_loaded_handoff(&mut self, envelope: &CliEnvelope) -> Result<i32, CliError> {
33 let entrypoint = select_cli_entrypoint(self.receipts())?;
34 run_loaded_cli(self.cx_mut(), &entrypoint, envelope)
35 }
36}
37
38pub fn cli_main_entrypoint_symbol(name: &str) -> Symbol {
40 Symbol::qualified("cli", format!("main/{name}"))
41}
42
43pub fn select_cli_entrypoint(receipts: &[LoadReceipt]) -> Result<CliEntrypoint, CliError> {
48 receipts
49 .iter()
50 .filter(|receipt| matches!(receipt.role, LoadReceiptRole::Library))
51 .find_map(entrypoint_for_receipt)
52 .or_else(|| {
53 receipts
54 .iter()
55 .filter(|receipt| matches!(receipt.role, LoadReceiptRole::BootCodec { .. }))
56 .find_map(entrypoint_for_receipt)
57 })
58 .ok_or_else(|| no_entrypoint_error(receipts))
59}
60
61pub fn run_loaded_cli(
63 cx: &mut Cx,
64 entrypoint: &CliEntrypoint,
65 envelope: &CliEnvelope,
66) -> Result<i32, CliError> {
67 let envelope = cli_envelope_value(cx, envelope)?;
68 let result = cx
69 .call_function(&entrypoint.symbol, Args::new(vec![envelope]))
70 .map_err(|err| {
71 CliError::new(format!(
72 "cli handoff failed for {} from {}: {err}",
73 entrypoint.symbol, entrypoint.lib
74 ))
75 })?;
76 value_to_exit_code(cx, result)
77}
78
79fn entrypoint_for_receipt(receipt: &LoadReceipt) -> Option<CliEntrypoint> {
80 receipt
81 .exports
82 .iter()
83 .find(|record| record_claims_cli_main(record))
84 .map(|record| CliEntrypoint {
85 lib: receipt.manifest.id.clone(),
86 symbol: record.symbol.clone(),
87 })
88}
89
90fn record_claims_cli_main(record: &ExportRecord) -> bool {
91 record.kind == ExportKind::named(ExportKind::FUNCTION)
92 && matches!(record.state, ExportState::Resolved { .. })
93 && symbol_claims_cli_main(&record.symbol)
94}
95
96fn symbol_claims_cli_main(symbol: &Symbol) -> bool {
97 let symbol = symbol.as_qualified_str();
98 symbol == CLI_MAIN_ENTRYPOINT || symbol.starts_with(&format!("{CLI_MAIN_ENTRYPOINT}/"))
99}
100
101fn no_entrypoint_error(receipts: &[LoadReceipt]) -> CliError {
102 let loaded = if receipts.is_empty() {
103 "none".to_owned()
104 } else {
105 receipts
106 .iter()
107 .map(|receipt| receipt.manifest.id.to_string())
108 .collect::<Vec<_>>()
109 .join(", ")
110 };
111 CliError::new(format!(
112 "no loaded lib claims {CLI_MAIN_ENTRYPOINT}; loaded libs: {loaded}; load one with --load"
113 ))
114}