From b37d3e90dd59afd5413defc5d7f391c2d8e2686e Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Fri, 16 Oct 2020 16:18:57 +0200 Subject: [PATCH] Add minimal example project (including CI) - take 2 (#122) * Add minimal example project (including CI) --- .github/workflows/example-project.yml | 84 ++++++++++++++++++++++++ .gitignore | 2 + example-project/Cargo.toml | 11 ++++ example-project/README.md | 19 ++++++ example-project/cbindgen.toml | 7 ++ example-project/src/capi.rs | 31 +++++++++ example-project/src/lib.rs | 68 +++++++++++++++++++ example-project/usage-from-c/Makefile | 7 ++ example-project/usage-from-c/run_tests.c | 24 +++++++ 9 files changed, 253 insertions(+) create mode 100644 .github/workflows/example-project.yml create mode 100644 example-project/Cargo.toml create mode 100644 example-project/README.md create mode 100644 example-project/cbindgen.toml create mode 100644 example-project/src/capi.rs create mode 100644 example-project/src/lib.rs create mode 100644 example-project/usage-from-c/Makefile create mode 100644 example-project/usage-from-c/run_tests.c diff --git a/.github/workflows/example-project.yml b/.github/workflows/example-project.yml new file mode 100644 index 00000000..543bb03a --- /dev/null +++ b/.github/workflows/example-project.yml @@ -0,0 +1,84 @@ +name: Build example project + +on: [push, pull_request] + +env: + CARGO_TERM_COLOR: always + +jobs: + + example-project: + + strategy: + matrix: + include: + - os: ubuntu-latest + - os: macos-latest + - os: windows-latest + toolchain-suffix: -gnu + - os: windows-latest + toolchain-suffix: -msvc + + runs-on: ${{ matrix.os }} + + steps: + - name: Clone Git repository + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable${{ matrix.toolchain-suffix }} + override: true + + - name: Install cargo-c applet + run: | + cargo install --path . + + - name: Set MSVC binaries path + if: matrix.toolchain-suffix == '-msvc' + run: | + $BinGlob = "VC\Tools\MSVC\*\bin\Hostx64\x64" + $BinPath = vswhere -latest -products * -find "$BinGlob" | Select-Object -Last 1 + echo "$BinPath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: Test example project + working-directory: example-project + run: | + cargo test --verbose + + - name: Build C API for example project + working-directory: example-project + run: | + cargo cbuild --verbose --release + + - name: Install into temporary location + if: startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') + working-directory: example-project + run: | + cargo cinstall --verbose --release --destdir=temp + + - name: Copy installed files to /usr/local + if: startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') + working-directory: example-project + run: | + sudo cp -r temp/usr/local/* /usr/local/ + + - name: Test pkg-config + if: startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') + run: | + set -x + test "$(pkg-config --cflags example_project)" = "-I/usr/local/include/example_project" + test "$(pkg-config --libs example_project)" = "-L/usr/local/lib -lexample_project" + + - name: Update dynamic linker cache + if: startsWith(matrix.os, 'ubuntu') + run: | + sudo ldconfig + + - name: Test usage from C (using Makefile) + if: startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') + working-directory: example-project/usage-from-c + run: | + make diff --git a/.gitignore b/.gitignore index 69369904..b52f82ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /target **/*.rs.bk Cargo.lock +/example-project/target/ +/example-project/usage-from-c/run_tests diff --git a/example-project/Cargo.toml b/example-project/Cargo.toml new file mode 100644 index 00000000..227c4faa --- /dev/null +++ b/example-project/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "example-project" +version = "0.1.0" +edition = "2018" + +[features] +default = [] +capi = ["libc"] + +[dependencies] +libc = { version = "0.2", optional = true } diff --git a/example-project/README.md b/example-project/README.md new file mode 100644 index 00000000..33fc1ae5 --- /dev/null +++ b/example-project/README.md @@ -0,0 +1,19 @@ +Example project using `cargo-c` +=============================== + +For detailed usage instructions, have a look at the +[Github workflow configuration](../.github/workflows/example-project.yml). + +Note that `cargo install --path .` is used to install `cargo-c` +from the locally cloned Git repository. +If you want to install the latest release from +[crates.io](https://crates.io/crates/cargo-c), +you should use this instead: + + cargo install cargo-c + +Running `cargo cbuild --release` will create the C header file +`example_project.h` in the `target/release` directory. +This file will contain the comments from the file [`capi.rs`](src/capi.rs). + +Run `cargo doc --open` to view the documentation of the Rust code. diff --git a/example-project/cbindgen.toml b/example-project/cbindgen.toml new file mode 100644 index 00000000..a2afd067 --- /dev/null +++ b/example-project/cbindgen.toml @@ -0,0 +1,7 @@ +include_guard = "EXAMPLE_PROJECT_H" +include_version = true +language = "C" +cpp_compat = true + +[export.rename] +"OddCounter" = "ExampleProjectOddCounter" diff --git a/example-project/src/capi.rs b/example-project/src/capi.rs new file mode 100644 index 00000000..12c90ef1 --- /dev/null +++ b/example-project/src/capi.rs @@ -0,0 +1,31 @@ +use crate::OddCounter; + +// NB: The documentation comments from this file will be available +// in the auto-generated header file example_project.h + +/// Create new counter object given a start value. +/// +/// On error (if an even start value is used), NULL is returned. +/// The returned object must be eventually discarded with example_project_oddcounter_free(). +#[no_mangle] +pub extern "C" fn example_project_oddcounter_new(start: u32) -> Option> { + OddCounter::new(start).ok().map(Box::new) +} + +/// Discard a counter object. +/// +/// Passing NULL is allowed. +#[no_mangle] +pub extern "C" fn example_project_oddcounter_free(_: Option>) {} + +/// Increment a counter object. +#[no_mangle] +pub extern "C" fn example_project_oddcounter_increment(counter: &mut OddCounter) { + counter.increment() +} + +/// Obtain the current value of a counter object. +#[no_mangle] +pub extern "C" fn example_project_oddcounter_get_current(counter: &OddCounter) -> u32 { + counter.current() +} diff --git a/example-project/src/lib.rs b/example-project/src/lib.rs new file mode 100644 index 00000000..eeddf0e8 --- /dev/null +++ b/example-project/src/lib.rs @@ -0,0 +1,68 @@ +/*! +Example library for [cargo-c]. + +[cargo-c]: https://crates.io/crates/cargo-c +*/ + +#![warn(rust_2018_idioms)] +#![deny(missing_docs)] + +#[cfg(cargo_c)] +mod capi; + +/// A counter for odd numbers. +/// +/// Note that this `struct` does *not* use `#[repr(C)]`. +/// It can therefore contain arbitrary Rust types. +/// In the C API, it will be available as an *opaque pointer*. +#[derive(Debug)] +pub struct OddCounter { + number: u32, +} + +impl OddCounter { + /// Create a new counter, given an odd number to start. + pub fn new(start: u32) -> Result { + if start % 2 == 0 { + Err(OddCounterError::Even) + } else { + Ok(OddCounter { number: start }) + } + } + + /// Increment by 2. + pub fn increment(&mut self) { + self.number += 2; + } + + /// Obtain the current (odd) number. + pub fn current(&self) -> u32 { + self.number + } +} + +/// Error type for [OddCounter::new]. +/// +/// In a "real" library, there would probably be more error variants. +#[derive(Debug)] +pub enum OddCounterError { + /// An even number was specified as `start` value. + Even, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn create42() { + assert!(OddCounter::new(42).is_err()); + } + + #[test] + fn increment43() { + let mut counter = OddCounter::new(43).unwrap(); + counter.increment(); + assert_eq!(counter.current(), 45); + } +} diff --git a/example-project/usage-from-c/Makefile b/example-project/usage-from-c/Makefile new file mode 100644 index 00000000..cd0f63df --- /dev/null +++ b/example-project/usage-from-c/Makefile @@ -0,0 +1,7 @@ +LDLIBS = -lexample_project + +test: run_tests + ./run_tests + +clean: + $(RM) run_tests diff --git a/example-project/usage-from-c/run_tests.c b/example-project/usage-from-c/run_tests.c new file mode 100644 index 00000000..634faffa --- /dev/null +++ b/example-project/usage-from-c/run_tests.c @@ -0,0 +1,24 @@ +#include +#include + +int main() { + ExampleProjectOddCounter *counter = example_project_oddcounter_new(4); + if (counter) { + printf("Unexpected success\n"); + return 1; + } + counter = example_project_oddcounter_new(5); + if (!counter) { + printf("Error creating ExampleProjectOddCounter\n"); + return 1; + } + example_project_oddcounter_increment(counter); + uint32_t result = example_project_oddcounter_get_current(counter); + example_project_oddcounter_free(counter); + if (result == 7) { + return 0; + } else { + printf("Error: unexpected result: %d\n", result); + return 1; + } +}