Skip to main content

sim_run_core/
handoff.rs

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
8/// Symbol prefix a loaded lib claims to own command-line execution.
9pub const CLI_MAIN_ENTRYPOINT: &str = "cli/main";
10
11/// A loaded function that owns command-line execution.
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct CliEntrypoint {
14    /// Library that exported the entrypoint.
15    pub lib: Symbol,
16    /// Exported function symbol invoked for the handoff.
17    pub symbol: Symbol,
18}
19
20impl LoadSession {
21    /// Loads a boot session and runs the selected loaded CLI entrypoint.
22    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    /// Runs the selected loaded CLI entrypoint for an already-loaded session.
32    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
38/// Builds the qualified `cli/main/NAME` entrypoint symbol for a named lib.
39pub fn cli_main_entrypoint_symbol(name: &str) -> Symbol {
40    Symbol::qualified("cli", format!("main/{name}"))
41}
42
43/// Selects the loaded entrypoint that claims [`CLI_MAIN_ENTRYPOINT`].
44///
45/// Prefers a `--load` library over the boot codec, and returns an error
46/// when no loaded lib claims the entrypoint.
47pub 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
61/// Calls a loaded entrypoint with the boot envelope and returns its exit code.
62pub 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}