Skip to content
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
84 changes: 84 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Quality Checks

on:
push:
branches: [main]
pull_request:
merge_group:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always
# Keep every multi-workspace check in sync.
QUALITY_WORKSPACES: |
.
tests/testlib
tests/rust-consumer

jobs:
cargo-fmt:
name: Code Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: |
for workspace in $QUALITY_WORKSPACES; do
cargo fmt --manifest-path "$workspace/Cargo.toml" --all -- --check
done

taplo-fmt:
name: TOML Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: uncenter/setup-taplo@v1
- run: taplo fmt --check

clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: |
for workspace in $QUALITY_WORKSPACES; do
cargo clippy --manifest-path "$workspace/Cargo.toml" --workspace --all-targets --all-features -- -D warnings
done

doc:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- env:
RUSTDOCFLAGS: -D warnings
run: |
for workspace in $QUALITY_WORKSPACES; do
cargo doc --manifest-path "$workspace/Cargo.toml" --workspace --all-features --no-deps
done

unused-dependencies:
name: Unused Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: taiki-e/install-action@e67fa11c4b9316fa714ddf0abed07a0c3143b95b # v2.87.4
with:
tool: cargo-shear
- run: |
for workspace in $QUALITY_WORKSPACES; do
(cd "$workspace" && cargo shear)
done
114 changes: 114 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: Release

on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
validate-source:
name: Validate release source
runs-on: ubuntu-latest
outputs:
release-notes: ${{ steps.changelog.outputs.notes }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
persist-credentials: false
- name: Require tagged commit to be on main
run: |
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
if ! git merge-base --is-ancestor HEAD refs/remotes/origin/main; then
echo "::error::Release tags must point to a commit on main."
exit 1
fi
- name: Extract release notes
id: changelog
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
python3 - <<'PY'
import os
import re
import uuid
from pathlib import Path

tag = os.environ["RELEASE_TAG"]
changelog = Path("CHANGELOG.md").read_text()
sections = re.split(r"^## +([^\n]+)\n", changelog, flags=re.MULTILINE)
matches = [
sections[index + 1].strip()
for index in range(1, len(sections), 2)
if re.fullmatch(rf"{re.escape(tag)} - [0-9]{{4}}-[0-9]{{2}}-[0-9]{{2}}", sections[index].strip())
]
if len(matches) != 1 or not matches[0]:
raise SystemExit(f"CHANGELOG.md must contain exactly one nonempty '## {tag} - YYYY-MM-DD' section")
delimiter = uuid.uuid4().hex
with open(os.environ["GITHUB_OUTPUT"], "a") as output:
output.write(f"notes<<{delimiter}\n{matches[0]}\n{delimiter}\n")
PY

publish:
name: Publish to crates.io
needs: validate-source
runs-on: ubuntu-latest
environment: crates-io
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- name: Validate release tag
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
python3 - <<'PY'
import os
import re
import tomllib
from pathlib import Path

tag = os.environ["RELEASE_TAG"]
if not re.fullmatch(r"(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)", tag):
raise SystemExit(f"Expected a bare MAJOR.MINOR.PATCH tag, got {tag!r}")
package = tomllib.loads(Path("Cargo.toml").read_text())["package"]
if tag != package["version"]:
raise SystemExit(f"Tag {tag!r} does not match Cargo.toml version {package['version']!r}")
PY
- name: Verify package
run: cargo publish --locked --dry-run
- name: Authenticate with crates.io
id: auth
uses: rust-lang/crates-io-auth-action@v1
- name: Publish crate
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish --locked

github-release:
name: Create GitHub release
needs: [validate-source, publish]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
# GitHub automatically attaches source archives (.zip and .tar.gz).
- name: Create release with changelog notes
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.ref_name }}
RELEASE_NOTES: ${{ needs.validate-source.outputs.release-notes }}
run: |
if ! gh release view "$RELEASE_TAG" > /dev/null 2>&1; then
gh release create "$RELEASE_TAG" --verify-tag --title "$RELEASE_TAG" --notes "$RELEASE_NOTES"
fi
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

## 0.2.0 - 2026-09-09

- Initial release of lib-patcher, a tool for filtering symbols in Rust static libraries to prevent linking conflicts.
9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
[package]
name = "lib-patcher"
version = "0.2.0"
publish = false
edition = "2024"
authors = ["Stephan Eckes <stephan.eckes@ai-coustics.com>"]
description = "Cross-platform symbol filtering for Rust static libraries to prevent linking conflicts"
readme = "README.md"
repository = "https://github.com/ai-coustics/lib-patcher"
license = "MIT OR Apache-2.0"
keywords = ["static-library", "linking", "symbol-filtering", "build-tools", "ffi"]
keywords = [
"static-library",
"linking",
"symbol-filtering",
"build-tools",
"ffi",
]
categories = ["development-tools::build-utils", "development-tools::ffi"]

[dependencies]
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ error: duplicate symbol: serde_json::ser::indent
#### Install

```sh
cargo install --git ssh://git@github.com/ai-coustics/lib-patcher.git lib-patcher
cargo install lib-patcher
```

#### Basic Usage
Expand Down Expand Up @@ -76,7 +76,20 @@ lib-patcher --input libmylib.a --list

### As a Rust Library (build.rs)

Use it programmatically in your `build.rs`:
Add `lib-patcher` as a build dependency to your project:

```sh
cargo add --build lib-patcher
```

Use it in your `build.rs` to patch an **already compiled external static library**
before linking it into your crate. In the example below, `vendor/libthirdparty.a`
must already exist.

This does **not** patch the library produced by the current crate: Cargo runs
`build.rs` before compiling that crate, so its output library is not available yet.
To patch your own crate's static library, first build it, then use the
[CLI tool](#as-a-cli-tool-post-build) as a separate post-build step.

```rust
use lib_patcher::patch_lib;
Expand Down
11 changes: 9 additions & 2 deletions src/android.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::env;
use std::path::{Path, PathBuf};

use crate::elf::{patch_elf, ElfToolchain};
use crate::elf::{ElfToolchain, patch_elf};

/// Android implementation: ELF symbol filtering using the Android NDK LLVM tools.
///
Expand Down Expand Up @@ -33,7 +33,14 @@ pub(crate) fn patch_android(
label: "Android",
};

patch_elf(static_lib, out_dir, lib_name, keep_prefix, final_lib, &tools);
patch_elf(
static_lib,
out_dir,
lib_name,
keep_prefix,
final_lib,
&tools,
);
}

struct AndroidToolchain {
Expand Down
11 changes: 9 additions & 2 deletions src/linux.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::path::{Path, PathBuf};
use std::process::Command;

use crate::elf::{patch_elf, ElfToolchain};
use crate::elf::{ElfToolchain, patch_elf};

/// Linux implementation: uses readelf + objcopy (the shared ELF routine) to
/// localize symbols, resolving native or GNU cross tools for the target arch.
Expand All @@ -27,7 +27,14 @@ pub(crate) fn patch_linux(
label: "Linux",
};

patch_elf(static_lib, out_dir, lib_name, keep_prefix, final_lib, &tools);
patch_elf(
static_lib,
out_dir,
lib_name,
keep_prefix,
final_lib,
&tools,
);
}

/// Maps a target architecture to its GNU cross-toolchain triplet prefix
Expand Down
Loading