Skip to content

Commit

Permalink
Introduce capi crate
Browse files Browse the repository at this point in the history
This change moves all C API functionality into the cblazesym crate in
the existing workspace. Doing so ensures better isolation between the
core Rust part and the C API and it will allow us to version the C API
different from the main Rust crate.

Closes: libbpf#344

Signed-off-by: Daniel Müller <[email protected]>
  • Loading branch information
d-e-s-o committed Nov 16, 2023
1 parent b80d77a commit f001add
Show file tree
Hide file tree
Showing 18 changed files with 396 additions and 215 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ jobs:
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Test and gather coverage
run: cargo llvm-cov --all-targets --features=nightly,generate-large-test-files --lcov --output-path lcov.info
run: cargo llvm-cov --workspace --all-targets --features=nightly,generate-large-test-files --lcov --output-path lcov.info
- name: Upload code coverage results
uses: codecov/codecov-action@v3
with:
Expand Down Expand Up @@ -117,7 +117,7 @@ jobs:
RUSTFLAGS: "-Zsanitizer=${{ matrix.sanitizer }}"
ASAN_OPTIONS: "detect_odr_violation=0:detect_leaks=0"
LSAN_OPTIONS: ""
run: cargo test --lib --tests --target x86_64-unknown-linux-gnu
run: cargo test --workspace --lib --tests --target x86_64-unknown-linux-gnu
test-release:
name: Test with release build
runs-on: ubuntu-22.04
Expand Down Expand Up @@ -162,7 +162,7 @@ jobs:
toolchain: stable
profile: minimal
override: true
- run: cargo check --workspace --features=generate-c-header
- run: cargo check --package=cblazesym --features=generate-c-header
- name: Check that C header is up-to-date
run: git diff --exit-code ||
(echo "!!!! CHECKED IN C HEADER IS OUTDATED !!!!" && false)
Expand Down Expand Up @@ -234,4 +234,4 @@ jobs:
toolchain: stable
profile: minimal
override: true
- run: cargo doc --no-deps
- run: cargo doc --workspace --no-deps
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Unreleased
- Adjusted various symbolization code paths to stop heap-allocating
- Handled potential numeric overflow in Gsym inlined function parser more
gracefully
- Moved C API into `cblazesym` crate


0.2.0-alpha.8
Expand Down
12 changes: 1 addition & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
".",
"capi",
"cli",
]

Expand Down Expand Up @@ -33,10 +34,6 @@ autobenches = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
name = "blazesym"
crate-type = ["lib", "cdylib", "staticlib"]

[features]
default = ["backtrace", "demangle", "dwarf"]
# Enable this feature to compile in support for capturing backtraces in errors.
Expand All @@ -47,11 +44,6 @@ backtrace = []
dwarf = ["gimli"]
# Enable this feature to get transparent symbol demangling.
demangle = ["cpp_demangle", "rustc-demangle"]
# Enable this feature to re-generate the library's C header file. An
# up-to-date version of this header should already be available in the
# include/ directory, so this feature is only necessary when APIs are
# changed.
generate-c-header = ["cbindgen", "which"]
# Enable this feature to opt in to the generation of unit test files.
# Having these test files created is necessary for running tests.
generate-unit-test-files = ["xz2", "zip"]
Expand Down Expand Up @@ -96,9 +88,7 @@ test-log = {version = "0.2", default-features = false, features = ["trace"]}
tracing-subscriber = {version = "0.3", default-features = false, features = ["env-filter", "fmt"]}

[build-dependencies]
cbindgen = {version = "0.26", optional = true}
libc = "0.2.137"
reqwest = {version = "0.11.18", optional = true, features = ["blocking"]}
xz2 = {version = "0.1.7", optional = true}
which = {version = "5.0.0", optional = true}
zip = {version = "0.6.4", optional = true, default-features = false}
24 changes: 3 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,27 +91,9 @@ comprehensive explanation of individual types and functions.


### C
For C interoperability, the aforementioned build will produce `libblazesym.a` as
well as `libblazesym.so` in the respective target folder (e.g.,
`<project-root>/target/debug/`).

