diff --git a/misc/experimental/rust/CMakeLists.txt b/misc/experimental/rust/CMakeLists.txt index b7353468b..e2236a1af 100644 --- a/misc/experimental/rust/CMakeLists.txt +++ b/misc/experimental/rust/CMakeLists.txt @@ -25,8 +25,16 @@ 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 "$;$" + ) + corrosion_import_crate(MANIFEST_PATH Cargo.toml) - corrosion_add_target_local_rustflags(rust_bundle_activator "-Cprefer-dynamic") + + corrosion_add_target_local_rustflags(rust_bundle_activator "-Cprefer-dynamic") + corrosion_link_libraries(rust_bundle_activator Celix::framework) #Note corrosion_import_crate import creates a rust_bundle_activator CMake target, but this is a INTERFACE target. #Using the INTERFACE_LINK_LIBRARIES property we can get the actual target. @@ -41,5 +49,4 @@ if (CELIX_RUST_EXPERIMENTAL) Celix::shell_tui rust_bundle ) - endif () diff --git a/misc/experimental/rust/Cargo.toml b/misc/experimental/rust/Cargo.toml index f0ed38b9c..8727be5d9 100644 --- a/misc/experimental/rust/Cargo.toml +++ b/misc/experimental/rust/Cargo.toml @@ -17,5 +17,6 @@ [workspace] members = [ + "celix_bindings", "hello_world_activator", ] diff --git a/misc/experimental/rust/README.md b/misc/experimental/rust/README.md index 552781e90..275886812 100644 --- a/misc/experimental/rust/README.md +++ b/misc/experimental/rust/README.md @@ -26,3 +26,26 @@ and is not intended to be used in production. Ideally Rust support is done by adding a Rust API for Apache Celix and use that API for Rust bundles, the current implementation only shows that is possible to write a bundle in Rust that gets called by Apache Celix framework. + +# Building + +To build rust source the Corrosion cmake module is used. This module will be automatically downloaded when building +using `FetchContent_Declare`. + +To actual build the rust source files both the rust compiler and cargo are required. For ubuntu this can be installed +using apt: +```bash +sudo apt install rustc cargo +``` +It is possible to define C bindings in rust manually, but this is a lot of work and error prone. Rust has a tool called +bindgen which can generate C bindings from a C header file. This is used to generate the C bindings for Apache Celix. +bindgen is used in the build.rs of the rust package `celix_bindings`. + +It is also possible to run bindgen manually, for example: +```bash +cargo install bindgen # install bindgen, only needed once +PATH=$PATH:$HOME/.cargo/bin # add cargo bin to path + +cd misc/experimental/rust/celix_bindings +bindgen -o src/celix_bindings.rs celix_bindings.h -- -I -I +``` diff --git a/misc/experimental/rust/celix_bindings/Cargo.toml b/misc/experimental/rust/celix_bindings/Cargo.toml new file mode 100644 index 000000000..b46868b9a --- /dev/null +++ b/misc/experimental/rust/celix_bindings/Cargo.toml @@ -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. + +[package] +name = "celix_bindings" +version = "0.0.1" + +[build-dependencies] +bindgen = "0.66.1" + +[lib] +name = "celix_bindings" +path = "src/lib.rs" +crate-type = ["rlib"] diff --git a/misc/experimental/rust/celix_bindings/build.rs b/misc/experimental/rust/celix_bindings/build.rs new file mode 100644 index 000000000..8bae728bd --- /dev/null +++ b/misc/experimental/rust/celix_bindings/build.rs @@ -0,0 +1,65 @@ +/* + * 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 bindgen; + +use std::error::Error; +use std::path::PathBuf; +use std::fs::File; +use std::io::{self, BufRead}; +use std::env; + +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"); + + //let include_path_file = Path::new("include_paths.txt"); + let file = File::open(&include_path_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")??; + for path in line.split(';') { + include_paths.push(path.to_string()); + } + Ok(include_paths) +} + +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"); + + // Add framework and utils include paths + for path in &include_paths { + builder = builder.clang_arg(format!("-I{}", path)); + } + + // Gen bindings + let bindings = builder + .generate() + .expect("Unable to generate bindings"); + + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("celix_bindings.rs")) + .expect("Couldn't write Apache Celix bindings!"); +} diff --git a/misc/experimental/rust/celix_bindings/src/celix_bindings.h b/misc/experimental/rust/celix_bindings/src/celix_bindings.h new file mode 100644 index 000000000..8495c9a43 --- /dev/null +++ b/misc/experimental/rust/celix_bindings/src/celix_bindings.h @@ -0,0 +1,32 @@ +/* + * 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. + */ + +/** + * @file celix_bindings.h + * @brief A header files that includes all Apache Celix headers used to create bindings. +*/ + +#include "celix_errno.h" +#include "celix_properties.h" +#include "celix_filter.h" + +#include "celix_bundle_context.h" +#include "celix_framework.h" +#include "celix_framework_factory.h" +#include "celix_framework_utils.h" diff --git a/misc/experimental/rust/celix_bindings/src/lib.rs b/misc/experimental/rust/celix_bindings/src/lib.rs new file mode 100644 index 000000000..c69c75247 --- /dev/null +++ b/misc/experimental/rust/celix_bindings/src/lib.rs @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#[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, + } +} diff --git a/misc/experimental/rust/hello_world_activator/Cargo.toml b/misc/experimental/rust/hello_world_activator/Cargo.toml index e3795ca0f..4844acd2c 100644 --- a/misc/experimental/rust/hello_world_activator/Cargo.toml +++ b/misc/experimental/rust/hello_world_activator/Cargo.toml @@ -17,8 +17,12 @@ [package] name = "rust_bundle_activator" -version = "0.1.0" +version = "0.0.1" + +[dependencies] +celix_bindings = { path = "../celix_bindings" } [lib] name = "rust_bundle_activator" +path = "src/lib.rs" crate-type = ["cdylib"] diff --git a/misc/experimental/rust/hello_world_activator/src/lib.rs b/misc/experimental/rust/hello_world_activator/src/lib.rs index 19e6d05a0..17d835d48 100644 --- a/misc/experimental/rust/hello_world_activator/src/lib.rs +++ b/misc/experimental/rust/hello_world_activator/src/lib.rs @@ -17,16 +17,91 @@ * under the License. */ +extern crate celix_bindings; + +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.) + +struct RustBundle { + name: String, + ctx: *mut celix_bundle_context_t, +} + +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) + } + + 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()); + Ok(()) + } + + unsafe fn start(&self) -> Result<(), NulError> { + self.log_lifecycle("started") + } + + unsafe fn stop(&self) -> Result<(), NulError> { + self.log_lifecycle("stopped") + } +} + +impl Drop for RustBundle { + 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; + } + *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_start(_data: *mut c_void, _context: *mut c_void) -> i32 { - println!("Rust Bundle started!"); - 0 +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_stop(_data: *mut c_void, _context: *mut c_void) -> i32 { - println!("Rust Bundle stopped!"); - 0 +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 }