diff --git a/CHANGES.md b/CHANGES.md index 5cb408344..0d03bf528 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,39 @@ See the License for the specific language governing permissions and limitations under the License. --> +# Noteworthy Changes for 2.4.0 (TBD) + +## New Features + +- V2 Shared memory for remote service admin. +- Zeroconf discovery of remote services. +- Symbol visibility support: Bundle symbols are now hidden by default, except for the bundle activator. +- Error injection library (for testing). +- Coding convention documentation. +- Celix error library for printing errors when no framework is available. +- Scope-based Resource Management (RAII-light for C). +- Rust Proof of Concept (PoC) for Apache Celix. + +## Improvements + +- Support for Conan 2. +- Support for uclibc (not tested in CI yet). +- Support for C++14 in addition to C++17. +- Deprecated `snprintf` usage; transitioned to `snprintf` or `aprintf`. +- Refactored the bundle cache to support retention of unchanged bundles on disk. +- Automatic scan for project build options using CMake. +- Use of upstream `civetweb` dependency instead of embedded sources. +- Applied attribute format for printf-like functions. +- Removed the busy loop mechanism from the pubsub admin websocket. +- Improved cleanup procedures during bundle uninstallation to conform to the OSGi specification. +- Improved `INSTALL_RPATH` to use `$ORIGIN` during installation. +- Corrected bundle update behavior when updating bundles from different sources. +- Enhanced `libcurl` initialization procedures and made `libcurl` optional. + +## Fixes + +- Numerous minor fixes, especially concerning multi-threading issues and error handling. + # Noteworthy changes for 2.3.0 (2022-07-10) ## New Features diff --git a/CMakeLists.txt b/CMakeLists.txt index f659fb16f..38f1daec3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -cmake_minimum_required (VERSION 3.18) +cmake_minimum_required (VERSION 3.19) cmake_policy(SET CMP0012 NEW) cmake_policy(SET CMP0042 NEW) cmake_policy(SET CMP0068 NEW) diff --git a/misc/experimental/rust/CMakeLists.txt b/misc/experimental/rust/CMakeLists.txt index e2236a1af..964ab62da 100644 --- a/misc/experimental/rust/CMakeLists.txt +++ b/misc/experimental/rust/CMakeLists.txt @@ -16,7 +16,7 @@ # under the License. option(CELIX_RUST_EXPERIMENTAL "Enable experimental rust bundle" OFF) -if (CELIX_RUST_EXPERIMENTAL) +if (CELIX_RUST_EXPERIMENTAL AND TARGET Celix::shell_tui AND TARGET Celix::shell AND TARGET Celix::log_admin) include(FetchContent) FetchContent_Declare( Corrosion @@ -26,13 +26,12 @@ if (CELIX_RUST_EXPERIMENTAL) FetchContent_MakeAvailable(Corrosion) #Prepare a list of include paths needed to generating bindings for the Apache Celix C API - file(GENERATE - OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include_paths.txt" - CONTENT "$;$" - ) + #Note for now this includes framework, utils and shell_api maybe this should be separated in the future. + file(GENERATE OUTPUT + "${CMAKE_CURRENT_BINARY_DIR}/include_paths.txt" CONTENT + "$;$;$;$") corrosion_import_crate(MANIFEST_PATH Cargo.toml) - corrosion_add_target_local_rustflags(rust_bundle_activator "-Cprefer-dynamic") corrosion_link_libraries(rust_bundle_activator Celix::framework) @@ -41,12 +40,28 @@ if (CELIX_RUST_EXPERIMENTAL) get_target_property(ACTUAL_LIB_TARGET rust_bundle_activator INTERFACE_LINK_LIBRARIES) add_celix_bundle(rust_bundle ACTIVATOR ${ACTUAL_LIB_TARGET}) add_dependencies(rust_bundle rust_bundle_activator) + corrosion_add_target_local_rustflags(rust_shell_command_activator "-Cprefer-dynamic") + corrosion_link_libraries(rust_shell_command_activator + Celix::framework + ) - add_celix_container(rust_container - NO_COPY + 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 Celix::shell Celix::shell_tui rust_bundle ) -endif () + + add_celix_container(rust_shell_cnt NO_COPY + BUNDLES + Celix::shell + Celix::shell_tui + Celix::log_admin + rust_shell_command + ) + +endif() diff --git a/misc/experimental/rust/Cargo.toml b/misc/experimental/rust/Cargo.toml index 8727be5d9..bd144ccfb 100644 --- a/misc/experimental/rust/Cargo.toml +++ b/misc/experimental/rust/Cargo.toml @@ -18,5 +18,9 @@ [workspace] members = [ "celix_bindings", + "celix", "hello_world_activator", + "rust_shell_api", + "shell_command_bundle", ] +resolver = "2" \ No newline at end of file diff --git a/misc/experimental/rust/celix/Cargo.toml b/misc/experimental/rust/celix/Cargo.toml new file mode 100644 index 000000000..fc3559706 --- /dev/null +++ b/misc/experimental/rust/celix/Cargo.toml @@ -0,0 +1,29 @@ +# 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. + +[package] +name = "celix" +version = "0.0.1" +edition = '2021' + +[dependencies] +celix_bindings = { path = "../celix_bindings" } + +[lib] +name = "celix" +path = "src/lib.rs" +crate-type = ["rlib"] diff --git a/misc/experimental/rust/celix/src/bundle_activator.rs b/misc/experimental/rust/celix/src/bundle_activator.rs new file mode 100644 index 000000000..d3050bcea --- /dev/null +++ b/misc/experimental/rust/celix/src/bundle_activator.rs @@ -0,0 +1,88 @@ +/* + * 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. + */ + +use std::sync::Arc; + +use super::BundleContext; +use super::Error; + +pub trait BundleActivator { + fn new(ctx: Arc) -> Self; + fn start(&mut self) -> Result<(), Error> { + /* Default implementation */ + Ok(()) + } + fn stop(&mut self) -> Result<(), Error> { + /* Default implementation */ + Ok(()) + } +} + +#[macro_export] +macro_rules! generate_bundle_activator { + ($activator:ty) => { + #[no_mangle] + pub unsafe extern "C" fn celix_bundleActivator_create( + ctx: *mut $crate::details::CBundleContext, + out: *mut *mut ::std::ffi::c_void, + ) -> $crate::details::CStatus { + let boxed_context = $crate::bundle_context_new(ctx); + let mut arc_context = Arc::from(boxed_context); + let activator = <$activator>::new(arc_context); + *out = Box::into_raw(Box::new(activator)) as *mut ::std::ffi::c_void; + $crate::CELIX_SUCCESS + } + + #[no_mangle] + pub unsafe extern "C" fn celix_bundleActivator_start( + handle: *mut ::std::ffi::c_void, + ctx: *mut $crate::details::CBundleContext, + ) -> $crate::details::CStatus { + let activator = &mut *(handle as *mut $activator); + let result = activator.start(); + match result { + Ok(_) => $crate::CELIX_SUCCESS, + Err(e) => e.into(), + } + } + + #[no_mangle] + pub unsafe extern "C" fn celix_bundleActivator_stop( + handle: *mut ::std::ffi::c_void, + ctx: *mut $crate::details::CBundleContext, + ) -> $crate::details::CStatus { + let activator = &mut *(handle as *mut $activator); + let result = activator.stop(); + match result { + Ok(_) => $crate::CELIX_SUCCESS, + Err(e) => e.into(), + } + } + + #[no_mangle] + pub unsafe extern "C" fn celix_bundleActivator_destroy( + handle: *mut ::std::ffi::c_void, + _ctx: *mut $crate::details::CBundleContext, + ) -> $crate::details::CStatus { + let reclaimed_activator = Box::from_raw(handle as *mut $activator); + drop(reclaimed_activator); + $crate::CELIX_SUCCESS + } + }; +} diff --git a/misc/experimental/rust/celix/src/bundle_context.rs b/misc/experimental/rust/celix/src/bundle_context.rs new file mode 100644 index 000000000..e944dfa48 --- /dev/null +++ b/misc/experimental/rust/celix/src/bundle_context.rs @@ -0,0 +1,669 @@ +/* + * 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. + */ + +use std::any::type_name; +use std::any::Any; +use std::collections::HashMap; +use std::ffi::c_void; +use std::ops::Deref; +use std::ptr::null_mut; +use std::sync::Arc; +use std::sync::Mutex; +use std::sync::Weak; + +use celix_bindings::{celix_bundleContext_log, celix_bundleContext_stopTracker, celix_bundleContext_stopTrackerAsync, celix_bundleContext_trackServicesWithOptions, celix_bundleContext_trackServicesWithOptionsAsync, celix_properties_t}; +use celix_bindings::celix_service_filter_options; +use celix_bindings::celix_bundleContext_registerServiceWithOptionsAsync; +use celix_bindings::celix_bundleContext_unregisterServiceAsync; +use celix_bindings::celix_service_tracking_options_t; +use celix_bindings::celix_service_use_options_t; +use celix_bindings::celix_service_filter_options_t; +use celix_bindings::celix_bundleContext_useServicesWithOptions; +use celix_bindings::celix_bundleContext_useServiceWithOptions; +use celix_bindings::celix_bundleContext_registerServiceWithOptions; +use celix_bindings::celix_bundleContext_unregisterService; +use celix_bindings::celix_bundle_context_t; +use celix_bindings::celix_properties_create; +use celix_bindings::celix_properties_set; +use celix_bindings::celix_service_registration_options_t; + +use super::Error; +use super::LogLevel; + +pub struct ServiceRegistration { + service_id: i64, + service_name: String, + unregister_async: bool, + weak_ctx: Weak, + svc: Option>, + unmanaged_svc: Option<*mut dyn Any>, +} + +impl ServiceRegistration { + pub fn get_service_id(&self) -> i64 { + self.service_id + } + + pub fn get_service_name(&self) -> &str { + self.service_name.as_str() + } + + pub fn get_service(&self) -> Option<&dyn Any> { + if let Some(boxed_svc) = self.svc.as_ref() { + Some(boxed_svc.as_ref()) + } else if let Some(unmanaged_svc) = self.unmanaged_svc { + unsafe { + Some(&*unmanaged_svc) + } + } else { + None + } + } +} + +impl Drop for ServiceRegistration { + fn drop(&mut self) { + let ctx = self.weak_ctx.upgrade(); + match ctx { + Some(ctx) => ctx.unregister_service(self.service_id, self.unregister_async), + None => println!("Cannot unregister ServiceRegistration: BundleContext is gone"), + } + } +} + +pub struct ServiceRegistrationBuilder<'a, T: ?Sized + 'static> { + ctx: &'a BundleContext, + register_async: bool, + unregister_async: bool, + svc: Option>, //note box is needed for stable pointer value + unmanaged_svc: Option<*mut dyn Any>, + service_name: String, + service_version: String, + service_properties: HashMap, +} + +impl ServiceRegistrationBuilder<'_, T> { + fn new(ctx: &BundleContext) -> ServiceRegistrationBuilder { + ServiceRegistrationBuilder { + ctx, + register_async: false, + unregister_async: false, + svc: None, + unmanaged_svc: None, + 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_service_name_if_not_set(&mut self) -> &mut Self { + if self.service_name.is_empty() { + self.service_name = type_name::().to_string(); + } + self + } + + pub fn with_service(&mut self, svc: T) -> &mut Self { + self.svc = Some(Box::new(svc)); + self.with_service_name_if_not_set() + } + + pub fn with_unmanaged_service(&mut self, svc: *mut T) -> &mut Self { + self.unmanaged_svc = Some(svc); + self.with_service_name_if_not_set() + } + + 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) -> &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 + } + + pub fn with_register_async(&mut self) -> &mut Self { + self.register_async = true; + self + } + + pub fn with_register_sync(&mut self) -> &mut Self { + self.register_async = false; + self + } + + pub fn with_unregister_async(&mut self) -> &mut Self { + self.unregister_async = true; + self + } + + pub fn with_unregister_sync(&mut self) -> &mut Self { + self.unregister_async = false; + 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.svc.is_none() && self.unmanaged_svc.is_none() { + self.ctx + .log_error("Cannot register service. No instance provided"); + valid = false; + } + match valid { + true => Ok(()), + false => Err(Error::BundleException), + } + } + + fn get_c_svc(svc_reg: &ServiceRegistration) -> *mut c_void { + if let Some(boxed_svc) = svc_reg.svc.as_ref() { + boxed_svc.deref() as *const dyn Any as *mut c_void + } else if let Some(unmanaged_svc) = svc_reg.unmanaged_svc { + unmanaged_svc as *mut c_void + } else { + null_mut() + } + } + + pub fn build(&mut self) -> Result { + self.validate()?; + + let mut svc_reg = ServiceRegistration { + service_id: -1, + service_name: self.service_name.clone(), + unregister_async: self.unregister_async, + weak_ctx: self.ctx.get_self().clone(), + svc: if self.svc.is_none() { None } else { Some(self.svc.take().unwrap()) }, + unmanaged_svc: self.unmanaged_svc, + }; + + let svc_ptr = Self::get_c_svc(&svc_reg); + 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: *mut celix_properties_t; + unsafe { + c_service_properties = celix_properties_create(); + 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: svc_ptr, + 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, + }; + + if self.register_async { + unsafe { + svc_reg.service_id = celix_bundleContext_registerServiceWithOptions(self.ctx.get_c_bundle_context(), &opts); + } + } else { + unsafe { + svc_reg.service_id = celix_bundleContext_registerServiceWithOptionsAsync(self.ctx.get_c_bundle_context(), &opts); + } + } + if svc_reg.service_id >= 0 { + Ok(svc_reg) + } else { + Err(Error::BundleException) + } + } +} + +pub struct ServiceUseBuilder<'a, T> { + ctx: &'a BundleContext, + many: bool, + service_name: String, + filter: String, + callback: Option>, //note double boxed +} + +impl ServiceUseBuilder<'_, T> { + fn new(ctx: &BundleContext, many: bool) -> ServiceUseBuilder { + ServiceUseBuilder { + ctx, + many, + service_name: type_name::().to_string(), + filter: "".to_string(), + callback: None, + } + } + + pub fn with_callback(&mut self, closure: Box)-> &mut Self { + self.callback = Some(closure); + self + } + + pub fn with_service_name(&mut self, name: &str) -> &mut Self { + self.service_name = name.to_string(); + self + } + + pub fn with_filter(&mut self, filter: &str) -> &mut Self { + self.filter = filter.to_string(); + self + } + + fn validate(&self) -> Result<(), Error> { + if self.service_name.is_empty() { + return Err(Error::BundleException); + } + Ok(()) + } + + unsafe extern "C" fn use_service_c_callback(handle: *mut c_void, svc: *mut c_void) { + let closure = handle as *const Box; + let closure = closure.as_ref().unwrap(); + + let typed_svc = svc as *const T; + let typed_svc = typed_svc.as_ref().unwrap(); + + closure(typed_svc); + } + + pub fn build(&mut self) -> Result { + self.validate()?; + + let c_service_name = std::ffi::CString::new(self.service_name.as_str()).unwrap(); + let c_filter = std::ffi::CString::new(self.filter.as_str()).unwrap(); + let c_service_name_ptr: *const i8 = c_service_name.as_ptr(); + let c_filter_ptr: *const i8 = if self.filter.is_empty() { null_mut()} else {c_filter.as_ptr() }; + + let c_closure_ptr = self.callback.as_ref().unwrap() as *const Box as *mut c_void; + + let opts = celix_service_use_options_t { + filter: celix_service_filter_options_t { + serviceName: c_service_name_ptr, + versionRange: null_mut(), + filter: c_filter_ptr, + serviceLanguage: null_mut(), + ignoreServiceLanguage: false, + }, + waitTimeoutInSeconds: 0.0, + callbackHandle: c_closure_ptr, + use_: Some(Self::use_service_c_callback), + useWithProperties: None, + useWithOwner: None, + flags: 0, + }; + + if self.many { + unsafe { + let count = celix_bundleContext_useServicesWithOptions(self.ctx.get_c_bundle_context(), &opts); + Ok(count as isize) + } + } else { + unsafe { + let called = celix_bundleContext_useServiceWithOptions(self.ctx.get_c_bundle_context(), &opts); + if called { + Ok(1) + } else { + Ok(0) + } + } + } + } +} + +struct ServiceTrackerCallbacks { + set_callback: Option)>>, + add_callback: Option>, + remove_callback: Option>, +} + +pub struct ServiceTracker { + ctx: Arc, + tracker_id: i64, + // shared_data: Mutex>, + // data_condition: Condvar, + callbacks: Box>, //Note in a box to ensure pointer value is stable after move + stop_async: bool, +} + +impl ServiceTracker { + pub fn close(&mut self) { + self.ctx.stop_tracker(self.tracker_id, self.stop_async); + self.tracker_id = -1; + } +} + +impl Drop for ServiceTracker { + fn drop(&mut self) { + self.close(); + } +} + +pub struct ServiceTrackerBuilder<'a, T> { + ctx: &'a BundleContext, + service_name: String, + filter: String, + track_async: bool, + stop_async: bool, + set_callback: Option)>>, + add_callback: Option>, + remove_callback: Option>, +} + +impl ServiceTrackerBuilder<'_, T> { + fn new(ctx: &BundleContext) -> ServiceTrackerBuilder { + ServiceTrackerBuilder { + ctx, + service_name: type_name::().to_string(), + filter: "".to_string(), + track_async: false, + stop_async: false, + set_callback: None, + add_callback: None, + remove_callback: None, + } + } + + pub fn with_service_name(&mut self, name: &str) -> &mut Self { + self.service_name = name.to_string(); + self + } + + pub fn with_filter(&mut self, filter: &str) -> &mut Self { + self.filter = filter.to_string(); + self + } + + pub fn with_set_callback(&mut self, closure: Box)>) -> &mut Self { + self.set_callback = Some(closure); + self + } + + pub fn with_add_callback(&mut self, closure: Box) -> &mut Self { + self.add_callback = Some(closure); + self + } + + pub fn with_remove_callback(&mut self, closure: Box) -> &mut Self { + self.remove_callback = Some(closure); + self + } + + pub fn with_track_async(&mut self) -> &mut Self { + self.track_async = true; + self + } + + pub fn with_track_sync(&mut self) -> &mut Self { + self.track_async = false; + self + } + + pub fn with_stop_async(&mut self) -> &mut Self { + self.stop_async = true; + self + } + + pub fn with_stop_sync(&mut self) -> &mut Self { + self.stop_async = false; + self + } + + fn validate(&self) -> Result<(), Error> { + if self.service_name.is_empty() { + return Err(Error::BundleException); + } + Ok(()) + } + + unsafe extern "C" fn set_callback_for_c(handle: *mut ::std::os::raw::c_void, svc: *mut ::std::os::raw::c_void) { + let callbacks = handle as *const ServiceTrackerCallbacks; + let callbacks = callbacks.as_ref().unwrap(); + + if svc.is_null() { + if let Some(set_callback) = callbacks.set_callback.as_ref() { + set_callback(None); + } + } else { + let typed_svc = svc as *const T; + let typed_svc = typed_svc.as_ref().unwrap(); + if let Some(set_callback) = callbacks.set_callback.as_ref() { + set_callback(Some(typed_svc)); + } + } + } + + unsafe extern "C" fn add_callback_for_c(handle: *mut ::std::os::raw::c_void, svc: *mut ::std::os::raw::c_void) { + let callbacks = handle as *const ServiceTrackerCallbacks; + let callbacks = callbacks.as_ref().unwrap(); + + let typed_svc = svc as *const T; + let typed_svc = typed_svc.as_ref().unwrap(); + + if let Some(add_callback) = callbacks.add_callback.as_ref() { + add_callback(typed_svc); + } + } + + unsafe extern "C" fn remove_callback_for_c(handle: *mut ::std::os::raw::c_void, svc: *mut ::std::os::raw::c_void) { + let callbacks = handle as *const ServiceTrackerCallbacks; + let callbacks = callbacks.as_ref().unwrap(); + + let typed_svc = svc as *const T; + let typed_svc = typed_svc.as_ref().unwrap(); + + if let Some(remove_callback) = callbacks.remove_callback.as_ref() { + remove_callback(typed_svc); + } + } + + pub fn build(&mut self) -> Result, Error> { + self.validate()?; + + let mut svc_tracker = ServiceTracker { + ctx: self.ctx.get_self().upgrade().unwrap(), + tracker_id: -1, + callbacks: Box::new(ServiceTrackerCallbacks { + set_callback: self.set_callback.take(), + add_callback: self.add_callback.take(), + remove_callback: self.remove_callback.take(), + }), + stop_async: self.stop_async, + }; + + let c_service_name = std::ffi::CString::new(self.service_name.as_str()).unwrap(); + let c_filter = std::ffi::CString::new(self.filter.as_str()).unwrap(); + let c_callback_handle = svc_tracker.callbacks.as_ref() as *const ServiceTrackerCallbacks as *mut c_void; + + let opts = celix_service_tracking_options_t{ + filter: celix_service_filter_options { + serviceName: c_service_name.as_ptr(), + versionRange: null_mut(), + filter: if self.filter.is_empty() { null_mut() } else { c_filter.as_ptr() }, + serviceLanguage: null_mut(), + ignoreServiceLanguage: false, + }, + callbackHandle: c_callback_handle, + set: Some(Self::set_callback_for_c), + setWithProperties: None, + setWithOwner: None, + add: Some(Self::add_callback_for_c), + addWithProperties: None, + addWithOwner: None, + remove: Some(Self::remove_callback_for_c), + removeWithProperties: None, + removeWithOwner: None, + trackerCreatedCallbackData: null_mut(), + trackerCreatedCallback: None, + }; + + let svc_tracker_id: i64; + unsafe { + if self.track_async { + svc_tracker_id = celix_bundleContext_trackServicesWithOptionsAsync(self.ctx.get_c_bundle_context(), &opts); + } else { + svc_tracker_id = celix_bundleContext_trackServicesWithOptions(self.ctx.get_c_bundle_context(), &opts); + } + } + + if svc_tracker_id >= 0 { + svc_tracker.tracker_id = svc_tracker_id; + Ok(svc_tracker) + } else { + Err(Error::BundleException) + } + } +} + +pub struct BundleContext { + c_bundle_context: *mut celix_bundle_context_t, + weak_self: Mutex>>, +} + +impl BundleContext { + fn new(c_bundle_context: *mut celix_bundle_context_t) -> Arc { + let ctx = Arc::new(BundleContext { + 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) { + let mut guard = self.weak_self.lock().unwrap(); + *guard = Some(weak_self); + } + + fn get_self(&self) -> Weak { + self.weak_self.lock().unwrap().clone().unwrap() + } + + fn log_to_c(&self, level: LogLevel, message: &str) { + unsafe { + let result = std::ffi::CString::new(message); + match result { + Ok(c_str) => { + celix_bundleContext_log( + self.c_bundle_context, + level.into(), + c_str.as_ptr() as *const i8, + ); + } + Err(e) => { + println!("Error creating CString: {}", e); + } + } + } + } + + pub fn get_c_bundle_context(&self) -> *mut celix_bundle_context_t { + self.c_bundle_context + } + + pub fn log(&self, level: LogLevel, message: &str) { + self.log_to_c(level, message); + } + + pub fn log_trace(&self, message: &str) { + self.log(LogLevel::Trace, message); + } + + pub fn log_debug(&self, message: &str) { + self.log(LogLevel::Debug, message); + } + + pub fn log_info(&self, message: &str) { + self.log(LogLevel::Info, message); + } + + pub fn log_warning(&self, message: &str) { + self.log(LogLevel::Warning, message); + } + + pub fn log_error(&self, message: &str) { + self.log(LogLevel::Error, message); + } + + pub fn log_fatal(&self, message: &str) { + self.log(LogLevel::Fatal, message); + } + + pub fn register_service(&self) -> ServiceRegistrationBuilder { + ServiceRegistrationBuilder::new(self) + } + + fn unregister_service(&self, service_id: i64, unregister_async: bool) { + unsafe { + if unregister_async { + celix_bundleContext_unregisterServiceAsync(self.c_bundle_context, service_id, null_mut(), None); + } else { + celix_bundleContext_unregisterService(self.c_bundle_context, service_id); + } + } + } + + pub fn use_service(&self) -> ServiceUseBuilder { + ServiceUseBuilder::new(self, false) + } + + pub fn use_services(&self) -> ServiceUseBuilder { + ServiceUseBuilder::new(self, true) + } + + pub fn track_services(&self) -> ServiceTrackerBuilder { ServiceTrackerBuilder::new(self) } + + fn stop_tracker(&self, tracker_id: i64, stop_async: bool) { + unsafe { + if stop_async { + celix_bundleContext_stopTrackerAsync(self.c_bundle_context, tracker_id, null_mut(), None); + } else { + celix_bundleContext_stopTracker(self.c_bundle_context, tracker_id); + } + } + } +} + +pub fn bundle_context_new(c_bundle_context: *mut celix_bundle_context_t) -> Arc { + BundleContext::new(c_bundle_context) +} diff --git a/misc/experimental/rust/celix/src/errno.rs b/misc/experimental/rust/celix/src/errno.rs new file mode 100644 index 000000000..4f9f85ee0 --- /dev/null +++ b/misc/experimental/rust/celix/src/errno.rs @@ -0,0 +1,59 @@ +/* + * 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. + */ + +use std::fmt::{Debug, Formatter}; +use celix_bindings::celix_status_t; + +pub const CELIX_SUCCESS: celix_status_t = celix_bindings::CELIX_SUCCESS as celix_status_t; + +//Note compile-time defined constants are not available in rust generated bindings, so +//these are defined with literal values. +pub const BUNDLE_EXCEPTION: celix_status_t = 70001; + +pub enum Error { + BundleException, + CelixStatusError(celix_status_t), // Represent not explicitly mapped celix_status_t values +} + +impl From for Error { + fn from(status: celix_status_t) -> Self { + match status { + BUNDLE_EXCEPTION => Error::BundleException, + _ => Error::CelixStatusError(status), + } + } +} + +impl Into for Error { + fn into(self) -> celix_status_t { + match self { + Error::BundleException => BUNDLE_EXCEPTION, + Error::CelixStatusError(status) => status, + } + } +} + +impl Debug for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Error::BundleException => write!(f, "BundleException"), + Error::CelixStatusError(status) => write!(f, "CelixStatusError({})", status), + } + } +} diff --git a/misc/experimental/rust/celix/src/lib.rs b/misc/experimental/rust/celix/src/lib.rs new file mode 100644 index 000000000..5ad567172 --- /dev/null +++ b/misc/experimental/rust/celix/src/lib.rs @@ -0,0 +1,52 @@ +/* + * 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. + */ + +// Re-export the celix_status_t and celix_bundle_context_t C API in this crate public API so that +// it can be used in the generate_bundle_activator macro. +// Note that as result the celix rust lib is leaking the celix_status_t and celix_bundle_context_t +// C API in its public API. +#[doc(hidden)] +pub mod details { + pub use celix_bindings::celix_bundle_context_t as CBundleContext; + pub use celix_bindings::celix_status_t as CStatus; +} + +mod errno; +// Re-export errno types in the public API. +pub use self::errno::Error; +pub use self::errno::CELIX_SUCCESS; + +mod log_level; +// Re-export log level types in the public API. +pub use self::log_level::LogLevel; + +mod bundle_context; +// Re-export bundle context types in the public API. +pub use self::bundle_context::bundle_context_new; +pub use self::bundle_context::BundleContext; +pub use self::bundle_context::ServiceRegistration; +pub use self ::bundle_context::ServiceTracker; + +mod bundle_activator; +// Re-export bundle activator types in the public API. +pub use self::bundle_activator::BundleActivator; + +mod log_helper; +// Re-export log helper types in the public API. +pub use self::log_helper::LogHelper; diff --git a/misc/experimental/rust/celix/src/log_helper.rs b/misc/experimental/rust/celix/src/log_helper.rs new file mode 100644 index 000000000..e62238c3c --- /dev/null +++ b/misc/experimental/rust/celix/src/log_helper.rs @@ -0,0 +1,113 @@ +/* + * 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. + */ + +use std::sync::{Arc, Mutex, RwLock}; + +use super::BundleContext; +use super::LogLevel; + +use celix_bindings::celix_log_service_t; +use crate::ServiceTracker; + +pub struct LogHelper { + name: String, + tracker: Mutex>>, + log_svc: RwLock>, +} + +impl LogHelper { + + pub fn new(ctx: Arc, name: &str) -> Arc { + let helper = Arc::new(LogHelper{ + name: name.to_string(), + tracker: Mutex::new(None), + log_svc: RwLock::new(None), + }); + let filter = format!("(name={})", name); + let weak_helper = Arc::downgrade(&helper); + let tracker = ctx.track_services::() + .with_service_name("celix_log_service") + .with_filter(filter.as_str()) + .with_set_callback(Box::new(move|optional_svc| { + if let Some(helper) = weak_helper.upgrade() { + helper.set_celix_log_service(optional_svc); + } + })) + .build().unwrap(); + helper.tracker.lock().unwrap().replace(tracker); + helper + } + + pub fn get_name(&self) -> &str { + &self.name + } + + pub fn set_celix_log_service(&self, optional_svc: Option<&celix_log_service_t>) { + match optional_svc { + Some(svc) => { + let svc_ptr: *const celix_log_service_t = svc as *const celix_log_service_t; + self.log_svc.write().unwrap().replace(svc_ptr); + } + None => { + self.log_svc.write().unwrap().take(); + } + } + } + + pub fn log(&self, level: LogLevel, message: &str) { + let str_result = std::ffi::CString::new(message).unwrap(); + let guard = self.log_svc.read().unwrap(); + if let Some(svc) = guard.as_ref() { + unsafe { + if svc.is_null() { + return; + } + let svc = &**svc; + if svc.log.is_none() { + return; + } + let log_fn = svc.log.as_ref().unwrap(); + log_fn(svc.handle, level.into(), str_result.as_ptr()); + } + } + } + pub fn log_trace(&self, message: &str) { + self.log(LogLevel::Trace, message); + } + + pub fn log_debug(&self, message: &str) { + self.log(LogLevel::Debug, message); + } + + pub fn log_info(&self, message: &str) { + self.log(LogLevel::Info, message); + } + + pub fn log_warning(&self, message: &str) { + self.log(LogLevel::Warning, message); + } + + pub fn log_error(&self, message: &str) { + self.log(LogLevel::Error, message); + } + + pub fn log_fatal(&self, message: &str) { + self.log(LogLevel::Fatal, message); + } +} diff --git a/misc/experimental/rust/celix/src/log_level.rs b/misc/experimental/rust/celix/src/log_level.rs new file mode 100644 index 000000000..5beab988d --- /dev/null +++ b/misc/experimental/rust/celix/src/log_level.rs @@ -0,0 +1,64 @@ +/* + * 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. + */ + +use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_DEBUG as CELIX_LOG_LEVEL_DEBUG; +use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_DISABLED as CELIX_LOG_LEVEL_DISABLED; +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_INFO as CELIX_LOG_LEVEL_INFO; +use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_TRACE as CELIX_LOG_LEVEL_TRACE; +use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_WARNING as CELIX_LOG_LEVEL_WARNING; +use celix_bindings::celix_log_level_e; + +pub enum LogLevel { + Trace, + Debug, + Info, + Warning, + Error, + Fatal, + Disabled, +} +impl From for LogLevel { + fn from(level: celix_log_level_e) -> Self { + match level { + 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, + } + } +} + +impl Into for LogLevel { + fn into(self) -> celix_log_level_e { + match self { + 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/celix_bindings/Cargo.toml b/misc/experimental/rust/celix_bindings/Cargo.toml index b46868b9a..221002531 100644 --- a/misc/experimental/rust/celix_bindings/Cargo.toml +++ b/misc/experimental/rust/celix_bindings/Cargo.toml @@ -18,6 +18,7 @@ [package] name = "celix_bindings" version = "0.0.1" +edition = '2021' [build-dependencies] bindgen = "0.66.1" diff --git a/misc/experimental/rust/celix_bindings/build.rs b/misc/experimental/rust/celix_bindings/build.rs index 8bae728bd..374f4d454 100644 --- a/misc/experimental/rust/celix_bindings/build.rs +++ b/misc/experimental/rust/celix_bindings/build.rs @@ -17,23 +17,37 @@ * under the License. */ -extern crate bindgen; - +use std::env; use std::error::Error; -use std::path::PathBuf; use std::fs::File; use std::io::{self, BufRead}; -use std::env; +use std::path::PathBuf; -fn print_include_paths() -> Result, Box> { - let build_dir = PathBuf::from(env::var("CORROSION_BUILD_DIR").unwrap()); - let include_path_file = build_dir.join("include_paths.txt"); +fn open_include_paths_file() -> Result> { + let file: File; - //let include_path_file = Path::new("include_paths.txt"); - let file = File::open(&include_path_file)?; + let corrosion_build_dir = env::var("CORROSION_BUILD_DIR"); + if corrosion_build_dir.is_ok() { + let build_dir = PathBuf::from(corrosion_build_dir.unwrap()); + let include_path_file = build_dir.join("include_paths.txt"); + file = File::open(&include_path_file)?; + } else { + println!("include_paths.txt not found in CORROSION_BUILD_DIR. Failing back to CELIX_RUST_INCLUDE_PATHS_FILE env value"); + let include_path_file = env::var("CELIX_RUST_INCLUDE_PATHS_FILE")?; + file = File::open(&include_path_file)?; + } + + Ok(file) +} + +fn print_include_paths() -> Result, Box> { + let file = open_include_paths_file()?; let reader = io::BufReader::new(file); let mut include_paths = Vec::new(); - let line = reader.lines().next().ok_or("Expected at least one line")??; + let line = reader + .lines() + .next() + .ok_or("Expected at least one line")??; for path in line.split(';') { include_paths.push(path.to_string()); } @@ -44,8 +58,7 @@ fn main() { println!("cargo:info=Start build.rs for celix_bindings"); let include_paths = print_include_paths().unwrap(); - let mut builder = bindgen::Builder::default() - .header("src/celix_bindings.h"); + let mut builder = bindgen::Builder::default().header("src/celix_bindings.h"); // Add framework and utils include paths for path in &include_paths { @@ -53,10 +66,7 @@ fn main() { } // Gen bindings - let bindings = builder - .generate() - .expect("Unable to generate bindings"); - + let bindings = builder.generate().expect("Unable to generate bindings"); let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); bindings diff --git a/misc/experimental/rust/celix_bindings/src/celix_bindings.h b/misc/experimental/rust/celix_bindings/src/celix_bindings.h index 8495c9a43..76c87b6a3 100644 --- a/misc/experimental/rust/celix_bindings/src/celix_bindings.h +++ b/misc/experimental/rust/celix_bindings/src/celix_bindings.h @@ -30,3 +30,6 @@ #include "celix_framework.h" #include "celix_framework_factory.h" #include "celix_framework_utils.h" + +#include "celix_shell_command.h" +#include "celix_log_service.h" diff --git a/misc/experimental/rust/celix_bindings/src/lib.rs b/misc/experimental/rust/celix_bindings/src/lib.rs index c69c75247..0fd4dc707 100644 --- a/misc/experimental/rust/celix_bindings/src/lib.rs +++ b/misc/experimental/rust/celix_bindings/src/lib.rs @@ -17,25 +17,13 @@ * under the License. */ -#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code)] - mod bindings { - include!(concat!(env!("OUT_DIR"), "/celix_bindings.rs")); - } -pub use bindings::*; - -//Note C #defines (compile-time constants) are not all generated in the bindings. -pub const CELIX_SUCCESS: celix_status_t = 0; -pub const CELIX_BUNDLE_EXCEPTION: celix_status_t = 70001; - -// Move to celix_api lib -pub mod celix { - - #[warn(unused_imports)] - pub enum LogLevel { - Trace = ::bindings::celix_log_level_CELIX_LOG_LEVEL_TRACE as isize, - Debug = ::bindings::celix_log_level_CELIX_LOG_LEVEL_DEBUG as isize, - Info = ::bindings::celix_log_level_CELIX_LOG_LEVEL_INFO as isize, - Warn = ::bindings::celix_log_level_CELIX_LOG_LEVEL_WARNING as isize, - Error = ::bindings::celix_log_level_CELIX_LOG_LEVEL_ERROR as isize, - } +#[allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + dead_code +)] +mod bindings { + include!(concat!(env!("OUT_DIR"), "/celix_bindings.rs")); } +pub use bindings::*; diff --git a/misc/experimental/rust/hello_world_activator/Cargo.toml b/misc/experimental/rust/hello_world_activator/Cargo.toml index 4844acd2c..378d5fb59 100644 --- a/misc/experimental/rust/hello_world_activator/Cargo.toml +++ b/misc/experimental/rust/hello_world_activator/Cargo.toml @@ -16,11 +16,12 @@ # under the License. [package] -name = "rust_bundle_activator" +name = "rust_bundle" version = "0.0.1" +edition = '2021' [dependencies] -celix_bindings = { path = "../celix_bindings" } +celix = { path = "../celix" } [lib] name = "rust_bundle_activator" diff --git a/misc/experimental/rust/hello_world_activator/src/lib.rs b/misc/experimental/rust/hello_world_activator/src/lib.rs index 17d835d48..d59212c28 100644 --- a/misc/experimental/rust/hello_world_activator/src/lib.rs +++ b/misc/experimental/rust/hello_world_activator/src/lib.rs @@ -17,91 +17,37 @@ * under the License. */ -extern crate celix_bindings; +use std::sync::Arc; -use std::error::Error; -use std::os::raw::c_void; -use std::ffi::CString; -use std::ffi::NulError; -use celix_bindings::*; //Add all Apache Celix C bindings to the namespace (i.e. celix_bundleContext_log, etc.) +use celix::BundleActivator; +use celix::BundleContext; +use celix::Error; -struct RustBundle { - name: String, - ctx: *mut celix_bundle_context_t, +struct HelloWorldBundle { + ctx: Arc, } -impl RustBundle { - - unsafe fn new(name: String, ctx: *mut celix_bundle_context_t) -> Result { - let result = RustBundle { - name, - ctx, - }; - result.log_lifecycle("created")?; - Ok(result) +impl BundleActivator for HelloWorldBundle { + fn new(ctx: Arc) -> Self { + ctx.log_info("Hello World Bundle Activator created"); + HelloWorldBundle { ctx } } - unsafe fn log_lifecycle(&self, event: &str) -> Result<(), NulError> { - let id = celix_bundleContext_getBundleId(self.ctx); - let c_string = CString::new(format!("Rust Bundle '{}' with id {} {}!", self.name, id, event))?; - celix_bundleContext_log(self.ctx, celix_log_level_CELIX_LOG_LEVEL_INFO, c_string.as_ptr()); + fn start(&mut self) -> Result<(), Error> { + self.ctx.log_info("Hello World Bundle Activator started"); Ok(()) } - unsafe fn start(&self) -> Result<(), NulError> { - self.log_lifecycle("started") - } - - unsafe fn stop(&self) -> Result<(), NulError> { - self.log_lifecycle("stopped") + fn stop(&mut self) -> Result<(), Error> { + self.ctx.log_info("Hello World Bundle Activator stopped"); + Ok(()) } } -impl Drop for RustBundle { +impl Drop for HelloWorldBundle { fn drop(&mut self) { - unsafe { - let result = self.log_lifecycle("destroyed"); - match result { - Ok(()) => (), - Err(e) => println!("Error while logging: {}", e), - } - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn celix_bundleActivator_create(ctx: *mut celix_bundle_context_t, data: *mut *mut c_void) -> celix_status_t { - let rust_bundle = RustBundle::new("Hello World".to_string(), ctx); - if rust_bundle.is_err() { - return CELIX_BUNDLE_EXCEPTION; + self.ctx.log_info("Hello World Bundle Activator destroyed"); } - *data = Box::into_raw(Box::new(rust_bundle.unwrap())) as *mut c_void; - CELIX_SUCCESS } -#[no_mangle] -pub unsafe extern "C" fn celix_bundleActivator_start(data: *mut c_void, _ctx: *mut celix_bundle_context_t) -> celix_status_t { - let rust_bundle = &*(data as *mut RustBundle); - let result = rust_bundle.start(); - match result { - Ok(()) => CELIX_SUCCESS, - Err(_) => CELIX_BUNDLE_EXCEPTION, - } -} - -#[no_mangle] -pub unsafe extern "C" fn celix_bundleActivator_stop(data: *mut c_void, _ctx: *mut celix_bundle_context_t) -> celix_status_t { - let rust_bundle = &*(data as *mut RustBundle); - let result = rust_bundle.stop(); - match result { - Ok(()) => CELIX_SUCCESS, - Err(_) => CELIX_BUNDLE_EXCEPTION, - } -} - -#[no_mangle] -pub unsafe extern "C" fn celix_bundleActivator_destroy(data: *mut c_void, _ctx: *mut celix_bundle_context_t) -> celix_status_t { - let rust_bundle = Box::from_raw(data as *mut RustBundle); - drop(rust_bundle); - CELIX_SUCCESS -} +celix::generate_bundle_activator!(HelloWorldBundle); diff --git a/misc/experimental/rust/rust_shell_api/Cargo.toml b/misc/experimental/rust/rust_shell_api/Cargo.toml new file mode 100644 index 000000000..ed9c1c646 --- /dev/null +++ b/misc/experimental/rust/rust_shell_api/Cargo.toml @@ -0,0 +1,29 @@ +# 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. + +[package] +name = "rust_shell_api" +version = "0.0.1" +edition = '2021' + +[dependencies] +celix = { path = "../celix" } + +[lib] +name = "rust_shell_api" +path = "src/lib.rs" +crate-type = ["rlib"] diff --git a/misc/experimental/rust/rust_shell_api/src/lib.rs b/misc/experimental/rust/rust_shell_api/src/lib.rs new file mode 100644 index 000000000..18a6a4060 --- /dev/null +++ b/misc/experimental/rust/rust_shell_api/src/lib.rs @@ -0,0 +1,28 @@ +/* + * 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. + */ + +use celix::Error; +pub const COMMAND_NAME: &str = "command.name"; +pub const COMMAND_USAGE: &str = "command.usage"; +pub const COMMAND_DESCRIPTION: &str = "command.description"; + +#[doc = "A trait to implement a Celix Shell Command"] +pub trait RustShellCommand { + fn execute_command(&self, command_line: &str) -> Result<(), Error>; +} diff --git a/misc/experimental/rust/shell_command_bundle/Cargo.toml b/misc/experimental/rust/shell_command_bundle/Cargo.toml new file mode 100644 index 000000000..aba9f73b1 --- /dev/null +++ b/misc/experimental/rust/shell_command_bundle/Cargo.toml @@ -0,0 +1,31 @@ +# 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. + +[package] +name = "rust_shell_command_activator" +version = "0.0.1" +edition = '2021' + +[dependencies] +celix_bindings = { path = "../celix_bindings" } +celix = { path = "../celix" } +rust_shell_api = { path = "../rust_shell_api" } + +[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 000000000..9bd7f174a --- /dev/null +++ b/misc/experimental/rust/shell_command_bundle/src/lib.rs @@ -0,0 +1,180 @@ +/* + * 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. + */ + +use std::ffi::c_char; +use std::ffi::c_void; +use std::sync::Arc; + +use celix::{BundleActivator, LogHelper}; +use celix::BundleContext; +use celix::Error; +use rust_shell_api::RustShellCommand; + +use celix_bindings::celix_shell_command_t; +use celix_bindings::FILE; + +struct CShellCommandImpl { + ctx: Arc, +} + +impl CShellCommandImpl { + fn new(ctx: Arc) -> Self { + ctx.log_info("Shell Command created"); + CShellCommandImpl { 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 { + let obj = &mut *(handle as *mut CShellCommandImpl); + let str_command_line = std::ffi::CStr::from_ptr(command_line).to_str(); + if str_command_line.is_err() { + return false; + } + obj.execute_command(str_command_line.unwrap()); + } + true + } + + fn execute_command(&mut self, command_line: &str) { + self.ctx.log_info(format!("Execute command: \"{}\"", command_line).as_str()); + } +} + +//temporary, should be moved in a separate API crate + +struct RustShellCommandImpl { + ctx: Arc, +} + +impl RustShellCommandImpl { + fn new(ctx: Arc) -> Self { + ctx.log_info("Rust Shell Command created"); + RustShellCommandImpl { ctx } + } +} + +impl RustShellCommand for RustShellCommandImpl { + fn execute_command(&self, command_line: &str) -> Result<(), Error> { + self.ctx + .log_info(format!("Execute command: {}.", command_line).as_str()); + Ok(()) + } +} + +struct ShellCommandActivator { + ctx: Arc, + log_helper: Arc, + shell_command_provider: CShellCommandImpl, + registrations: Vec, +} + +impl ShellCommandActivator { + + fn register_services(&mut self) -> Result<(), Error> { + //Register C service registered as value + let registration = self.ctx.register_service() + .with_service(celix_shell_command_t { + handle: &mut self.shell_command_provider as *mut CShellCommandImpl as *mut c_void, + executeCommand: Some(CShellCommandImpl::call_execute_command), + }) + .with_service_name("celix_shell_command") + .with_property("command.name", "exe_c_command_in_rust") + .with_property("command.description", "Simple command written in Rust") + .build()?; + self.registrations.push(registration); + self.log_helper.log_info("C Shell Command registered"); + + //Register Rust trait service register using a Arc + let rust_shell_command: Arc = Arc::new(RustShellCommandImpl::new(self.ctx.clone())); + let registration = self.ctx.register_service() + .with_service(rust_shell_command) + .with_property(rust_shell_api::COMMAND_NAME, "exe_rust_command") + .with_property(rust_shell_api::COMMAND_DESCRIPTION, "Simple command written in a Rust trait") + .build()?; + self.registrations.push(registration); + self.log_helper.log_info("Rust trait Shell Command registered"); + + + Ok(()) + } + + fn use_services(&mut self) -> Result<(), Error> { + //test using C service + + self.log_helper.log_info("Use C service command service"); + let count = self.ctx.use_services() + .with_service_name("celix_shell_command") + .with_filter("(command.name=exe_c_command_in_rust)") + .with_callback(Box::new( |svc: &celix_shell_command_t| { + if let Some(exe_cmd) = svc.executeCommand { + let c_str = std::ffi::CString::new("test c service").unwrap(); + unsafe { + exe_cmd(svc.handle, c_str.as_ptr() as *const c_char, std::ptr::null_mut(), std::ptr::null_mut()); + } + } + })) + .build()?; + self.log_helper.log_info(format!("Found {} celix_shell_command_t services", count).as_str()); + + //test using Rust trait service + self.log_helper.log_info("Use Rust trait service command service"); + let count = self.ctx.use_services() + .with_callback(Box::new( |svc: &Arc| { + let _ = svc.execute_command("test rest trait"); + })) + .build()?; + self.log_helper.log_info(format!("Found {} RustShellCommandTrait services", count).as_str()); + + Ok(()) + } +} + +impl BundleActivator for ShellCommandActivator { + fn new(ctx: Arc) -> Self { + let result = ShellCommandActivator { + ctx: ctx.clone(), + shell_command_provider: CShellCommandImpl::new(ctx.clone()), + log_helper: LogHelper::new(ctx.clone(), "ShellCommandBundle"), + registrations: Vec::new(), + }; + result + } + fn start(&mut self) -> Result<(), Error> { + self.register_services()?; + self.use_services()?; + self.ctx.log_info("Rust Shell Command started"); + Ok(()) + } + + fn stop(&mut self) -> Result<(), Error> { + self.registrations.clear(); + self.ctx.log_info("Rust Shell Command stopped"); + Ok(()) + } +} + +celix::generate_bundle_activator!(ShellCommandActivator);