In your C programs include [`blazesym.h`](include/blazesym.h) (provided as part
of the library) from your source code and then link against the static or shared
library, respectively. When linking statically, you may also need to link:
```text
-lrt -ldl -lpthread -lm
```

An example of usage of the C API is in available in **libbpf-bootstrap**:
<https://github.com/libbpf/libbpf-bootstrap/blob/master/examples/c/profile.c>

This example periodically samples the running process of every processor
in a system and prints their stack traces.

A detailed [documentation of the C API](https://docs.rs/blazesym/latest/blazesym/c_api)
is available as part of the Rust documentation or can be generated locally from
the current repository snapshot using `cargo doc` (grouped under the `c_api`
module).
The companion crate [`cblazesym`](capi/) provides the means for
interfacing with the library from C. Please refer to its
[`README`](capi/README.md) for usage details.


### Command-line
Expand Down
1 change: 1 addition & 0 deletions capi/.cargo
33 changes: 33 additions & 0 deletions capi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "cblazesym"
version = "0.0.0"
edition = "2021"
rust-version = "1.64"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
name = "cblazesym"
# `lib` is necessary for end-to-end tests.
crate-type = ["lib", "cdylib", "staticlib"]

[features]
# Enable this feature to re-generate the library's C header file. An
# up-to-date version of this header should already be available in the
# include/ directory, so this feature is only necessary when APIs are
# changed.
generate-c-header = ["cbindgen", "which"]

[build-dependencies]
cbindgen = {version = "0.26", optional = true}
which = {version = "5.0.0", optional = true}

[dependencies]
# Pinned, because we use #[doc(hidden)] APIs.
blazesym = {version = "=0.2.0-alpha.8", path = "../"}
libc = "0.2.137"

[dev-dependencies]
env_logger = "0.10"
test-log = {version = "0.2.13", default-features = false, features = ["trace"]}
tracing-subscriber = {version = "0.3", default-features = false, features = ["env-filter", "fmt"]}
35 changes: 35 additions & 0 deletions capi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[![pipeline](https://github.com/libbpf/blazesym/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/libbpf/blazesym/actions/workflows/test.yml)
[![rustc](https://img.shields.io/badge/rustc-1.65+-blue.svg)](https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html)

cblazesym
=========

**cblazesym** provides C language bindings for the
[**blazesym**][blazesym] library.

## Build & Use
**cblazesym** requires a standard Rust toolchain and can be built using
the Cargo project manager (e.g., `cargo build`).

The build will produce `libcblazesym.a` as well as `libcblazesym.so` in
the respective target folder (e.g., `<project-root>/target/debug/`).

In your C programs include [`blazesym.h`](include/blazesym.h) (provided as part
of the crate) from your source code and then link against the static or
shared library, respectively. When linking statically, you may also need
to link:
```text
-lrt -ldl -lpthread -lm
```

An example of usage of the C API is in available in **libbpf-bootstrap**:
<https://github.com/libbpf/libbpf-bootstrap/blob/master/examples/c/profile.c>

This example periodically samples the running process of every processor
in a system and prints their stack traces.

A detailed [documentation of the C API](https://docs.rs/cblazesym/latest/)
is available as part of the Rust documentation or can be generated locally from
the current repository snapshot using `cargo doc`.

[blazesym]: https://crates.io/crates/blazesym
170 changes: 170 additions & 0 deletions capi/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#![allow(clippy::let_unit_value)]

use std::env;
use std::ffi::OsStr;
use std::io::Error;
use std::io::ErrorKind;
use std::io::Result;
use std::ops::Deref as _;
use std::path::Path;
use std::process::Command;
use std::process::Stdio;


/// Format a command with the given list of arguments as a string.
fn format_command<C, A, S>(command: C, args: A) -> String
where
C: AsRef<OsStr>,
A: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
args.into_iter().fold(
command.as_ref().to_string_lossy().into_owned(),
|mut cmd, arg| {
cmd += " ";
cmd += arg.as_ref().to_string_lossy().deref();
cmd
},
)
}

/// Run a command with the provided arguments.
fn run<C, A, S>(command: C, args: A) -> Result<()>
where
C: AsRef<OsStr>,
A: IntoIterator<Item = S> + Clone,
S: AsRef<OsStr>,
{
let instance = Command::new(command.as_ref())
.stdin(Stdio::null())
.stdout(Stdio::null())
.env_clear()
.envs(env::vars().filter(|(k, _)| k == "PATH"))
.args(args.clone())
.output()
.map_err(|err| {
Error::new(
ErrorKind::Other,
format!(
"failed to run `{}`: {err}",
format_command(command.as_ref(), args.clone())
),
)
})?;

if !instance.status.success() {
let code = if let Some(code) = instance.status.code() {
format!(" ({code})")
} else {
" (terminated by signal)".to_string()
};

let stderr = String::from_utf8_lossy(&instance.stderr);
let stderr = stderr.trim_end();
let stderr = if !stderr.is_empty() {
format!(": {stderr}")
} else {
String::new()
};

Err(Error::new(
ErrorKind::Other,
format!(
"`{}` reported non-zero exit-status{code}{stderr}",
format_command(command, args)
),
))
} else {
Ok(())
}
}

/// Compile `src` into `dst` using the provided compiler.
fn compile(compiler: &str, src: &Path, dst: &str, options: &[&str]) {
let dst = src.with_file_name(dst);
println!("cargo:rerun-if-changed={}", src.display());
println!("cargo:rerun-if-changed={}", dst.display());

let () = run(
compiler,
options
.iter()
.map(OsStr::new)
.chain([src.as_os_str(), "-o".as_ref(), dst.as_os_str()]),
)
.unwrap_or_else(|err| panic!("failed to run `{compiler}`: {err}"));
}

/// Compile `src` into `dst` using `cc`.
#[cfg_attr(not(feature = "generate-c-header"), allow(dead_code))]
fn cc(src: &Path, dst: &str, options: &[&str]) {
compile("cc", src, dst, options)
}

fn main() {
#[cfg(feature = "generate-c-header")]
{
use std::fs::copy;
use std::fs::write;

let crate_dir = env!("CARGO_MANIFEST_DIR");

cbindgen::Builder::new()
.with_crate(crate_dir)
.with_config(cbindgen::Config::from_root_or_default(crate_dir))
.generate()
.expect("Unable to generate bindings")
.write_to_file(Path::new(crate_dir).join("include").join("blazesym.h"));

// Generate a C program that just included blazesym.h as a basic
// smoke test that cbindgen didn't screw up completely.
let out_dir = env::var_os("OUT_DIR").unwrap();
let out_dir = Path::new(&out_dir);
let blaze_src_c = out_dir.join("blazesym.c");
let () = write(
&blaze_src_c,
r#"
#include <blazesym.h>
int main() {
return 0;
}
"#,
)
.unwrap();

let blaze_src_cxx = out_dir.join("blazesym.cpp");
let _bytes = copy(&blaze_src_c, &blaze_src_cxx).expect("failed to copy file");

cc(
&blaze_src_c,
"blazesym.bin",
&[
"-Wall",
"-Wextra",
"-Werror",
"-I",
Path::new(crate_dir).join("include").to_str().unwrap(),
],
);

// Best-effort check that C++ can compile the thing as well. Hopefully
// all flags are supported...
for cxx in ["clang++", "g++"] {
if which::which(cxx).is_ok() {
compile(
cxx,
&blaze_src_cxx,
&format!("blazesym_cxx_{cxx}.bin"),
&[
"-Wall",
"-Wextra",
"-Werror",
"-I",
Path::new(crate_dir).join("include").to_str().unwrap(),
],
);
}
}
}
}
8 changes: 4 additions & 4 deletions cbindgen.toml → capi/cbindgen.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml
# See https://github.com/mozilla/cbindgen/blob/master/docs.md#cbindgentoml

language = "C"
cpp_compat = true
Expand All @@ -11,9 +11,9 @@ exclude = ["Addr"]

[export.rename]
"Addr" = "uintptr_t"
"Inspector" = "blaze_inspector"
"Normalizer" = "blaze_normalizer"
"Symbolizer" = "blaze_symbolizer"
"Inspector" = "struct blaze_inspector"
"Normalizer" = "struct blaze_normalizer"
"Symbolizer" = "struct blaze_symbolizer"

[fn]
args = "Vertical"
Expand Down
Loading

0 comments on commit f001add

Please sign in to comment.