Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/599 add rust bindings #603

Merged
merged 3 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions misc/experimental/rust/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 "$<TARGET_PROPERTY:framework,INTERFACE_INCLUDE_DIRECTORIES>;$<TARGET_PROPERTY:utils,INTERFACE_INCLUDE_DIRECTORIES>"
)

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.
Expand All @@ -41,5 +49,4 @@ if (CELIX_RUST_EXPERIMENTAL)
Celix::shell_tui
rust_bundle
)

endif ()
1 change: 1 addition & 0 deletions misc/experimental/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@

[workspace]
members = [
"celix_bindings",
"hello_world_activator",
]
23 changes: 23 additions & 0 deletions misc/experimental/rust/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<celix_framework_include_dir> -I<celix_utils_include_dir>
```
28 changes: 28 additions & 0 deletions misc/experimental/rust/celix_bindings/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"]
65 changes: 65 additions & 0 deletions misc/experimental/rust/celix_bindings/build.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<String>, Box<dyn Error>> {
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!");
}
32 changes: 32 additions & 0 deletions misc/experimental/rust/celix_bindings/src/celix_bindings.h
Original file line number Diff line number Diff line change
@@ -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"
41 changes: 41 additions & 0 deletions misc/experimental/rust/celix_bindings/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
6 changes: 5 additions & 1 deletion misc/experimental/rust/hello_world_activator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
87 changes: 81 additions & 6 deletions misc/experimental/rust/hello_world_activator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RustBundle, NulError> {
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
}
Loading