cargo applet to build and install C-ABI compatible dynamic and static libraries.
It produces and installs a correct pkg-config file, a static library and a dynamic library, and a C header to be used by any C (and C-compatible) software.
cargo-c may be installed from crates.io.
cargo install cargo-c
The rustc
version supported is the same as the one supported by the cargo
version embedded in the package version, or as set in the
rust-version field.
You must have the cargo build requirements satisfied in order to build cargo-c:
git
pkg-config
(on Unix, used to figure out the host-provided headers/libraries)curl
(on Unix)- OpenSSL headers (only for Unix, this is the
libssl-dev
package on deb-based distributions)
You may pass --features=vendored-openssl
if you have problems building openssl-sys using the host-provided OpenSSL.
cargo install cargo-c --features=vendored-openssl
# build the library, create the .h header, create the .pc file
$ cargo cbuild --destdir=${D} --prefix=/usr --libdir=/usr/lib64
# build the library, create the .h header, create the .pc file, build and run the tests
$ cargo ctest
# build the library, create the .h header, create the .pc file and install all of it
$ cargo cinstall --destdir=${D} --prefix=/usr --libdir=/usr/lib64
For a more in-depth explanation of how cargo-c
works and how to use it for
your crates, read Building Crates so they Look Like C ABI Libraries.
This is the ideal setup for a project that wants to keep their C-API within the main crate:
- Create a
capi.rs
with the C-API you want to expose and use#[cfg(cargo_c)]
#[cfg(feature="capi")]
to hide it when you build a normal rust library. - Make sure you have a lib target and if you are using a workspace the first member is the crate you want to export, that means that you might have to add a "." member at the start of the list.
Since Rust 1.38, also add "staticlib" to the "lib"Do not specify thecrate-type
.crate-type
, cargo-c will add the correct library target by itself.- You may use the feature
capi
to add C-API-specific optional dependencies.NOTE: It must be always present in
Cargo.toml
- Remember to add a
cbindgen.toml
and fill it with at least the include guard and probably you want to set the language to C (it defaults to C++) - Once you are happy with the result update your documentation to tell the user
to install
cargo-c
and docargo cinstall --prefix=/usr --destdir=/tmp/some-place
or something along those lines.
If you plan to keep the bindings as a separate crate and do not need to autogenerate the headers you may just populate Cargo.toml:
- Add a
capi
feature, since it is used by cargo-c to identify packages that has to be built as C-libraries within a workspace. - Set the entry in
package.metadata.capi.header.generate
tofalse
. - Optionally override the path to the header to a custom one instead of the default one.
You may override various aspects of cargo-c
via settings in Cargo.toml
under the package.metadata.capi
key
[package.metadata.capi]
# Configures the minimum required cargo-c version. Trying to run with an
# older version causes an error.
min_version = "0.6.10"
[package.metadata.capi.header]
# Used as header file name. By default this is equal to the crate name.
# The name can be with or without the header filename extension `.h`
name = "new_name"
# Install the header into a subdirectory with the name of the crate. This
# is enabled by default, pass `false` or "" to disable it.
subdirectory = "libfoo-2.0/foo"
# Generate the header file with `cbindgen`, or copy a pre-generated header
# from the `assets` subdirectory. By default a header is generated.
generation = true
# Can be use to disable header generation completely.
# This can be used when generating dynamic modules instead of an actual library.
enabled = true
[package.metadata.capi.pkg_config]
# Used as the package name in the pkg-config file and defaults to the crate name.
name = "libfoo"
# Used as the pkg-config file name and defaults to the crate name.
filename = "libfoo-2.0"
# Used as the package description in the pkg-config file and defaults to the crate description.
description = "some description"
# Used as the package version in the pkg-config file and defaults to the crate version.
version = "1.2.3"
# Used as the Requires field in the pkg-config file, if defined
requires = "gstreamer-1.0, gstreamer-base-1.0"
# Used as the Requires.private field in the pkg-config file, if defined
requires_private = "gobject-2.0, glib-2.0 >= 2.56.0, gmodule-2.0"
# Strip the include search path from the last n components, useful to support installing in a
# subdirectory but then include with the path. By default it is 0.
strip_include_path_components = 1
[package.metadata.capi.library]
# Used as the library name and defaults to the crate name. This might get
# prefixed with `lib` depending on the target platform.
name = "new_name"
# Used as library version and defaults to the crate version. How this is used
# depends on the target platform.
version = "1.2.3"
# Used to install the library to a subdirectory of `libdir`.
install_subdir = "gstreamer-1.0"
# Used to disable versioning links when installing the dynamic library
versioning = false
# Instead of using semver, select a fixed number of version components for your SONAME version suffix:
# Setting this to 1 with a version of 0.0.0 allows a suffix of `.so.0`
# Setting this to 3 always includes the full version in the SONAME (indicate any update is ABI breaking)
#version_suffix_components = 2
# Add `-Cpanic=abort` to the RUSTFLAGS automatically, it may be useful in case
# something might panic in the crates used by the library.
rustflags = "-Cpanic=abort"
# Used to disable the generation of additional import library file in platforms
# that have the concept such as Windows
import_library = false
[package.metadata.capi.install.data]
# Used to install the data to a subdirectory of `datadir`. By default it is the same as `name`
subdirectory = "foodata"
# Copy the pre-generated data files found in {root_dir}/{from} to {datadir}/{to}/{matched subdirs}
# If {from} is a single path instead of a glob, the destination is {datapath}/{to}.
# datapath is {datadir}/{subdirectory}
asset = [{from="pattern/with/or/without/**/*", to="destination"}]
# Copy the pre-generated data files found in {OUT_DIR}/{from} to {includedir}/{to}/{matched subdirs}
# If {from} is a single path instead of a glob, the destination is {datapath}/{to}.
# datapath is {datadir}/{subdirectory}
generated = [{from="pattern/with/or/without/**/*", to="destination"}]
[package.metadata.capi.install.include]
# Copy the pre-generated includes found in {root_dir}/{from} to {includedir}/{to}/{matched subdirs}
# If {from} is a single path instead of a glob, the destination is {includepath}/{to}.
# includepath is {includedir}/{header.subdirectory}
asset = [{from="pattern/with/or/without/**/*", to="destination"}]
# Copy the pre-generated includes found in {OUT_DIR}/{from} to {includedir}/{to}/{matched subdirs}
# If {from} is a single path instead of a glob, the destination is {includedpath}/{to}.
# includepath is {includedir}/{header.subdirectory}
generated = [{from="pattern/with/or/without/**/*", to="destination"}]
Do not pass RUSTFLAGS
that are managed by cargo through other means, (e.g. the flags driven by [profiles]
or the flags driven by [target.<>]
), cargo-c effectively builds as if the target is always explicitly passed.
- ebur128
- gcode-rs
- gst-plugins-rs
- lewton
- libdovi
- libimagequant
- librsvg
- rav1e
- rustls-ffi
- sled
- pathfinder
- udbserver
- cli
- build command
- install command
- test command
- cargo applet support
- build targets
- pkg-config generation
- header generation (cbindgen integration)
-
staticlib
support -
cdylib
support - Generate version information in the header
- Make it tunable
- Extra Cargo.toml keys
- Better status reporting
When running on a musl-based system (e.g. Alpine Linux), it could be that using the cdylib
library type results in the following error (as reported here):
Error: CliError { error: Some(cannot produce cdylib for as the target x86_64-unknown-linux-musl does not support these crate types), exit_code: 101 }
This suggests that Rust was not built with crt-static=false
and it typically happens if Rust has been installed through rustup.
Shared libraries can be enabled manually in this case, by editing the file .cargo/config
like so:
# .cargo/config
[target.x86_64-unknown-linux-musl]
rustflags = [
"-C", "target-feature=-crt-static",
]
However, it is preferred to install Rust through the system package manager instead of rustup (e.g. with apk add rust
), because the provided package should already handle this (see e.g. here).
In order to accomodate Debian's multiarch approach the cargo-c
default for the libdir
is lib/<triplet>
on such system.
Either pass an explicit --libdir
or pass --target
to return to the common libdir=lib
default.
This software has been partially developed in the scope of the H2020 project SIFIS-Home with GA n. 952652.