sim_lib_compute_cli/
lib.rs1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3mod args;
11mod envelope;
12mod evidence;
13mod render;
14
15use std::sync::Arc;
16
17use sim_kernel::{
18 AbiVersion, Args, Callable, CapabilityName, Cx, Error, Export, Lib, LibManifest, LibTarget,
19 Linker, LoadCx, Object, ObjectCompat, Result, Symbol, Value, Version,
20};
21
22pub use args::{ComputeCommand, OutputMode, ProfileAction, parse_compute_args};
23pub use render::help;
24
25use crate::{
26 envelope::envelope_args,
27 evidence::{profile_evidence, provider_rows, recipe_evidence},
28 render::{render_profile, render_providers, render_recipe},
29};
30
31pub fn compute_device_capability() -> CapabilityName {
33 CapabilityName::new("compute.device")
34}
35
36pub fn compute_profile_read_capability() -> CapabilityName {
38 CapabilityName::new("compute.profile.read")
39}
40
41pub fn compute_profile_write_capability() -> CapabilityName {
43 CapabilityName::new("compute.profile.write")
44}
45
46pub fn compute_cli_lib_symbol() -> Symbol {
48 Symbol::qualified("lib", "compute-cli")
49}
50
51pub fn compute_entrypoint_symbol() -> Symbol {
53 Symbol::qualified("cli", "main/compute")
54}
55
56#[derive(Clone, Debug, Eq, PartialEq)]
58pub struct ComputeCliError {
59 message: String,
60}
61
62impl ComputeCliError {
63 pub fn new(message: impl Into<String>) -> Self {
65 Self {
66 message: message.into(),
67 }
68 }
69
70 fn from_kernel(error: Error) -> Self {
71 Self::new(error.to_string())
72 }
73}
74
75impl std::fmt::Display for ComputeCliError {
76 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 formatter.write_str(&self.message)
78 }
79}
80
81impl std::error::Error for ComputeCliError {}
82
83#[derive(Clone, Default)]
85pub struct ComputeCliLib {
86 profile_store: Option<Value>,
87}
88
89impl ComputeCliLib {
90 pub fn new() -> Self {
92 Self::default()
93 }
94
95 pub fn with_profile_store(profile_store: Value) -> Self {
97 Self {
98 profile_store: Some(profile_store),
99 }
100 }
101}
102
103impl Lib for ComputeCliLib {
104 fn manifest(&self) -> LibManifest {
105 LibManifest {
106 id: compute_cli_lib_symbol(),
107 version: Version(env!("CARGO_PKG_VERSION").to_owned()),
108 abi: AbiVersion { major: 0, minor: 1 },
109 target: LibTarget::HostRegistered,
110 requires: Vec::new(),
111 capabilities: Vec::new(),
112 exports: vec![Export::Function {
113 symbol: compute_entrypoint_symbol(),
114 function_id: None,
115 }],
116 }
117 }
118
119 fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
120 linker.function_value(
121 compute_entrypoint_symbol(),
122 cx.factory().opaque(Arc::new(ComputeEntrypoint {
123 profile_store: self.profile_store.clone(),
124 }))?,
125 )?;
126 Ok(())
127 }
128}
129
130#[derive(Clone)]
131struct ComputeEntrypoint {
132 profile_store: Option<Value>,
133}
134
135impl Object for ComputeEntrypoint {
136 fn display(&self, _cx: &mut Cx) -> Result<String> {
137 Ok("cli/main/compute".to_owned())
138 }
139
140 fn as_any(&self) -> &dyn std::any::Any {
141 self
142 }
143}
144
145impl ObjectCompat for ComputeEntrypoint {
146 fn as_callable(&self) -> Option<&dyn Callable> {
147 Some(self)
148 }
149}
150
151impl Callable for ComputeEntrypoint {
152 fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
153 let Some(envelope) = args.values().first() else {
154 return Err(Error::Eval("missing compute envelope".to_owned()));
155 };
156 let args = envelope_args(cx, envelope)?;
157 let command = parse_compute_args(&args).map_err(|err| Error::Eval(err.to_string()))?;
158 let output = run_command(cx, self.profile_store.as_ref(), &command)
159 .map_err(|err| Error::Eval(err.to_string()))?;
160 print!("{output}");
161 cx.factory().bool(true)
162 }
163}
164
165pub fn run_command(
167 cx: &mut Cx,
168 profile_store: Option<&Value>,
169 command: &ComputeCommand,
170) -> std::result::Result<String, ComputeCliError> {
171 match command {
172 ComputeCommand::Help => Ok(help().to_owned()),
173 ComputeCommand::Devices(selection) | ComputeCommand::Probe(selection) => {
174 cx.require(&compute_device_capability())
175 .map_err(ComputeCliError::from_kernel)?;
176 Ok(render_providers(command, &provider_rows(cx, selection)))
177 }
178 ComputeCommand::Profile(request) => {
179 let capability = match request.action {
180 ProfileAction::Save => compute_profile_write_capability(),
181 ProfileAction::List | ProfileAction::Read => compute_profile_read_capability(),
182 };
183 cx.require(&capability)
184 .map_err(ComputeCliError::from_kernel)?;
185 let evidence = profile_evidence(cx, profile_store, request)?;
186 Ok(render_profile(command, &evidence))
187 }
188 ComputeCommand::Explain(request) => {
189 cx.require(&compute_device_capability())
190 .map_err(ComputeCliError::from_kernel)?;
191 cx.require(&compute_profile_read_capability())
192 .map_err(ComputeCliError::from_kernel)?;
193 let evidence = profile_evidence(cx, profile_store, request)?;
194 Ok(render_profile(command, &evidence))
195 }
196 ComputeCommand::Recipe(_) => {
197 cx.require(&compute_device_capability())
198 .map_err(ComputeCliError::from_kernel)?;
199 Ok(render_recipe(command, &recipe_evidence()))
200 }
201 }
202}
203
204pub static RECIPES: sim_cookbook::EmbeddedDir =
206 include!(concat!(env!("OUT_DIR"), "/cookbook_recipes.rs"));
207
208#[cfg(test)]
209mod tests;