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-latesttarget: x86_64-unknown-linux-gnuuse-pgo: trueuse-cross: false
- os: windows-latesttarget: x86_64-pc-windows-msvcuse-pgo: trueuse-cross: false
- os: macos-latesttarget: aarch64-apple-darwinuse-pgo: trueuse-cross: false
- os: ubuntu-latesttarget: aarch64-unknown-linux-gnuuse-pgo: false # no native runneruse-cross: trueruns-on: ${{ matrix.os }}steps:
- name: Checkout Repositoryuses: actions/checkout@v4
- name: Build Binaryuses: Reloaded-Project/devops-rust-lightweight-binary@v1with:
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,bench,nightly | 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. |
packages | No | '' | Multi-line list of package names to test (one per line). If empty, tests all packages in workspace. |
Note: The packages parameter is passed through to the test action and does not affect which binary is built.
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, excludebench_estimate(c);bench_decompress(c);}#[cfg(feature = "pgo")]{// Realistic usage patterns for PGOgenerate_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 guestuse-pgo: trueuse-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 Libraryuses: Reloaded-Project/devops-rust-lightweight-binary@v1with:
crate-name: my-cratetarget: x86_64-unknown-linux-gnuadditional-rustflags: -C opt-level=3additional-rustc-args: --all-features- name: Build C Library with Specific Nightlyuses: Reloaded-Project/devops-rust-lightweight-binary@v1with:
crate-name: my-cratetarget: x86_64-unknown-linux-gnurust-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 Crateuses: Reloaded-Project/devops-rust-lightweight-binary@v1with:
rust-project-path: projects/api-cratecrate-name: api-cratetarget: x86_64-unknown-linux-gnuuse-cache: true # first step: restore cacheinstall-binstall: true # default
- name: Build CLI Crateuses: Reloaded-Project/devops-rust-lightweight-binary@v1with:
rust-project-path: projects/cli-cratecrate-name: cli-cratetarget: x86_64-unknown-linux-gnuuse-cache: false # subsequent steps: disable to avoid duplicate saveinstall-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-lateststeps:
- uses: actions/checkout@v4
- uses: Reloaded-Project/devops-rust-lightweight-binary@v1with:
crate-name: my-cratetarget: x86_64-unknown-linux-gnufeatures: ""build-with-simd:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: Reloaded-Project/devops-rust-lightweight-binary@v1with:
crate-name: my-cratetarget: x86_64-unknown-linux-gnufeatures: "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.
