konfigkoll_script/
engine.rs1use crate::plugins::command::Commands;
2use crate::plugins::error::KError;
3use crate::plugins::package_managers::PackageManagers;
4use crate::plugins::properties::Properties;
5use crate::plugins::settings::Settings;
6use crate::types::Phase;
7use camino::Utf8Path;
8use camino::Utf8PathBuf;
9use color_eyre::Section;
10use color_eyre::SectionExt;
11use eyre::WrapErr;
12use paketkoll_types::backend::Backend;
13use paketkoll_types::backend::Files;
14use paketkoll_types::backend::PackageBackendMap;
15use paketkoll_types::backend::PackageMapMap;
16use paketkoll_types::intern::Interner;
17use rune::Diagnostics;
18use rune::Source;
19use rune::Vm;
20use rune::termcolor::Buffer;
21use rune::termcolor::ColorChoice;
22use rune::termcolor::StandardStream;
23use std::panic::AssertUnwindSafe;
24use std::panic::catch_unwind;
25use std::sync::Arc;
26use std::sync::OnceLock;
27
28#[derive(Debug)]
30pub struct EngineState {
31 pub(crate) properties: Properties,
33 pub(crate) commands: Commands,
35 pub(crate) settings: Arc<Settings>,
37 pub(crate) package_managers: Option<PackageManagers>,
39}
40
41pub(crate) static CFG_PATH: OnceLock<Utf8PathBuf> = OnceLock::new();
43
44impl EngineState {
45 #[must_use]
46 pub fn new(files_path: Utf8PathBuf) -> Self {
47 let settings = Arc::new(Settings::default());
48 Self {
49 properties: Default::default(),
50 commands: Commands::new(files_path, settings.clone()),
51 settings,
52 package_managers: None,
53 }
54 }
55
56 pub fn setup_package_managers(
57 &mut self,
58 package_backends: &PackageBackendMap,
59 file_backend_id: Backend,
60 files_backend: &Arc<dyn Files>,
61 package_maps: &PackageMapMap,
62 interner: &Arc<Interner>,
63 ) {
64 self.package_managers = Some(PackageManagers::create_from(
65 package_backends,
66 file_backend_id,
67 files_backend,
68 package_maps,
69 interner,
70 ));
71 }
72
73 #[must_use]
74 pub fn settings(&self) -> Arc<Settings> {
75 Arc::clone(&self.settings)
76 }
77
78 #[must_use]
79 pub const fn commands(&self) -> &Commands {
80 &self.commands
81 }
82
83 pub fn commands_mut(&mut self) -> &mut Commands {
84 &mut self.commands
85 }
86}
87
88#[derive(Debug)]
90pub struct ScriptEngine {
91 runtime: Arc<rune::runtime::RuntimeContext>,
92 sources: rune::Sources,
93 unit: Arc<rune::Unit>,
95 pub(crate) state: EngineState,
97}
98
99impl ScriptEngine {
100 pub fn create_context() -> Result<rune::Context, rune::ContextError> {
101 let mut context = rune::Context::with_default_modules()?;
102
103 crate::plugins::register_modules(&mut context)?;
105 context.install(rune_modules::json::module(true)?)?;
106 context.install(rune_modules::toml::module(true)?)?;
107 context.install(rune_modules::toml::de::module(true)?)?;
108 context.install(rune_modules::toml::ser::module(true)?)?;
109
110 Ok(context)
111 }
112
113 pub fn new_with_files(config_path: &Utf8Path) -> eyre::Result<Self> {
114 CFG_PATH.set(config_path.to_owned()).map_err(|v| {
115 eyre::eyre!("Failed to set CFG_PATH to {v}, this should not be called more than once")
116 })?;
117 let context = Self::create_context()?;
118
119 let state = EngineState::new(config_path.join("files"));
121
122 let mut diagnostics = Diagnostics::new();
124
125 let mut sources = rune::Sources::new();
126 sources
127 .insert(
128 Source::from_path(config_path.join("main.rn"))
129 .wrap_err("Failed to load main.rn")?,
130 )
131 .wrap_err("Failed to insert source file")?;
132
133 let result = rune::prepare(&mut sources)
134 .with_context(&context)
135 .with_diagnostics(&mut diagnostics)
136 .build();
137
138 if !diagnostics.is_empty() {
139 let mut writer = StandardStream::stderr(ColorChoice::Always);
140 diagnostics.emit(&mut writer, &sources)?;
141 }
142
143 Ok(Self {
145 runtime: Arc::new(context.runtime()?),
146 sources,
147 state,
148 unit: Arc::new(result?),
149 })
150 }
151
152 #[tracing::instrument(level = "info", name = "script", skip(self))]
154 pub async fn run_phase(&mut self, phase: Phase) -> eyre::Result<()> {
155 self.state.commands.phase = phase;
157 let mut vm = Vm::new(self.runtime.clone(), self.unit.clone());
159 tracing::info!("Calling script");
160 let output = match phase {
161 Phase::SystemDiscovery => {
162 vm.async_call(
163 [phase.as_str()],
164 (&mut self.state.properties, self.state.settings.as_ref()),
165 )
166 .await
167 }
168 Phase::Ignores | Phase::ScriptDependencies => {
169 vm.async_call(
170 [phase.as_str()],
171 (&mut self.state.properties, &mut self.state.commands),
172 )
173 .await
174 }
175 Phase::Main => {
176 vm.async_call(
177 [phase.as_str()],
178 (
179 &mut self.state.properties,
180 &mut self.state.commands,
181 self.state
182 .package_managers
183 .as_ref()
184 .expect("Package managers must be set"),
185 ),
186 )
187 .await
188 }
189 };
190 let output = match output {
192 Ok(output) => output,
193 Err(e) => {
194 let err_str = format!("Rune error while executing {phase}: {}", &e);
195 tracing::error!("{}", err_str);
196 let mut writer = Buffer::ansi();
197 e.emit(&mut writer, &self.sources)?;
198
199 let rune_diag =
200 std::str::from_utf8(writer.as_slice().trim_ascii_end())?.to_string();
201
202 return Err(e)
203 .context("Rune runtime error")
204 .section(rune_diag.header(
205 " ━━━━━━━━━━━━━━━━━━━━━━━━ Rune Diagnostics and Backtrace \
206 ━━━━━━━━━━━━━━━━━━━━━━━━\n",
207 ));
208 }
209 };
210 tracing::info!("Returned from script");
211 match output {
213 rune::Value::Result(result) => match result.borrow_ref()?.as_ref() {
214 Ok(_) => (),
215 Err(e) => vm.with(|| try_format_error(phase, e))?,
216 },
217 _ => eyre::bail!("Got non-result from {phase}: {output:?}"),
218 }
219 Ok(())
220 }
221
222 #[inline]
223 #[must_use]
224 pub const fn state(&self) -> &EngineState {
225 &self.state
226 }
227
228 #[inline]
229 pub fn state_mut(&mut self) -> &mut EngineState {
230 &mut self.state
231 }
232}
233
234fn try_format_error(phase: Phase, value: &rune::Value) -> eyre::Result<()> {
238 match value.clone().into_any() {
239 rune::runtime::VmResult::Ok(any) => {
240 if let Ok(mut err) = any.downcast_borrow_mut::<KError>() {
241 tracing::error!("Got error result from {phase}: {}", *err.inner());
242 let err: eyre::Error = err.take_inner();
243 return Err(err);
244 }
245 if let Ok(err) = any.downcast_borrow_ref::<std::io::Error>() {
246 eyre::bail!("Got IO error result from {phase}: {:?}", *err);
247 }
248 let ty = try_get_type_info(value, "error");
249 let formatted = catch_unwind(AssertUnwindSafe(|| format!("{value:?}")));
250 eyre::bail!(
251 "Got error result from {phase}, but it is a unknown error type: {ty}: {any:?}, \
252 formats as: {formatted:?}",
253 );
254 }
255 rune::runtime::VmResult::Err(not_any) => {
256 tracing::error!(
257 "Got error result from {phase}, it was not an Any: {not_any:?}. Trying other \
258 approaches at printing the error."
259 );
260 }
261 }
262 let formatted = catch_unwind(AssertUnwindSafe(|| {
264 format!("Got error result from {phase}: {value:?}")
265 }));
266 match formatted {
267 Ok(str) => eyre::bail!(str),
268 Err(_) => {
269 let ty = try_get_type_info(value, "error");
270 eyre::bail!(
271 "Got error result from {phase}, but got a panic while attempting to format said \
272 error for printing, {ty}",
273 );
274 }
275 }
276}
277
278fn try_get_type_info(e: &rune::Value, what: &str) -> String {
280 match e.type_info() {
281 rune::runtime::VmResult::Ok(ty) => format!("type info for {what}: {ty:?}"),
282 rune::runtime::VmResult::Err(err) => {
283 format!("failed getting type info for {what}: {err:?}")
284 }
285 }
286}