A GitHub Action for building optimized, lightweight Rust binaries and C libraries.
Uses nightly Rust and nightly-only features to minimize binary size and maximize performance:
- Self-Built std: Smaller binaries, better optimizations.
- Abort on Panic: Smaller binaries. (Can be disabled)
- Profile Guided Optimization (PGO): Optimizes based on runtime usage patterns.
- Cross-Compilation: Via
cross-rs. - Tests and Coverage: Optional, via
devops-rust-test-and-coverageaction.
As a single job/step of a workflow:
build:
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
use-pgo: true
use-cross: false
- os: windows-latest
target: x86_64-pc-windows-msvc
use-pgo: true
use-cross: false
- os: macos-latest
target: aarch64-apple-darwin
use-pgo: true
use-cross: false
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
use-pgo: false # no native runner
use-cross: true
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Build Binary
uses: Reloaded-Project/devops-rust-lightweight-binary@v1
with:
target: ${{ matrix.target }}
crate-name: "my-crate"
use-pgo: ${{ matrix.use-pgo }}
use-cross: ${{ matrix.use-cross }}Tip
Adjust rust-project-path and workspace-path (if using cargo workspaces) if your project is not in the root folder ..
To use this action in your own repository:
- Create a new workflow file (e.g.,
.github/workflows/build-c-library.yml) in your repository. - Copy the example usage job from above into the new workflow file.
- Customize the input parameters as needed for your project.
| Input | Required | Default | Description |
|---|---|---|---|
rust-project-path |
No | . |
Path to the Rust project |
workspace-path |
No | . |
Workspace folder where target directory is. Uses rust-project-path if not set. |
build-library |
No | false |
Build a library instead of a binary. |
target |
Yes | The target platform for the Rust compiler | |
features |
No | '' |
Comma-separated list of features to include in the build |
artifact-prefix |
No | '' |
Prefix for artifact names. Combined with target/features to form final name (e.g., MyApp -> MyApp-linux-x64). If empty, no prefix is used. |
run-tests-and-coverage |
No | false |
Run tests and coverage using the devops-rust-test-and-coverage action. |
use-cache |
No | true |
Enable or disable the build cache using Swatinem/rust-cache. |
| Input | Required | Default | Description |
|---|---|---|---|
no-default-features |
No | false |
Do not include default features in the build |
rust-toolchain |
No | nightly |
The Rust toolchain to use. Can be nightly or a specific nightly version (e.g., nightly-2025-09-18). |
use-cross |
No | false |
Use cross-rs for building. If false, use cargo. |
| Input | Required | Default | Description |
|---|---|---|---|
use-pgo |
No | false |
Use Profile-Guided Optimization [PGO] to build the library. |
pgo-project-path |
No | . |
Path to the Rust project used for gathering PGO data. Can be same or separate project. |
pgo-benchmark-name |
No | 'my_benchmark' |
Benchmark name to use with PGO. |
abort-on-panic |
No | true |
Abort immediately on panic. If false, the default panic handler is used. |
size-optimized-std |
No | false |
Builds std with size optimizations, such as reduced core::fmt footprint. |
| Input | Required | Default | Description |
|---|---|---|---|
upload-artifacts |
No | true |
Upload the built artifacts as a GitHub Actions artifact |
upload-symbols-separately |
No | true |
Upload debug symbol files (.pdb, .dwp, .dSYM) as separate artifacts instead of bundling with main artifacts |
use-friendly-target-names |
No | true |
Transform target triples to user-friendly names (e.g., x86_64-unknown-linux-gnu -> linux-x64) in artifact names. |
artifact-name-exclude-features |
No | c-exports |
Comma-separated list of features to exclude from artifact names. Features are still built but not included in artifact names. Set to empty string to include all. |
| Input | Required | Default | Description |
|---|---|---|---|
additional-rustflags |
No | '' |
Additional RUSTFLAGS to pass to the Rust compiler |
additional-rustc-args |
No | '' |
Additional arguments to pass directly to rustc |
additional-std-features |
No | `` | Specify extra build-std features. |
These parameters are only used when run-tests-and-coverage is enabled and are passed directly to the devops-rust-test-and-coverage action:
| Input | Required | Default | Description |
|---|---|---|---|
upload-coverage-to-codecov |
No | true |
Whether to upload coverage to Codecov |
codecov-token |
No | Codecov token for uploading coverage | |
use-tarpaulin |
No | true |
Whether to use Tarpaulin for code coverage. If false, only runs tests. |
use-binstall |
No | true |
Whether to use cargo-binstall for installing components like tarpaulin. If false, uses cargo install. |
install-binstall |
No | true |
Whether to install cargo-binstall. If false, assumes it is already available in the environment. |
additional-test-args |
No | '' |
Additional arguments passed directly to the cargo test command. |
additional-tarpaulin-args |
No | '' |
Additional arguments passed directly to the cargo tarpaulin command. |
codecov-flags |
No | 'unittests' |
Flags to pass to Codecov for organizing coverage reports. |
codecov-name |
No | 'codecov-umbrella' |
Custom defined name for the coverage upload. |
Note: The following parameters are used by both this action AND passed through to the test action: target, features, no-default-features, use-cross.
Note: Stable and beta toolchains are not supported due to required nightly features. Only nightly and specific nightly versions (e.g.,
nightly-2025-09-18) are supported.
PGO compiles and runs a benchmark (configured via pgo-benchmark-name and pgo-project-path) to collect runtime data, then uses that profile to optimize the final build. Keep the benchmark close to real usage patterns:
#[cfg(not(feature = "pgo"))]
{
// Regular benchmarks, unrealistic for profiling, exclude
bench_estimate(c);
bench_decompress(c);
}
#[cfg(feature = "pgo")]
{
// Realistic usage patterns for PGO
generate_pgo_data();
}PGO requires the target platform to match the host. With use-cross: true, cross-compilation may work:
target: aarch64-unknown-linux-gnu # x64 host to aarch64 simulated guest
use-pgo: true
use-cross: trueIf the process fails, your CI will fail, so experiment to find what works.
Set run-tests-and-coverage: true to run tests and generate coverage after the build.
This invokes devops-rust-test-and-coverage with the same configuration (target, features, use-cross, etc.).
- Tests run via
cargoorcross(based onuse-cross) - Coverage via Tarpaulin when
use-tarpaulin: true(ignored ifuse-cross: true)
Set build-library: true to build a library instead of a binary (equivalent to crate-type = ["cdylib", "staticlib"]).
Artifacts include static (.a, .lib) and dynamic (.so, .dll, .dylib) libraries depending on target.
Find more examples in the tests.
- name: Build C Library
uses: Reloaded-Project/devops-rust-lightweight-binary@v1
with:
crate-name: my-crate
target: x86_64-unknown-linux-gnu
additional-rustflags: -C opt-level=3
additional-rustc-args: --all-features- name: Build C Library with Specific Nightly
uses: Reloaded-Project/devops-rust-lightweight-binary@v1
with:
crate-name: my-crate
target: x86_64-unknown-linux-gnu
rust-toolchain: nightly-2025-09-18Tip
The best approach depends on whether your builds share the same settings.
When building multiple crates within a workspace with the same settings, use a single job. Shared dependencies are compiled once and reused:
- name: Build API Crate
uses: Reloaded-Project/devops-rust-lightweight-binary@v1
with:
rust-project-path: projects/api-crate
crate-name: api-crate
target: x86_64-unknown-linux-gnu
use-cache: true # first step: restore cache
install-binstall: true # default
- name: Build CLI Crate
uses: Reloaded-Project/devops-rust-lightweight-binary@v1
with:
rust-project-path: projects/cli-crate
crate-name: cli-crate
target: x86_64-unknown-linux-gnu
use-cache: false # subsequent steps: disable to avoid duplicate save
install-binstall: false # already installed aboveNote
Only enable use-cache on the first step. The cache is restored at the start and saved after the job completes.
Same applies to install-binstall - only install once per job.
When building crates with different settings, use separate jobs to avoid cache conflicts:
jobs:
build-standard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Reloaded-Project/devops-rust-lightweight-binary@v1
with:
crate-name: my-crate
target: x86_64-unknown-linux-gnu
features: ""
build-with-simd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Reloaded-Project/devops-rust-lightweight-binary@v1
with:
crate-name: my-crate
target: x86_64-unknown-linux-gnu
features: "simd,avx2"Building these in a single job would cause cache conflicts since both use the same target but different features.
After a successful run, the built artifacts will be available as a downloadable artifact in the GitHub Actions run.
Artifact names follow the pattern: {artifact-prefix}-{target}[-features] or {target}[-features] when no prefix is set.
| Scenario | Example Name |
|---|---|
| No prefix, friendly names | linux-x64, windows-arm64 |
With prefix MyApp |
MyApp-linux-x64, MyApp-windows-arm64 |
With features simd,avx2 |
linux-x64-simd,avx2 |
| With prefix and features | MyApp-linux-x64-simd,avx2 |
When use-friendly-target-names is enabled (default), the target triple is replaced with a
user-friendly platform identifier (e.g., x86_64-unknown-linux-gnu becomes linux-x64,
aarch64-apple-darwin becomes macos-arm64). Unmapped targets fall back to the raw target triple.
Use artifact-name-exclude-features to exclude specific features from artifact names while still building them.
This is useful when features are already distinguished by artifact-prefix (e.g., C library builds).
| Features | Exclude List | Resulting Suffix |
|---|---|---|
c-exports |
c-exports |
(none) |
c-exports,simd |
c-exports |
-simd |
foo,bar |
foo,bar |
(none) |
bar,foo |
foo,bar |
(none) |
The default exclusion is c-exports, which is commonly redundant for C library builds in Reloaded projects;
where often the prefix is something like c-library. To include all features in names, set to an empty string "".
When upload-symbols-separately is enabled (default), debug symbols are uploaded as separate artifacts
with a .symbols suffix:
- Main artifact:
MyApp-linux-x64 - Symbol artifact:
MyApp-linux-x64.symbols
To access the artifacts:
- Navigate to the Actions tab in your repository.
- Click on the workflow run that built the artifacts.
- In the "Artifacts" section, you will find the generated artifacts, which you can download.
| Input | Required | Default | Description |
|---|---|---|---|
crate-name |
No | '' |
Name of the Rust crate/package. Used for cache key and artifact directory. |
The crate-name parameter is still used for:
- Cache key: When provided, it helps create unique cache keys for your builds
- Artifact directory: Used in the
ARTIFACT_OUT_DIRpath construction
However, using crate-name for artifact naming is deprecated. Please use artifact-prefix instead.
For backwards compatibility, if crate-name is set and artifact-prefix is not provided, the legacy
naming behaviour is used:
- Binary artifacts:
{crate-name}-{target}[-features] - Library artifacts:
C-Library-{crate-name}-{target}[-features]
A deprecation warning will appear in the workflow logs when this legacy behaviour is triggered.
Migration: Replace crate-name: my-crate with artifact-prefix: my-crate for binaries, or
artifact-prefix: C-Library-my-crate for libraries to maintain the same artifact names.
Building C libraries from Rust projects can be a complex process, especially when considering different target platforms, compiler flags, and optimizations like PGO.
This action simplifies the process by providing a configurable and reusable workflow that handles the building of C libraries from Rust projects.
Contributions are welcome! If you encounter any issues or have suggestions for improvements, please open an issue or submit a pull request in this repository.
This project is licensed under the MIT License. See the LICENSE file for details.