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.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    /// Runs the selected loaded CLI entrypoint for an already-loaded session.
36    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
42/// Builds the qualified `cli/main/NAME` entrypoint symbol for a named lib.
43pub fn cli_main_entrypoint_symbol(name: &str) -> Symbol {
44    Symbol::qualified("cli", format!("main/{name}"))
45}
46
47/// Selects the loaded entrypoint that claims [`CLI_MAIN_ENTRYPOINT`].
48///
49/// Prefers a `--load` library over the boot codec, and returns an error
50/// when no loaded lib claims the entrypoint.
51pub 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
65/// Calls a loaded entrypoint with the boot envelope and returns its exit code.
66pub 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}