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