From 328371e2adebaba8ce68091391a3133fa0a2a6af Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Wed, 17 Jan 2024 17:19:10 +0100 Subject: [PATCH] diagnostic improved, test config partially made, path renamings --- Cargo.lock | 42 ++--- Cargo.toml | 4 +- plugin_test_config.json5 | 62 +++++++- plugins/zenoh-backend-example/Cargo.toml | 2 +- plugins/zenoh-backend-traits/src/config.rs | 3 + .../src/backends_mgt.rs | 2 +- .../zenoh-plugin-storage-manager/src/lib.rs | 38 ++--- .../src/replica/storage.rs | 26 ++-- .../src/storages_mgt.rs | 2 +- .../src/manager/dynamic_plugin.rs | 29 ++-- plugins/zenoh-plugin-trait/src/vtable.rs | 146 +++++++++++++----- 11 files changed, 240 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e73f7976e..a03e85fd79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4607,6 +4607,27 @@ dependencies = [ "zenoh-util", ] +[[package]] +name = "zenoh-backend-example" +version = "0.11.0-dev" +dependencies = [ + "async-std", + "async-trait", + "clap", + "const_format", + "env_logger", + "futures", + "git-version", + "log", + "serde_json", + "zenoh", + "zenoh-core", + "zenoh-plugin-trait", + "zenoh-result", + "zenoh-util", + "zenoh_backend_traits", +] + [[package]] name = "zenoh-buffers" version = "0.11.0-dev" @@ -4983,27 +5004,6 @@ dependencies = [ "zenoh-util", ] -[[package]] -name = "zenoh-plugin-storage-example" -version = "0.11.0-dev" -dependencies = [ - "async-std", - "async-trait", - "clap", - "const_format", - "env_logger", - "futures", - "git-version", - "log", - "serde_json", - "zenoh", - "zenoh-core", - "zenoh-plugin-trait", - "zenoh-result", - "zenoh-util", - "zenoh_backend_traits", -] - [[package]] name = "zenoh-plugin-storage-manager" version = "0.11.0-dev" diff --git a/Cargo.toml b/Cargo.toml index f6249de921..918c432073 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,8 +39,8 @@ members = [ "io/zenoh-links/zenoh-link-ws/", "io/zenoh-links/zenoh-link-unixpipe/", "io/zenoh-transport", - "plugins/example-plugin", - "plugins/example-storage-plugin", + "plugins/zenoh-backend-example", + "plugins/zenoh-plugin-example", "plugins/zenoh-backend-traits", "plugins/zenoh-plugin-rest", "plugins/zenoh-plugin-storage-manager", diff --git a/plugin_test_config.json5 b/plugin_test_config.json5 index e5575c905d..9461b9acce 100644 --- a/plugin_test_config.json5 +++ b/plugin_test_config.json5 @@ -1,13 +1,67 @@ +// Config for testing all available plugins +// +// To run it do these steps: +// +// - Clone these repositories: +// ``` +// git clone https://github.com/eclipse-zenoh/zenoh.git +// git clone https://github.com/eclipse-zenoh/zenoh-backend-influxdb.git +// git clone https://github.com/eclipse-zenoh/zenoh-backend-rocksdb.git +// git clone https://github.com/eclipse-zenoh/zenoh-backend-filesystem.git +// git clone https://github.com/eclipse-zenoh/zenoh-plugin-webserver.git +// git clone https://github.com/eclipse-zenoh/zenoh-plugin-mqtt.git +// git clone https://github.com/eclipse-zenoh/zenoh-plugin-dds.git +// git clone https://github.com/eclipse-zenoh/zenoh-plugin-ros1.git +// git clone https://github.com/eclipse-zenoh/zenoh-plugin-ros2dds.git +// ``` +// +// - Init submodules for zenoh-plugin-ros1 +// ``` +// cd zenoh-plugin-ros1 +// git submodule init +// git submodule update +// ``` +// +// - Build projects +// ``` +// cd zenoh && cargo build && cd .. +// cd zenoh-backend-influxdb && cargo build && cd .. +// ... +// ``` +// +// - Run the zenohd server with this config file. +// Explicit setting RUST_LOG=info is important: without it the logs with default le are printed by zenohd itsellf, but not by plugins. +// ``` +// cd zenoh +// RUST_LOG=info cargo run -- --config plugin_test_config.json5 +// ``` +// +// { "plugins": { + // demonstrate "nof found" error + "not_found": { + }, + // example plugin, see "plugins/zenog-plugin-example" + "example": { + }, + // rest plugin, see "plugins/zenoh-plugin-rest" "rest": { "http_port": 8080, }, + // storage mangaer plugin, see "plugins/zenoh-plugin-storage-manager" + // supports different backends implemented as plugins also "storage_manager": { + backend_search_dirs: [ + "../zenoh-backend-influxdb/target/debug", + ], "volumes": { + // example backend, see "plugins/zenoh-backend-example" "example": { - "__path__": ["target/debug/libzenoh_backend_example.so","target/debug/libzenoh_backend_example.dylib"], - } + }, + // influxdb backend from "../zenoh-backend-influxdb" + "influxdb": { + }, }, "storages": { "memory": { @@ -20,10 +74,6 @@ }, } }, - "not_found": { - }, - "example": { - }, "dds": { "__path__": ["../zenoh-plugin-dds/target/debug/libzenoh_plugin_dds.so","../zenoh-plugin-dds/target/debug/libzenoh_plugin_dds.dylib"], } diff --git a/plugins/zenoh-backend-example/Cargo.toml b/plugins/zenoh-backend-example/Cargo.toml index 0e532691c8..eac0e0d803 100644 --- a/plugins/zenoh-backend-example/Cargo.toml +++ b/plugins/zenoh-backend-example/Cargo.toml @@ -13,7 +13,7 @@ # [package] rust-version = { workspace = true } -name = "zenoh-plugin-storage-example" +name = "zenoh-backend-example" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } diff --git a/plugins/zenoh-backend-traits/src/config.rs b/plugins/zenoh-backend-traits/src/config.rs index e3ccd2f524..f2f05a8eef 100644 --- a/plugins/zenoh-backend-traits/src/config.rs +++ b/plugins/zenoh-backend-traits/src/config.rs @@ -324,6 +324,9 @@ impl VolumeConfig { } } impl StorageConfig { + pub fn name(&self) -> &str { + &self.name + } pub fn to_json_value(&self) -> Value { let mut result = serde_json::Map::new(); result.insert("key_expr".into(), Value::String(self.key_expr.to_string())); diff --git a/plugins/zenoh-plugin-storage-manager/src/backends_mgt.rs b/plugins/zenoh-plugin-storage-manager/src/backends_mgt.rs index 85c4ebf48d..aa7260e868 100644 --- a/plugins/zenoh-plugin-storage-manager/src/backends_mgt.rs +++ b/plugins/zenoh-plugin-storage-manager/src/backends_mgt.rs @@ -35,7 +35,7 @@ pub(crate) async fn create_and_start_storage( out_interceptor: Option Sample + Send + Sync>>, zenoh: Arc, ) -> ZResult> { - log::trace!("Create storage {}", &admin_key); + log::trace!("Create storage '{}'", &admin_key); let capability = backend.get_capability(); let storage = backend.create_storage(config.clone()).await?; let store_intercept = StoreIntercept { diff --git a/plugins/zenoh-plugin-storage-manager/src/lib.rs b/plugins/zenoh-plugin-storage-manager/src/lib.rs index d718258df7..117779dba8 100644 --- a/plugins/zenoh-plugin-storage-manager/src/lib.rs +++ b/plugins/zenoh-plugin-storage-manager/src/lib.rs @@ -124,28 +124,28 @@ impl StorageRuntimeInner { plugins_manager, }; new_self - .spawn_volume(VolumeConfig { + .spawn_volume(&VolumeConfig { name: MEMORY_BACKEND_NAME.into(), backend: None, paths: None, required: false, rest: Default::default(), }) - .map_or_else(|e| log::error!("Cannot spawn memory volume: {}", e), |_| ()); - for volume in volumes { + .map_or_else(|e| log::error!("Cannot spawn static volume '{}': {}", MEMORY_BACKEND_NAME, e), |_| ()); + for volume in &volumes { new_self .spawn_volume(volume) - .map_or_else(|e| log::error!("Cannot spawn volume: {}", e), |_| ()); + .map_or_else(|e| log::error!("Cannot spawn volume '{}': {}", volume.name(), e), |_| ()); } - for storage in storages { + for storage in &storages { new_self .spawn_storage(storage) - .map_or_else(|e| log::error!("Cannot spawn storage: {}", e), |_| ()); + .map_or_else(|e| log::error!("Cannot spawn storage '{}': {}", storage.name(), e), |_| ()); } Ok(new_self) } fn update>(&mut self, diffs: I) -> ZResult<()> { - for diff in diffs { + for ref diff in diffs { match diff { ConfigDiff::DeleteVolume(volume) => self.kill_volume(&volume.name)?, ConfigDiff::AddVolume(volume) => { @@ -159,7 +159,7 @@ impl StorageRuntimeInner { } fn kill_volume>(&mut self, name: T) -> ZResult<()> { let name = name.as_ref(); - log::info!("Killing volume {}", name); + log::info!("Killing volume '{}'", name); if let Some(storages) = self.storages.remove(name) { async_std::task::block_on(futures::future::join_all( storages @@ -169,15 +169,15 @@ impl StorageRuntimeInner { } self.plugins_manager .started_plugin_mut(name) - .ok_or(format!("Cannot find volume {} to stop it", name))? + .ok_or(format!("Cannot find volume '{}' to stop it", name))? .stop(); Ok(()) } - fn spawn_volume(&mut self, config: VolumeConfig) -> ZResult<()> { + fn spawn_volume(&mut self, config: &VolumeConfig) -> ZResult<()> { let volume_id = config.name(); let backend_name = config.backend(); log::info!( - "Spawning volume {} with backend {}", + "Spawning volume '{}' with backend '{}'", volume_id, backend_name ); @@ -191,16 +191,16 @@ impl StorageRuntimeInner { .declare_dynamic_plugin_by_name(volume_id, backend_name)? }; let loaded = declared.load()?; - loaded.start(&config)?; + loaded.start(config)?; Ok(()) } - fn kill_storage(&mut self, config: StorageConfig) { + fn kill_storage(&mut self, config: &StorageConfig) { let volume = &config.volume_id; - log::info!("Killing storage {} from volume {}", config.name, volume); + log::info!("Killing storage '{}' from volume '{}'", config.name, volume); if let Some(storages) = self.storages.get_mut(volume) { if let Some(storage) = storages.get_mut(&config.name) { log::debug!( - "Closing storage {} from volume {}", + "Closing storage '{}' from volume '{}'", config.name, config.volume_id ); @@ -209,19 +209,19 @@ impl StorageRuntimeInner { } } } - fn spawn_storage(&mut self, storage: StorageConfig) -> ZResult<()> { + fn spawn_storage(&mut self, storage: &StorageConfig) -> ZResult<()> { let admin_key = self.status_key() + "/storages/" + &storage.name; let volume_id = storage.volume_id.clone(); let backend = self .plugins_manager .started_plugin(&volume_id) .ok_or(format!( - "Cannot find volume {} to spawn storage {}", + "Cannot find volume '{}' to spawn storage '{}'", volume_id, storage.name ))?; let storage_name = storage.name.clone(); log::info!( - "Spawning storage {} from volume {} with backend {}", + "Spawning storage '{}' from volume '{}' with backend '{}'", storage_name, volume_id, backend.name() @@ -230,7 +230,7 @@ impl StorageRuntimeInner { let out_interceptor = backend.instance().outgoing_data_interceptor(); let stopper = async_std::task::block_on(create_and_start_storage( admin_key, - storage, + storage.clone(), backend.instance(), in_interceptor, out_interceptor, diff --git a/plugins/zenoh-plugin-storage-manager/src/replica/storage.rs b/plugins/zenoh-plugin-storage-manager/src/replica/storage.rs index 16f5fd4a36..84f592d899 100644 --- a/plugins/zenoh-plugin-storage-manager/src/replica/storage.rs +++ b/plugins/zenoh-plugin-storage-manager/src/replica/storage.rs @@ -146,7 +146,7 @@ impl StorageService { let storage_sub = match self.session.declare_subscriber(&self.key_expr).res().await { Ok(storage_sub) => storage_sub, Err(e) => { - log::error!("Error starting storage {}: {}", self.name, e); + log::error!("Error starting storage '{}': {}", self.name, e); return; } }; @@ -161,7 +161,7 @@ impl StorageService { { Ok(storage_queryable) => storage_queryable, Err(e) => { - log::error!("Error starting storage {}: {}", self.name, e); + log::error!("Error starting storage '{}': {}", self.name, e); return; } }; @@ -205,7 +205,7 @@ impl StorageService { message = rx.recv_async() => { match message { Ok(StorageMessage::Stop) => { - log::trace!("Dropping storage {}", self.name); + log::trace!("Dropping storage '{}'", self.name); return }, Ok(StorageMessage::GetStatus(tx)) => { @@ -243,7 +243,7 @@ impl StorageService { message = rx.recv_async() => { match message { Ok(StorageMessage::Stop) => { - log::trace!("Dropping storage {}", self.name); + log::trace!("Dropping storage '{}'", self.name); return }, Ok(StorageMessage::GetStatus(tx)) => { @@ -458,7 +458,7 @@ impl StorageService { } Err(e) => { log::warn!( - "Storage {} raised an error fetching a query on key {} : {}", + "Storage '{}' raised an error fetching a query on key {} : {}", self.name, key_expr, e @@ -527,14 +527,14 @@ impl StorageService { }; if let Err(e) = q.reply(Ok(sample)).res().await { log::warn!( - "Storage {} raised an error replying a query: {}", + "Storage '{}' raised an error replying a query: {}", self.name, e ) } } } - Err(e) => log::warn!("Storage {} raised an error on query: {}", self.name, e), + Err(e) => log::warn!("Storage'{}' raised an error on query: {}", self.name, e), }; } drop(storage); @@ -561,7 +561,7 @@ impl StorageService { }; if let Err(e) = q.reply(Ok(sample)).res().await { log::warn!( - "Storage {} raised an error replying a query: {}", + "Storage '{}' raised an error replying a query: {}", self.name, e ) @@ -570,11 +570,11 @@ impl StorageService { } Err(e) => { let err_message = - format!("Storage {} raised an error on query: {}", self.name, e); + format!("Storage '{}' raised an error on query: {}", self.name, e); log::warn!("{}", err_message); if let Err(e) = q.reply(Err(err_message.into())).res().await { log::warn!( - "Storage {} raised an error replying a query: {}", + "Storage '{}' raised an error replying a query: {}", self.name, e ) @@ -602,7 +602,7 @@ impl StorageService { } } Err(e) => log::warn!( - "Storage {} raised an error while retrieving keys: {}", + "Storage '{}' raised an error while retrieving keys: {}", self.name, e ), @@ -659,7 +659,7 @@ impl StorageService { { Ok(replies) => replies, Err(e) => { - log::error!("Error aligning storage {}: {}", self.name, e); + log::error!("Error aligning storage '{}': {}", self.name, e); return; } }; @@ -669,7 +669,7 @@ impl StorageService { self.process_sample(sample).await; } Err(e) => log::warn!( - "Storage {} received an error to align query: {}", + "Storage '{}' received an error to align query: {}", self.name, e ), diff --git a/plugins/zenoh-plugin-storage-manager/src/storages_mgt.rs b/plugins/zenoh-plugin-storage-manager/src/storages_mgt.rs index 7bf1714206..6de5e2f2ca 100644 --- a/plugins/zenoh-plugin-storage-manager/src/storages_mgt.rs +++ b/plugins/zenoh-plugin-storage-manager/src/storages_mgt.rs @@ -35,7 +35,7 @@ pub(crate) async fn start_storage( let storage_name = parts[7]; let name = format!("{uuid}/{storage_name}"); - log::trace!("Start storage {} on {}", name, config.key_expr); + log::trace!("Start storage '{}' on keyexpr '{}'", name, config.key_expr); let (tx, rx) = flume::bounded(1); diff --git a/plugins/zenoh-plugin-trait/src/manager/dynamic_plugin.rs b/plugins/zenoh-plugin-trait/src/manager/dynamic_plugin.rs index 3dfd0263fa..5377e1547e 100644 --- a/plugins/zenoh-plugin-trait/src/manager/dynamic_plugin.rs +++ b/plugins/zenoh-plugin-trait/src/manager/dynamic_plugin.rs @@ -11,7 +11,7 @@ // ZettaScale Zenoh Team, // use crate::*; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use libloading::Library; use zenoh_result::{bail, ZResult}; @@ -48,16 +48,14 @@ impl DynamicPluginSource { struct DynamicPluginStarter { _lib: Library, path: PathBuf, - plugin_version: &'static str, - plugin_long_version: &'static str, vtable: PluginVTable, } impl DynamicPluginStarter { - fn new(lib: Library, path: PathBuf) -> ZResult { - log::debug!("Loading plugin {}", &path.to_str().unwrap(),); + fn get_vtable(lib: &Library, path: &Path) -> ZResult> { + log::debug!("Loading plugin {}", path.to_str().unwrap(),); let get_plugin_loader_version = unsafe { lib.get:: PluginLoaderVersion>(b"get_plugin_loader_version")? }; let plugin_loader_version = get_plugin_loader_version(); @@ -70,28 +68,31 @@ impl ); } let get_compatibility = unsafe { lib.get:: Compatibility>(b"get_compatibility")? }; - let plugin_compatibility_record = get_compatibility(); - let host_compatibility_record = + let mut plugin_compatibility_record = get_compatibility(); + let mut host_compatibility_record = Compatibility::with_empty_plugin_version::(); log::debug!( "Plugin compativilty record: {:?}", &plugin_compatibility_record ); - if !plugin_compatibility_record.are_compatible(&host_compatibility_record) { + if !plugin_compatibility_record.compare(&mut host_compatibility_record) { bail!( - "Plugin compatibility mismatch:\n\nHost:\n{}\nPlugin:\n{}\n", + "Plugin compatibility mismatch:\nHost:\n{}Plugin:\n{}", host_compatibility_record, plugin_compatibility_record ); } let load_plugin = unsafe { lib.get:: PluginVTable>(b"load_plugin")? }; - let vtable = load_plugin(); + + Ok(load_plugin()) + } + fn new(lib: Library, path: PathBuf) -> ZResult { + let vtable = Self::get_vtable(&lib, &path) + .map_err(|e| format!("Error loading {}: {}", path.to_str().unwrap(), e))?; Ok(Self { _lib: lib, path, - plugin_version: plugin_compatibility_record.plugin_version(), - plugin_long_version: plugin_compatibility_record.plugin_long_version(), vtable, }) } @@ -130,10 +131,10 @@ impl PluginStatus self.name.as_str() } fn version(&self) -> Option<&str> { - self.starter.as_ref().map(|v| v.plugin_version) + self.starter.as_ref().map(|v| v.vtable.plugin_version) } fn long_version(&self) -> Option<&str> { - self.starter.as_ref().map(|v| v.plugin_long_version) + self.starter.as_ref().map(|v| v.vtable.plugin_long_version) } fn path(&self) -> &str { if let Some(starter) = &self.starter { diff --git a/plugins/zenoh-plugin-trait/src/vtable.rs b/plugins/zenoh-plugin-trait/src/vtable.rs index 6abdbf145d..7541c4a41a 100644 --- a/plugins/zenoh-plugin-trait/src/vtable.rs +++ b/plugins/zenoh-plugin-trait/src/vtable.rs @@ -22,11 +22,13 @@ type StartFn = fn(&str, &StartArgs) -> ZResult; #[repr(C)] pub struct PluginVTable { + pub plugin_version: &'static str, + pub plugin_long_version: &'static str, pub start: StartFn, } impl PluginStructVersion for PluginVTable { fn struct_version() -> u64 { - 1 + 2 } fn struct_features() -> &'static str { FEATURES @@ -64,12 +66,12 @@ impl Display for StructVersion { #[repr(C)] #[derive(Debug, PartialEq, Eq, Clone)] pub struct Compatibility { - rust_version: RustVersion, - vtable_version: StructVersion, - start_args_version: StructVersion, - instance_version: StructVersion, - plugin_version: &'static str, - plugin_long_version: &'static str, + rust_version: Option, + vtable_version: Option, + start_args_version: Option, + instance_version: Option, + plugin_version: Option<&'static str>, + plugin_long_version: Option<&'static str>, } impl Compatibility { @@ -78,12 +80,14 @@ impl Compatibility { InstanceType: PluginInstance, PluginType: Plugin, >() -> Self { - let rust_version = RustVersion::new(); - let vtable_version = StructVersion::new::>(); - let start_args_version = StructVersion::new::(); - let instance_version = StructVersion::new::(); - let plugin_version = PluginType::PLUGIN_VERSION; - let plugin_long_version = PluginType::PLUGIN_LONG_VERSION; + let rust_version = Some(RustVersion::new()); + let vtable_version = Some(StructVersion::new::< + PluginVTable, + >()); + let start_args_version = Some(StructVersion::new::()); + let instance_version = Some(StructVersion::new::()); + let plugin_version = Some(PluginType::PLUGIN_VERSION); + let plugin_long_version = Some(PluginType::PLUGIN_LONG_VERSION); Self { rust_version, vtable_version, @@ -97,47 +101,111 @@ impl Compatibility { StartArgsType: PluginStartArgs, InstanceType: PluginInstance, >() -> Self { - let rust_version = RustVersion::new(); - let vtable_version = StructVersion::new::>(); - let start_args_version = StructVersion::new::(); - let instance_version = StructVersion::new::(); + let rust_version = Some(RustVersion::new()); + let vtable_version = Some(StructVersion::new::< + PluginVTable, + >()); + let start_args_version = Some(StructVersion::new::()); + let instance_version = Some(StructVersion::new::()); Self { rust_version, vtable_version, start_args_version, instance_version, - plugin_version: "", - plugin_long_version: "", + plugin_version: None, + plugin_long_version: None, } } - pub fn plugin_version(&self) -> &'static str { + pub fn plugin_version(&self) -> Option<&'static str> { self.plugin_version } - pub fn plugin_long_version(&self) -> &'static str { + pub fn plugin_long_version(&self) -> Option<&'static str> { self.plugin_long_version } - /// Returns true if rust compiler and structures version are exactly the same and - /// plugin version is compatible with the requested version range in the configuration file - pub fn are_compatible(&self, other: &Self) -> bool { - RustVersion::are_compatible(&self.rust_version, &other.rust_version) - && self.vtable_version == other.vtable_version - && self.start_args_version == other.start_args_version - && self.instance_version == other.instance_version - // TODO: check plugin version may be added later + /// Compares fields if both are Some, otherwise skips the comparison. + /// Returns true if all the comparisons returned true, otherwise false. + /// If comparison passed or skipped, the corresponding field in both structs is set to None. + /// If comparison failed, the corresponding field in both structs is kept as is. + /// This allows not only to check compatibility, but also point to exact reasons of incompatibility. + pub fn compare(&mut self, other: &mut Self) -> bool { + let mut result = true; + Self::compare_field_fn( + &mut result, + &mut self.rust_version, + &mut other.rust_version, + RustVersion::are_compatible, + ); + Self::compare_field( + &mut result, + &mut self.vtable_version, + &mut other.vtable_version, + ); + Self::compare_field( + &mut result, + &mut self.start_args_version, + &mut other.start_args_version, + ); + Self::compare_field( + &mut result, + &mut self.instance_version, + &mut other.instance_version, + ); + // TODO: here we can later implement check for plugin version range compatibility + Self::compare_field( + &mut result, + &mut self.plugin_version, + &mut other.plugin_version, + ); + Self::compare_field( + &mut result, + &mut self.plugin_long_version, + &mut other.plugin_long_version, + ); + result + } + + // Utility function for compare single field + fn compare_field_fn bool>( + result: &mut bool, + a: &mut Option, + b: &mut Option, + compare: F, + ) { + let compatible = if let (Some(a), Some(b)) = (&a, &b) { + compare(a, b) + } else { + true + }; + if compatible { + *a = None; + *b = None; + } else { + *result = false; + } + } + fn compare_field(result: &mut bool, a: &mut Option, b: &mut Option) { + Self::compare_field_fn(result, a, b, |a, b| a == b); } } impl Display for Compatibility { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}\nVTable:{}StartArgs:{}Instance:{}Plugin:{}", - self.rust_version, - self.vtable_version, - self.start_args_version, - self.instance_version, - self.plugin_version - ) + if let Some(rust_version) = &self.rust_version { + writeln!(f, "Rust version:\n{}", rust_version)?; + } + if let Some(vtable_version) = &self.vtable_version { + writeln!(f, "VTable version:\n{}", vtable_version)?; + } + if let Some(start_args_version) = &self.start_args_version { + writeln!(f, "StartArgs version:\n{}", start_args_version)?; + } + if let Some(instance_version) = &self.instance_version { + writeln!(f, "Instance version:\n{}", instance_version)?; + } + if let Some(plugin_version) = &self.plugin_version { + writeln!(f, "Plugin version: {}", plugin_version)?; + } + Ok(()) } } @@ -201,6 +269,8 @@ impl Default for RustVersion { impl PluginVTable { pub fn new>() -> Self { Self { + plugin_version: ConcretePlugin::PLUGIN_VERSION, + plugin_long_version: ConcretePlugin::PLUGIN_LONG_VERSION, start: ConcretePlugin::start, } }