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

Add demes ffi module to provide a C api #339

Merged
merged 1 commit into from
Jan 18, 2024
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
26 changes: 23 additions & 3 deletions .github/workflows/test_ffi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,32 @@ jobs:
with:
crate: cbindgen
version: "=0.24.3"
- name: Run cmake
- name: Run cmake on demes-forward example
run: |
cmake -Sdemes-forward-capi/c_example -Bbuild
- name: Build
- name: Build demes-forward example
run: |
cmake --build build
- name: Run C example
- name: Run demes-forward C example
run: |
./build/example demes-forward-capi/example_yaml/*.yaml
- name: cleanup
run: |
rm -rf build
- name: Run cmake on demes example
run: |
cmake -Sdemes/c_example -Bbuild
- name: Build demes example
run: |
cmake --build build
- name: Run demes C example
run: |
./build/example demes/demes-spec/examples/browning_america.yaml
- run: sudo apt-get update -y
if: matrix.os == 'ubuntu-latest'
- run: sudo apt-get install -y valgrind
if: matrix.os == 'ubuntu-latest'
- name: Run demes C example through valgrind
if: matrix.os == 'ubuntu-latest'
run: |
valgrind ./build/example demes/demes-spec/examples/browning_america.yaml
40 changes: 40 additions & 0 deletions .github/workflows/valgrind.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
on:
push:
branches: [main, dev]
pull_request:

name: valgrind

jobs:
cargo-valgrind:
name: Run valgrind
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
rust:
- stable
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.12.0
with:
access_token: ${{ secrets.GITHUB_TOKEN }}

- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
submodules: recursive
- uses: dtolnay/rust-toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- uses: Swatinem/rust-cache@v2
- run: sudo apt-get update -y
if: matrix.os == 'ubuntu-latest'
- run: sudo apt-get install -y valgrind
if: matrix.os == 'ubuntu-latest'
- run: cargo install cargo-valgrind
- name: run cargo valgrind on demes::ffi
run: |
cargo valgrind test --manifest-path demes/Cargo.toml --all-features ffi

3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "demes-forward-capi/corrosion"]
path = demes-forward-capi/c_example/corrosion
url = https://github.com/corrosion-rs/corrosion.git
[submodule "demes/c_example/corrosion"]
path = demes/c_example/corrosion
url = https://github.com/corrosion-rs/corrosion.git
4 changes: 4 additions & 0 deletions demes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ homepage = "https://github.com/molpopgen/demes-rs"
repository = "https://github.com/molpopgen/demes-rs"
rust-version = "1.60.0"

[lib]
crate-type = ["lib", "staticlib"]

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "doc_cfg"]

[features]
json = ["serde_json"]
ffi = []

[dependencies]
thiserror = "~1"
Expand Down
26 changes: 26 additions & 0 deletions demes/c_example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Usage from the workspace root:
# cmake -S demes/c_example -B build
# cmake --build build
# cmake --build build --target clean
cmake_minimum_required(VERSION 3.15)
project(c_example LANGUAGES C)

MESSAGE(${CMAKE_SOURCE_DIR} ${PROJECT_SOURCE_DIR})

add_compile_options(-W -Wall -Werror -Wconversion)
add_subdirectory(corrosion)

# Specify to only build the demes crate and to use the ffi cargo feature of the crate
corrosion_import_crate(MANIFEST_PATH ../Cargo.toml CRATES demes FEATURES ffi)
# The header, demes.h, will be built in the root of the build dir
get_filename_component(DEMES_HEADER_LOCATION ${CMAKE_BINARY_DIR} DIRECTORY CACHE)
add_custom_target(header DEPENDS ${DEMES_HEADER_LOCATION}/demes.h)
add_executable(example example.c)
add_dependencies(example cargo-build_demes header)
target_include_directories(example BEFORE PUBLIC ${DEMES_HEADER_LOCATION})
target_link_directories(example PUBLIC ${CMAKE_BINARY_DIR})
# We link the static C archive of demes and the C math lib to the binary
target_link_libraries(example PUBLIC libdemes.a m)

# Use cbindgen to build our header
add_custom_command(OUTPUT ${DEMES_HEADER_LOCATION}/demes.h COMMAND cbindgen -l C -o ${DEMES_HEADER_LOCATION}/demes.h ${CMAKE_SOURCE_DIR}/..)
1 change: 1 addition & 0 deletions demes/c_example/corrosion
Submodule corrosion added at 2d71b9
129 changes: 129 additions & 0 deletions demes/c_example/example.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#define DEMES_FFI = 1

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <demes.h>

void
handle_error(int rv, FFIError *error, Graph *graph)
{
char *error_msg = NULL;
if (rv != 0)
{
assert(demes_error_has_error(error));
assert((error_msg = demes_error_message(error)) != NULL);
fprintf(stderr, "%s\n", error_msg);
demes_c_char_deallocate(error_msg);
demes_error_deallocate(error);
if (graph != NULL)
{
demes_graph_deallocate(graph);
}
exit(1);
}
}

void
iterate_epochs(const Deme *deme)
{
Epoch const *epoch;
size_t num_epochs = demes_deme_num_epochs(deme);
size_t i;
double midpoint, size_at_midpoint, start_time, end_time;
for (i = 0; i < num_epochs; ++i)
{
assert((epoch = demes_deme_epoch(deme, i)) != NULL);
start_time = demes_epoch_start_time(epoch);
end_time = demes_epoch_end_time(epoch);
midpoint = end_time + (start_time - end_time)/2.0;
assert(demes_epoch_size_at(epoch, midpoint, &size_at_midpoint) == 0);
fprintf(stdout, "\t\tstart time: %lf\n", start_time);
fprintf(stdout, "\t\tend time: %lf\n", end_time);
fprintf(stdout, "\t\tstart size: %lf\n", demes_epoch_start_size(epoch));
fprintf(stdout, "\t\tmidpoint size: %lf\n", size_at_midpoint);
fprintf(stdout, "\t\tend size: %lf\n", demes_epoch_end_size(epoch));
}
}

void
iterate_ancestors_proportions(const Graph *graph, const Deme *deme)
{
size_t const *ancestor_indexes;
Deme const *ancestor;
double const *ancestor_proportions;
double proportion;
char *ancestor_name;
size_t i, num_ancestors;

num_ancestors = demes_deme_num_ancestors(deme);
ancestor_indexes = demes_deme_ancestor_indexes(deme);
ancestor_proportions = demes_deme_proportions(deme);

for (i = 0; i < num_ancestors; ++i)
{
assert((ancestor = demes_graph_deme(graph, ancestor_indexes[i])) != NULL);
ancestor_name = demes_deme_name(deme);
proportion = ancestor_proportions[i];
fprintf(stdout, "\t \t%s %lf\n", ancestor_name, proportion);
demes_c_char_deallocate(ancestor_name);
}
}

void
iterate_demes(Graph *graph, FFIError *error)
{
size_t i, num_epochs;
Deme const *deme;
size_t num_demes;
char *deme_name = NULL;

assert(!demes_error_has_error(error));

num_demes = demes_graph_num_demes(graph);
for (i = 0; i < num_demes; ++i)
{
assert((deme = demes_graph_deme(graph, i)) != NULL);
num_epochs = demes_deme_num_epochs(deme);
assert((deme_name = demes_deme_name(deme)) != NULL);
fprintf(stdout, "deme %ld:\n", i);
fprintf(stdout, "\tname: %s\n", deme_name);
fprintf(stdout, "\tno. epochs: %ld\n", num_epochs);
fprintf(stdout, "\tstart time: %lf\n", demes_deme_start_time(deme));
fprintf(stdout, "\tend time: %lf\n", demes_deme_end_time(deme));
fprintf(stdout, "\tstart size: %lf\n", demes_deme_start_size(deme));
fprintf(stdout, "\tend size: %lf\n", demes_deme_end_size(deme));
demes_c_char_deallocate(deme_name);
fprintf(stdout, "\tancestor details:\n");
iterate_ancestors_proportions(graph, deme);
fprintf(stdout, "\tepoch details:\n");
iterate_epochs(deme);
}
}

int
main(int argc, char **argv)
{
FFIError *error = NULL;
Graph *graph = NULL;
int rv;

if (argc != 2)
{
fprintf(stderr, "usage: example filename\n");
exit(1);
}

error = demes_error_allocate();

rv = demes_graph_load_from_file(argv[1], error, &graph);
handle_error(rv, error, graph);

iterate_demes(graph, error);

demes_error_deallocate(error);
if (graph != NULL)
{
demes_graph_deallocate(graph);
}
}
3 changes: 3 additions & 0 deletions demes/cbindgen.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[defines]
"feature = ffi" = "DEMES_FFI"
"feature = json" = "DEMES_JSON"
1 change: 1 addition & 0 deletions demes/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use thiserror::Error;
/// ";
/// assert!(matches!(demes::loads(yaml), Err(demes::DemesError::EpochError(_))));
/// ```
// cbindgen:no-export
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum DemesError {
Expand Down
Loading
Loading