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.config_report.is_some() {
24 print!("{}", self.run_config_report(boot)?);
25 return Ok(0);
26 }
27 if boot.list || boot.inspect.is_some() {
28 print!("{}", self.run_loaded_introspection(boot)?);
29 return Ok(0);
30 }
31 self.load_boot(boot)?;
32 self.run_loaded_handoff(&boot.envelope())
33 }
34
35 pub fn run_loaded_handoff(&mut self, envelope: &CliEnvelope) -> Result<i32, CliError> {
37 let entrypoint = select_cli_entrypoint(self.receipts())?;
38 run_loaded_cli(self.cx_mut(), &entrypoint, envelope)
39 }
40}
41
42pub fn cli_main_entrypoint_symbol(name: &str) -> Symbol {
44 Symbol::qualified("cli", format!("main/{name}"))
45}
46
47pub fn select_cli_entrypoint(receipts: &[LoadReceipt]) -> Result<CliEntrypoint, CliError> {
52 receipts
53 .iter()
54 .filter(|receipt| matches!(receipt.role, LoadReceiptRole::Library))
55 .find_map(entrypoint_for_receipt)
56 .or_else(|| {
57 receipts
58 .iter()
59 .filter(|receipt| matches!(receipt.role, LoadReceiptRole::BootCodec { .. }))
60 .find_map(entrypoint_for_receipt)
61 })
62 .ok_or_else(|| no_entrypoint_error(receipts))
63}
64
65pub fn run_loaded_cli(
67 cx: &mut Cx,
68 entrypoint: &CliEntrypoint,
69 envelope: &CliEnvelope,
70) -> Result<i32, CliError> {
71 let envelope = cli_envelope_value(cx, envelope)?;
72 let result = cx
73 .call_function(&entrypoint.symbol, Args::new(vec![envelope]))
74 .map_err(|err| {
75 CliError::new(format!(
76 "cli handoff failed for {} from {}: {err}",
77 entrypoint.symbol, entrypoint.lib
78 ))
79 })?;
80 value_to_exit_code(cx, result)
81}
82
83fn entrypoint_for_receipt(receipt: &LoadReceipt) -> Option<CliEntrypoint> {
84 receipt
85 .exports
86 .iter()
87 .find(|record| record_claims_cli_main(record))
88 .map(|record| CliEntrypoint {
89 lib: receipt.manifest.id.clone(),
90 symbol: record.symbol.clone(),
91 })
92}
93
94fn record_claims_cli_main(record: &ExportRecord) -> bool {
95 record.kind == ExportKind::named(ExportKind::FUNCTION)
96 && matches!(record.state, ExportState::Resolved { .. })
97 && symbol_claims_cli_main(&record.symbol)
98}
99
100fn symbol_claims_cli_main(symbol: &Symbol) -> bool {
101 let symbol = symbol.as_qualified_str();
102 symbol == CLI_MAIN_ENTRYPOINT || symbol.starts_with(&format!("{CLI_MAIN_ENTRYPOINT}/"))
103}
104
105fn no_entrypoint_error(receipts: &[LoadReceipt]) -> CliError {
106 let loaded = if receipts.is_empty() {
107 "none".to_owned()
108 } else {
109 receipts
110 .iter()
111 .map(|receipt| receipt.manifest.id.to_string())
112 .collect::<Vec<_>>()
113 .join(", ")
114 };
115 CliError::new(format!(
116 "no loaded lib claims {CLI_MAIN_ENTRYPOINT}; loaded libs: {loaded}; load one with --load"
117 ))
118}