This is an automated email from the ASF dual-hosted git repository. pnoltes pushed a commit to branch feature/599-provide-and-use-c-service-in-rust in repository https://gitbox.apache.org/repos/asf/celix.git
commit 1654e21e52199dc8dacddbd1ef1bfb575f496c86 Author: Pepijn Noltes <pepijnnol...@gmail.com> AuthorDate: Tue Aug 22 20:24:42 2023 +0200 #599: Refactor bundle context for service registration builder --- misc/experimental/rust/CMakeLists.txt | 22 +- misc/experimental/rust/Cargo.toml | 1 + misc/experimental/rust/celix/src/bundle_context.rs | 227 ++++++++++++++++++++- misc/experimental/rust/celix/src/lib.rs | 1 + misc/experimental/rust/celix/src/log_level.rs | 40 ++-- .../rust/{ => shell_command_bundle}/Cargo.toml | 19 +- .../rust/shell_command_bundle/src/lib.rs | 107 ++++++++++ 7 files changed, 373 insertions(+), 44 deletions(-) diff --git a/misc/experimental/rust/CMakeLists.txt b/misc/experimental/rust/CMakeLists.txt index a98150f2..6ab2287c 100644 --- a/misc/experimental/rust/CMakeLists.txt +++ b/misc/experimental/rust/CMakeLists.txt @@ -41,11 +41,11 @@ if (CELIX_RUST_EXPERIMENTAL) add_celix_bundle(rust_bundle ACTIVATOR ${ACTUAL_LIB_TARGET}) add_dependencies(rust_bundle rust_bundle_activator) -# corrosion_add_target_local_rustflags(rust_shell_tui_activator "-Cprefer-dynamic") -# corrosion_link_libraries(rust_shell_tui_activator Celix::framework) -# get_target_property(ACTUAL_LIB_TARGET rust_shell_tui_activator INTERFACE_LINK_LIBRARIES) -# add_celix_bundle(rust_shell_tui ACTIVATOR ${ACTUAL_LIB_TARGET}) -# add_dependencies(rust_shell_tui rust_shell_tui_activator) + corrosion_add_target_local_rustflags(rust_shell_command_activator "-Cprefer-dynamic") + corrosion_link_libraries(rust_shell_command_activator Celix::framework) + get_target_property(ACTUAL_LIB_TARGET rust_shell_command_activator INTERFACE_LINK_LIBRARIES) + add_celix_bundle(rust_shell_command ACTIVATOR ${ACTUAL_LIB_TARGET}) + add_dependencies(rust_shell_command rust_shell_command_activator) add_celix_container(rust_container NO_COPY BUNDLES @@ -54,11 +54,11 @@ if (CELIX_RUST_EXPERIMENTAL) rust_bundle ) -# add_celix_container(rust_shell_tui_cnt NO_COPY -# BUNDLES -# Celix::shell -# Celix::shell_tui -# rust_shell_tui -# ) + add_celix_container(rust_shell_cnt NO_COPY + BUNDLES + Celix::shell + Celix::shell_tui + rust_shell_command + ) endif() diff --git a/misc/experimental/rust/Cargo.toml b/misc/experimental/rust/Cargo.toml index d6679dd3..95f253d0 100644 --- a/misc/experimental/rust/Cargo.toml +++ b/misc/experimental/rust/Cargo.toml @@ -20,5 +20,6 @@ members = [ "celix_bindings", "celix", "hello_world_activator", + "shell_command_bundle", #"rust_shell_tui", ] diff --git a/misc/experimental/rust/celix/src/bundle_context.rs b/misc/experimental/rust/celix/src/bundle_context.rs index 4bfacce2..5ff380a4 100644 --- a/misc/experimental/rust/celix/src/bundle_context.rs +++ b/misc/experimental/rust/celix/src/bundle_context.rs @@ -17,11 +17,199 @@ * under the License. */ -use super::LogLevel; +use std::ptr::null_mut; +use std::ffi::c_void; +use std::collections::HashMap; +use std::sync::Arc; +use std::sync::Weak; +use std::any::type_name; +use std::any::Any; +use std::sync::Mutex; use celix_bindings::celix_bundle_context_t; use celix_bindings::celix_bundleContext_log; -use celix_bindings::celix_log_level_e; +use celix_bindings::celix_properties_create; +use celix_bindings::celix_properties_set; +use celix_bindings::celix_bundleContext_registerServiceWithOptions; +use celix_bindings::celix_bundleContext_unregisterService; +use celix_bindings::celix_service_registration_options_t; + +use super::Error; +use super::LogLevel; + +pub struct ServiceRegistration { + service_id: i64, + weak_ctx: Weak<BundleContextImpl>, + boxed_svc: Option<Box<dyn Any>>, + // arc_svc: Option<Arc<dyn Any>>, +} + +impl Drop for ServiceRegistration { + fn drop(&mut self) { + let ctx = self.weak_ctx.upgrade(); + match ctx { + Some(ctx) => ctx.unregister_service(self.service_id), + None => println!("Cannot unregister ServiceRegistration: BundleContext is gone"), + } + } +} + +pub struct ServiceRegistrationBuilder<'a> { + ctx: &'a BundleContextImpl, + boxed_svc: Option<Box<dyn Any>>, + // _arc_svc: Option<Arc<dyn Any>>, + unmanaged_svc: *mut c_void, + service_name: String, + service_version: String, + service_properties: HashMap<String, String>, +} + +impl ServiceRegistrationBuilder<'_> { + fn new<'a>(ctx: &'a BundleContextImpl) -> ServiceRegistrationBuilder<'a> { + ServiceRegistrationBuilder { + ctx, + boxed_svc: None, + //arc_svc: None, + unmanaged_svc: null_mut(), + service_name: "".to_string(), + service_version: "".to_string(), + service_properties: HashMap::new(), + } + } + + pub fn with_service_name(&mut self, name: &str) -> &mut Self { + self.service_name = name.to_string(); + self + } + + fn with_type_name_as_service_name<I>(&mut self) -> &mut Self { + if self.service_name.is_empty() { + self.service_name = type_name::<I>().to_string(); + } + self + } + + pub fn with_service<I:'static>(&mut self, instance: I) -> &mut Self { + self.boxed_svc = Some(Box::new(instance)); + //self.arc_svc = None; + self.unmanaged_svc = null_mut(); + self.with_type_name_as_service_name::<I>() + } + + //TODO check if dyn is needed (e.g. a trait object is needed) + pub fn with_boxed_service<I:'static>(&mut self, instance: Box</*dyn*/ I>) -> &mut Self { + self.boxed_svc = Some(instance); + //self.arc_svc = None; + self.unmanaged_svc = null_mut(); + self.with_type_name_as_service_name::<I>() + } + + //TODO check if dyn is needed (e.g. a trait object is needed) + // pub fn with_arc_instance<I:'static>(&mut self, instance: Arc</*dyn*/ I>) -> &mut Self { + // self.boxed_svc = None; + // self.arc_svc = Some(instance); + // self.unmanaged_svc = null_mut(); + // self.with_type_name_as_service_name::<I>() + // } + + pub fn with_unmanaged_service<I>(&mut self, instance: *mut I) -> &mut Self { + self.boxed_svc = None; + //self.arc_svc = None; + self.unmanaged_svc = instance as *mut c_void; + self.with_type_name_as_service_name::<I>() + } + + pub fn with_version(&mut self, version: &str) -> &mut Self { + self.service_version = version.to_string(); + self + } + + pub fn with_properties(&mut self, properties: HashMap<String, String>) -> &mut Self { + self.service_properties = properties; + self + } + + pub fn with_property(&mut self, key: &str, value: &str) -> &mut Self { + self.service_properties.insert(key.to_string(), value.to_string()); + self + } + + fn validate(&self) -> Result<(), Error> { + let mut valid = true; + if self.service_name.is_empty() { + self.ctx.log_error("Cannot register service. Service name is empty"); + valid = false; + } + if self.boxed_svc.is_none() && /*self.arc_svc.is_none() &&*/ self.unmanaged_svc.is_null() { + self.ctx.log_error("Cannot register service. No instance provided"); + valid = false; + } + match valid { + true => Ok(()), + false => Err(Error::BundleException), + } + } + + fn get_c_svc(&mut self) -> *mut c_void { + if let Some(boxed_svc) = self.boxed_svc.as_mut() { + let any_svc: &mut dyn Any = boxed_svc.as_mut(); + let boxed_svc_ptr = any_svc as *mut dyn Any; //note box still owns the instance + boxed_svc_ptr as *mut c_void + // } else if let Some(arc_svc) = self.arc_svc.as_mut() { + // let any_svc: &mut dyn Any = arc_svc.as_mut(); + // let arc_svc_ptr = arc_svc as *mut dyn Any; //note arc still owns the instance + // arc_svc_ptr as *mut c_void + } else if self.unmanaged_svc.is_null() { + panic!("Cannot get c_svc. No instance provided"); + } else { + self.unmanaged_svc as *mut c_void + } + } + + unsafe fn build_unsafe(&mut self) -> Result<ServiceRegistration, Error> { + let c_service_name = std::ffi::CString::new(self.service_name.as_str()).unwrap(); + let c_service_version = std::ffi::CString::new(self.service_version.as_str()).unwrap(); + let c_service_properties = celix_properties_create(); + let c_service = self.get_c_svc(); + for (key, value) in self.service_properties.iter() { + let c_key = std::ffi::CString::new(key.as_str()).unwrap(); + let c_value = std::ffi::CString::new(value.as_str()).unwrap(); + celix_properties_set(c_service_properties, c_key.as_ptr(), c_value.as_ptr()); + } + let opts = celix_service_registration_options_t { + svc: c_service, + factory: null_mut(), + serviceName: c_service_name.as_ptr() as *const i8, + properties: c_service_properties, + serviceLanguage: null_mut(), + serviceVersion: if self.service_version.is_empty() { null_mut() } else { c_service_version.as_ptr() as *const i8}, + asyncData: null_mut(), + asyncCallback: None, + }; + + let service_id: i64 = celix_bundleContext_registerServiceWithOptions( + self.ctx.get_c_bundle_context(), + &opts); + if service_id >= 0 { + Ok(ServiceRegistration { + service_id, + weak_ctx: self.ctx.get_self().clone(), + boxed_svc: self.boxed_svc.take(), //to ensure that a possible box instance is not dropped + // arc_svc: self.arc_svc.take(), //to ensure that a possible arc instance is not dropped + }) + } else { + Err(Error::BundleException) + } + } + + pub fn build(&mut self) -> Result<ServiceRegistration, Error> { + self.validate()?; + unsafe { + //TODO make unsafe part smaller (if possible) + self.build_unsafe() + } + } +} pub trait BundleContext { fn get_c_bundle_context(&self) -> *mut celix_bundle_context_t; @@ -39,18 +227,41 @@ pub trait BundleContext { fn log_error(&self, message: &str); fn log_fatal(&self, message: &str); + + fn register_service(&self) -> ServiceRegistrationBuilder; } struct BundleContextImpl { c_bundle_context: *mut celix_bundle_context_t, + weak_self : Mutex<Option<Weak<BundleContextImpl>>>, } impl BundleContextImpl { - pub fn new(c_bundle_context: *mut celix_bundle_context_t) -> Self { - BundleContextImpl { + fn new(c_bundle_context: *mut celix_bundle_context_t) -> Arc<Self> { + let ctx = Arc::new(BundleContextImpl { c_bundle_context, + weak_self: Mutex::new(None), + }); + let weak_ref = Arc::downgrade(&ctx); + ctx.set_self(weak_ref); + ctx + } + + fn set_self(&self, weak_self: Weak<BundleContextImpl>) { + let mut guard = self.weak_self.lock().unwrap(); + *guard = Some(weak_self); + } + + fn get_self(&self) -> Weak<BundleContextImpl> { + self.weak_self.lock().unwrap().clone().unwrap() + } + + fn unregister_service(&self, service_id: i64) { + unsafe { + celix_bundleContext_unregisterService(self.c_bundle_context, service_id); } } + fn log_to_c(&self, level: LogLevel, message: &str) { unsafe { let result = std::ffi::CString::new(message); @@ -84,8 +295,12 @@ impl BundleContext for BundleContextImpl { fn log_error(&self, message: &str){ self.log(LogLevel::Error, message); } fn log_fatal(&self, message: &str){ self.log(LogLevel::Fatal, message); } + + fn register_service(&self) -> ServiceRegistrationBuilder { + ServiceRegistrationBuilder::new(self) + } } -pub fn bundle_context_new(c_bundle_context: *mut celix_bundle_context_t) -> Box<dyn BundleContext> { - Box::new(BundleContextImpl::new(c_bundle_context)) +pub fn bundle_context_new(c_bundle_context: *mut celix_bundle_context_t) -> Arc<dyn BundleContext> { + BundleContextImpl::new(c_bundle_context) } diff --git a/misc/experimental/rust/celix/src/lib.rs b/misc/experimental/rust/celix/src/lib.rs index 791a3cda..9befdeb8 100644 --- a/misc/experimental/rust/celix/src/lib.rs +++ b/misc/experimental/rust/celix/src/lib.rs @@ -41,6 +41,7 @@ pub use self::log_level::LogLevel as LogLevel; mod bundle_context; // Re-export bundle context types in the public API. pub use self::bundle_context::BundleContext as BundleContext; +pub use self::bundle_context::ServiceRegistration as ServiceRegistration; pub use self::bundle_context::bundle_context_new as bundle_context_new; mod bundle_activator; diff --git a/misc/experimental/rust/celix/src/log_level.rs b/misc/experimental/rust/celix/src/log_level.rs index d2b22470..bc9b5e48 100644 --- a/misc/experimental/rust/celix/src/log_level.rs +++ b/misc/experimental/rust/celix/src/log_level.rs @@ -18,13 +18,13 @@ */ use celix_bindings::celix_log_level_e; -use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_TRACE; -use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_DEBUG; -use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_INFO; -use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_WARNING; -use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_ERROR; -use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_FATAL; -use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_DISABLED; +use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_TRACE as CELIX_LOG_LEVEL_TRACE; +use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_DEBUG as CELIX_LOG_LEVEL_DEBUG; +use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_INFO as CELIX_LOG_LEVEL_INFO; +use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_WARNING as CELIX_LOG_LEVEL_WARNING; +use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_ERROR as CELIX_LOG_LEVEL_ERROR; +use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_FATAL as CELIX_LOG_LEVEL_FATAL; +use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_DISABLED as CELIX_LOG_LEVEL_DISABLED; pub enum LogLevel { Trace, @@ -38,12 +38,12 @@ pub enum LogLevel { impl From<celix_log_level_e> for LogLevel { fn from(level: celix_log_level_e) -> Self { match level { - celix_log_level_CELIX_LOG_LEVEL_TRACE => LogLevel::Trace, - celix_log_level_CELIX_LOG_LEVEL_DEBUG => LogLevel::Debug, - celix_log_level_CELIX_LOG_LEVEL_INFO => LogLevel::Info, - celix_log_level_CELIX_LOG_LEVEL_WARNING => LogLevel::Warning, - celix_log_level_CELIX_LOG_LEVEL_ERROR => LogLevel::Error, - celix_log_level_CELIX_LOG_LEVEL_FATAL => LogLevel::Fatal, + CELIX_LOG_LEVEL_TRACE => LogLevel::Trace, + CELIX_LOG_LEVEL_DEBUG => LogLevel::Debug, + CELIX_LOG_LEVEL_INFO => LogLevel::Info, + CELIX_LOG_LEVEL_WARNING => LogLevel::Warning, + CELIX_LOG_LEVEL_ERROR => LogLevel::Error, + CELIX_LOG_LEVEL_FATAL => LogLevel::Fatal, _ => LogLevel::Disabled, } } @@ -52,13 +52,13 @@ impl From<celix_log_level_e> for LogLevel { impl Into<celix_log_level_e> for LogLevel { fn into(self) -> celix_log_level_e { match self { - LogLevel::Trace => celix_log_level_CELIX_LOG_LEVEL_TRACE, - LogLevel::Debug => celix_log_level_CELIX_LOG_LEVEL_DEBUG, - LogLevel::Info => celix_log_level_CELIX_LOG_LEVEL_INFO, - LogLevel::Warning => celix_log_level_CELIX_LOG_LEVEL_WARNING, - LogLevel::Error => celix_log_level_CELIX_LOG_LEVEL_ERROR, - LogLevel::Fatal => celix_log_level_CELIX_LOG_LEVEL_FATAL, - _ => celix_log_level_CELIX_LOG_LEVEL_DISABLED, + LogLevel::Trace => CELIX_LOG_LEVEL_TRACE, + LogLevel::Debug => CELIX_LOG_LEVEL_DEBUG, + LogLevel::Info => CELIX_LOG_LEVEL_INFO, + LogLevel::Warning => CELIX_LOG_LEVEL_WARNING, + LogLevel::Error => CELIX_LOG_LEVEL_ERROR, + LogLevel::Fatal => CELIX_LOG_LEVEL_FATAL, + _ => CELIX_LOG_LEVEL_DISABLED, } } } diff --git a/misc/experimental/rust/Cargo.toml b/misc/experimental/rust/shell_command_bundle/Cargo.toml similarity index 75% copy from misc/experimental/rust/Cargo.toml copy to misc/experimental/rust/shell_command_bundle/Cargo.toml index d6679dd3..1773c986 100644 --- a/misc/experimental/rust/Cargo.toml +++ b/misc/experimental/rust/shell_command_bundle/Cargo.toml @@ -15,10 +15,15 @@ # specific language governing permissions and limitations # under the License. -[workspace] -members = [ - "celix_bindings", - "celix", - "hello_world_activator", - #"rust_shell_tui", -] +[package] +name = "rust_shell_command_activator" +version = "0.0.1" + +[dependencies] +celix_bindings = { path = "../celix_bindings" } +celix = { path = "../celix" } + +[lib] +name = "rust_shell_command_activator" +path = "src/lib.rs" +crate-type = ["cdylib"] diff --git a/misc/experimental/rust/shell_command_bundle/src/lib.rs b/misc/experimental/rust/shell_command_bundle/src/lib.rs new file mode 100644 index 00000000..cc9b83f9 --- /dev/null +++ b/misc/experimental/rust/shell_command_bundle/src/lib.rs @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +extern crate celix_bindings; +extern crate celix; + +use std::sync::Arc; +use std::ffi::c_void; +use std::ffi::c_char; + +use celix::BundleActivator; +use celix::BundleContext; +use celix::Error; + +use celix_bindings::celix_shell_command_t; +use celix_bindings::FILE; + +//temporary, should be moved in a separate API crate +// trait RustShellCommand { +// fn execute_command(&mut self, command_line: &str, command_args: Vec<&str>) -> Result<(), Error>; +// } + +struct ShellCommandProvider { + ctx: Arc<dyn BundleContext>, +} +impl ShellCommandProvider { + fn new(ctx: Arc<dyn BundleContext>) -> Self { + ctx.log_info("Shell Command created"); + ShellCommandProvider{ + ctx, + } + } + + extern "C" fn call_execute_command(handle: *mut c_void, command_line: *const c_char, _out_stream: *mut FILE, _error_stream: *mut FILE) -> bool { + if handle.is_null() || command_line.is_null() { + return false; + } + unsafe { + println!("call_execute_command"); + let obj = &mut *(handle as *mut ShellCommandProvider); + let str_command_line = std::ffi::CStr::from_ptr(command_line).to_str().unwrap(); + obj.execute_command(str_command_line); + } + true + } + + fn execute_command(&mut self, command_line: &str) { + self.ctx.log_info(format!("Execute command: {}", command_line).as_str()); + } +} + +struct ShellCommandActivator { + ctx: Arc<dyn BundleContext>, + //log_helper: Box<dyn LogHelper>, + shell_command_provider: ShellCommandProvider, + registration: Option<celix::ServiceRegistration>, +} + +impl BundleActivator for ShellCommandActivator { + fn new(ctx: Arc<dyn celix::BundleContext>) -> Self { + let result = ShellCommandActivator { + ctx: ctx.clone(), + shell_command_provider: ShellCommandProvider::new(ctx.clone()), + //log_helper: log_helper_new(&*ctx, "ShellCommandBundle"), + registration: None, + }; + result + } + fn start(&mut self) -> Result<(), Error> { + let registration = self.ctx.register_service() + .with_service( celix_shell_command_t{ + handle: &mut self.shell_command_provider as *mut ShellCommandProvider as *mut c_void, + executeCommand: Some(ShellCommandProvider::call_execute_command), + }) + .with_service_name("celix_shell_command") + .with_property("command.name", "exe_in_rust") + .with_property("command.description", "Simple command written in Rust") + .build()?; + self.registration = Some(registration); + self.ctx.log_info("Rust Shell Command started"); + Ok(()) + } + + fn stop(&mut self) -> Result<(), Error> { + self.registration = None; + self.ctx.log_info("Rust Shell Command stopped"); + Ok(()) + } +} + +celix::generate_bundle_activator!(ShellCommandActivator);