Skip to content

Commit 943a885

Browse files
committed
chore: improve CI
* improve CI workflow: * add automatic dependabot updates (crates and actions) * move all project-specific CI logic into the `justfile` - this allows developers to re-run the same steps as CI locally, without the need to replicate whatever is being done in the CI * add test, test-msrv, coverage, and the release jobs * the release job will not actually release until crate version changes. See [docs](https://release-plz.dev/docs). It runs on all merges to main branch, and auto-generates a "release PR" - suggesting version bump and changelog file changes. * Added pre-commit CI -- once enabled, this will keep all PRs clean by automatically doing `cargo fmt` and other minor linting, without requiring users to re-submit their changes. * deleted and git-ignored the Lock file - libs should not have this file checked in, as it prevents maintainers from seeeing build bugs that the end users will encounter.
1 parent 2c18f73 commit 943a885

File tree

7 files changed

+338
-49
lines changed

7 files changed

+338
-49
lines changed

.github/dependabot.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
version: 2
2+
updates:
3+
4+
# Maintain dependencies for GitHub Actions
5+
- package-ecosystem: github-actions
6+
directory: "/"
7+
schedule:
8+
interval: weekly
9+
groups:
10+
all-actions-version-updates:
11+
applies-to: version-updates
12+
patterns:
13+
- "*"
14+
all-actions-security-updates:
15+
applies-to: security-updates
16+
patterns:
17+
- "*"
18+
19+
# Update Rust dependencies
20+
- package-ecosystem: cargo
21+
directory: "/"
22+
schedule:
23+
interval: daily
24+
time: "02:00"
25+
open-pull-requests-limit: 10
26+
groups:
27+
all-cargo-version-updates:
28+
applies-to: version-updates
29+
patterns:
30+
- "*"
31+
all-cargo-security-updates:
32+
applies-to: security-updates
33+
patterns:
34+
- "*"

.github/workflows/ci.yml

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
release:
9+
types: [ published ]
10+
workflow_dispatch:
11+
12+
defaults:
13+
run:
14+
shell: bash
15+
16+
env:
17+
CARGO_TERM_COLOR: always
18+
19+
jobs:
20+
test:
21+
name: Test
22+
runs-on: ${{ matrix.os }}
23+
strategy:
24+
matrix:
25+
os: [ ubuntu-latest, macos-15 ]
26+
steps:
27+
- uses: actions/checkout@v4
28+
- if: github.event_name != 'release' && github.event_name != 'workflow_dispatch'
29+
uses: Swatinem/rust-cache@v2
30+
- uses: dtolnay/rust-toolchain@stable
31+
- uses: taiki-e/install-action@v2
32+
with: { tool: just }
33+
- run: just ci-test
34+
35+
test-msrv:
36+
name: Test MSRV
37+
runs-on: ${{ matrix.os }}
38+
strategy:
39+
matrix:
40+
os: [ ubuntu-latest, macos-15 ]
41+
steps:
42+
- uses: actions/checkout@v4
43+
- if: github.event_name != 'release' && github.event_name != 'workflow_dispatch'
44+
uses: Swatinem/rust-cache@v2
45+
- uses: taiki-e/install-action@v2
46+
with: { tool: just }
47+
- name: Read MSRV
48+
id: msrv
49+
run: echo "value=$(just get-msrv)" >> $GITHUB_OUTPUT
50+
- name: Install MSRV Rust ${{ steps.msrv.outputs.value }}
51+
uses: dtolnay/rust-toolchain@stable
52+
with:
53+
toolchain: ${{ steps.msrv.outputs.value }}
54+
- run: just ci_mode=0 ci-test-msrv # Ignore warnings in MSRV
55+
56+
coverage:
57+
name: Code Coverage
58+
if: github.event_name != 'release'
59+
runs-on: ubuntu-latest
60+
steps:
61+
- uses: actions/checkout@v4
62+
- uses: Swatinem/rust-cache@v2
63+
- uses: taiki-e/install-action@v2
64+
with: { tool: 'just,cargo-llvm-cov' }
65+
- name: Generate code coverage
66+
run: just ci-coverage
67+
- name: Upload coverage to Codecov
68+
uses: codecov/codecov-action@v5
69+
with:
70+
token: ${{ secrets.CODECOV_TOKEN }}
71+
files: target/llvm-cov/codecov.info
72+
fail_ci_if_error: false
73+
74+
# This job checks if any of the previous jobs failed or were canceled.
75+
# This approach also allows some jobs to be skipped if they are not needed.
76+
ci-passed:
77+
needs: [ test, test-msrv ]
78+
if: always()
79+
runs-on: ubuntu-latest
80+
steps:
81+
- if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
82+
run: exit 1
83+
84+
# Release unpublished packages or create a PR with changes
85+
release-plz:
86+
needs: [ ci-passed ]
87+
if: |
88+
always()
89+
&& needs.ci-passed.result == 'success'
90+
&& github.event_name == 'push'
91+
&& github.ref == 'refs/heads/main'
92+
&& github.repository_owner == 'harfbuzz'
93+
runs-on: ubuntu-latest
94+
permissions:
95+
contents: write
96+
pull-requests: write
97+
concurrency:
98+
group: release-plz-${{ github.ref }}
99+
cancel-in-progress: false
100+
steps:
101+
- uses: actions/checkout@v4
102+
with: { fetch-depth: 0 }
103+
- uses: dtolnay/rust-toolchain@stable
104+
- name: Publish to crates.io if crate's version is newer
105+
uses: release-plz/[email protected]
106+
id: release
107+
with: { command: release }
108+
env:
109+
GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }}
110+
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
111+
- name: If version is the same, create a PR proposing new version and changelog for the next release
112+
uses: release-plz/[email protected]
113+
if: ${{ steps.release.outputs.releases_created == 'false' }}
114+
with: { command: release-pr }
115+
env:
116+
GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }}

.github/workflows/dependabot.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Dependabot auto-merge
2+
on: pull_request
3+
4+
jobs:
5+
dependabot:
6+
runs-on: ubuntu-latest
7+
if: github.actor == 'dependabot[bot]'
8+
steps:
9+
- name: Dependabot metadata
10+
id: metadata
11+
uses: dependabot/fetch-metadata@v2
12+
- name: Approve Dependabot PRs
13+
if: steps.metadata.outputs.update-type == 'version-update:semver-patch'
14+
run: gh pr review --approve "$PR_URL"
15+
env:
16+
PR_URL: ${{ github.event.pull_request.html_url }}
17+
GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }}
18+
- name: Enable auto-merge for Dependabot PRs
19+
if: steps.metadata.outputs.update-type == 'version-update:semver-patch'
20+
run: gh pr merge --auto --squash "$PR_URL"
21+
env:
22+
PR_URL: ${{ github.event.pull_request.html_url }}
23+
GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }}

.github/workflows/main.yml

Lines changed: 0 additions & 39 deletions
This file was deleted.

README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,17 @@ The library is completely safe.
5959

6060
There are no `unsafe` in this library and in most of its dependencies (excluding `bytemuck`).
6161

62-
## Developer documents
63-
64-
For notes on the backporting process of HarfBuzz code, see [docs/backporting.md](docs/backporting.md).
65-
66-
For notes on generating state machine using `ragel`, see [docs/ragel.md](docs/ragel.md).
67-
68-
The following HarfBuzz _studies_ are relevant to HarfRust development:
69-
70-
- 2025 - [Introducing HarfRust][2]
71-
- 2025 – [Caching][1]
62+
## Development
63+
64+
* This project is easier to develop with [just](https://github.com/casey/just#readme), a modern alternative to `make`.
65+
Install it with `cargo install just`.
66+
* To get a list of available commands, run `just`.
67+
* To run tests, use `just test`.
68+
* For notes on the backporting process of HarfBuzz code, see [docs/backporting.md](docs/backporting.md).
69+
* For notes on generating state machine using `ragel`, see [docs/ragel.md](docs/ragel.md).
70+
* The following HarfBuzz _studies_ are relevant to HarfRust development:
71+
- 2025 - [Introducing HarfRust][2]
72+
- 2025 – [Caching][1]
7273

7374
## License
7475

justfile

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#!/usr/bin/env just --justfile
2+
3+
main_crate := 'harfrust'
4+
features_flag := '--all-features'
5+
6+
# if running in CI, treat warnings as errors by setting RUSTFLAGS and RUSTDOCFLAGS to '-D warnings' unless they are already set
7+
# Use `CI=true just ci-test` to run the same tests as in GitHub CI.
8+
# Use `just env-info` to see the current values of RUSTFLAGS and RUSTDOCFLAGS
9+
ci_mode := if env('CI', '') != '' {'1'} else {''}
10+
export RUSTFLAGS := env('RUSTFLAGS', if ci_mode == '1' {'-D warnings'} else {''})
11+
export RUSTDOCFLAGS := env('RUSTDOCFLAGS', if ci_mode == '1' {'-D warnings'} else {''})
12+
export RUST_BACKTRACE := env('RUST_BACKTRACE', if ci_mode == '1' {'1'} else {''})
13+
14+
@_default:
15+
{{just_executable()}} --list
16+
17+
# Build the project
18+
build: build-lib build-lib-no-std
19+
cargo build --workspace --all-targets {{features_flag}}
20+
21+
# Build just the core lib
22+
build-lib:
23+
cargo build --lib --package {{main_crate}} {{features_flag}}
24+
25+
# Build just the core lib with no_std
26+
build-lib-no-std:
27+
cargo build --lib --package {{main_crate}} --no-default-features
28+
29+
# Quick compile without building a binary
30+
check:
31+
cargo check --workspace --all-targets {{features_flag}}
32+
cargo check --lib --package {{main_crate}} {{features_flag}}
33+
cargo check --lib --package {{main_crate}} --no-default-features
34+
35+
# Generate code coverage report to upload to codecov.io
36+
ci-coverage: env-info && \
37+
(coverage '--codecov --output-path target/llvm-cov/codecov.info')
38+
# ATTENTION: the full file path above is used in the CI workflow
39+
mkdir -p target/llvm-cov
40+
41+
# Run all tests as expected by CI
42+
ci-test: env-info test-fmt build test test-doc clippy && assert-git-is-clean
43+
44+
# Run minimal subset of tests to ensure compatibility with MSRV
45+
ci-test-msrv: env-info build-lib build-lib-no-std test
46+
47+
# Clean all build artifacts
48+
clean:
49+
cargo clean
50+
rm -f Cargo.lock
51+
52+
# Run cargo clippy to lint the code
53+
clippy *args:
54+
cargo clippy --workspace --all-targets {{features_flag}} {{args}}
55+
56+
# Generate code coverage report. Will install `cargo llvm-cov` if missing.
57+
coverage *args='--no-clean --open': (cargo-install 'cargo-llvm-cov')
58+
cargo llvm-cov --workspace --all-targets {{features_flag}} --include-build-script {{args}}
59+
60+
# Build and open code documentation
61+
docs *args='--open':
62+
DOCS_RS=1 cargo doc --no-deps {{args}} --workspace {{features_flag}}
63+
64+
# Print environment info
65+
env-info:
66+
@echo "Running {{if ci_mode == '1' {'in CI mode'} else {'in dev mode'} }} on {{os()}} / {{arch()}}"
67+
{{just_executable()}} --version
68+
rustc --version
69+
cargo --version
70+
rustup --version
71+
@echo "RUSTFLAGS='$RUSTFLAGS'"
72+
@echo "RUSTDOCFLAGS='$RUSTDOCFLAGS'"
73+
@echo "RUST_BACKTRACE='$RUST_BACKTRACE'"
74+
75+
# Reformat all code `cargo fmt`.
76+
fmt:
77+
cargo fmt --all
78+
79+
# Get any package's field from the metadata
80+
get-crate-field field package=main_crate: (assert-cmd 'jq')
81+
cargo metadata --format-version 1 | jq -e -r '.packages | map(select(.name == "{{package}}")) | first | .{{field}} | select(. != null)'
82+
83+
# Get the minimum supported Rust version (MSRV) for the crate
84+
get-msrv package=main_crate: (get-crate-field 'rust_version' package)
85+
86+
# Find the minimum supported Rust version (MSRV) using cargo-msrv extension, and update Cargo.toml
87+
msrv: (cargo-install 'cargo-msrv')
88+
cargo msrv find --write-msrv --ignore-lockfile {{features_flag}}
89+
90+
release *args='': (cargo-install 'release-plz')
91+
release-plz {{args}}
92+
93+
# Check semver compatibility with prior published version. Install it with `cargo install cargo-semver-checks`
94+
semver *args: (cargo-install 'cargo-semver-checks')
95+
cargo semver-checks {{features_flag}} {{args}}
96+
97+
# Run all unit and integration tests
98+
test:
99+
cargo test --workspace --all-targets {{features_flag}}
100+
cargo test --workspace --doc {{features_flag}}
101+
102+
# Test documentation generation
103+
test-doc: (docs '')
104+
105+
# Test code formatting
106+
test-fmt:
107+
cargo fmt --all -- --check
108+
109+
# Find unused dependencies. Install it with `cargo install cargo-udeps`
110+
udeps: (cargo-install 'cargo-udeps')
111+
cargo +nightly udeps --workspace --all-targets {{features_flag}}
112+
113+
# Update all dependencies, including breaking changes. Requires nightly toolchain (install with `rustup install nightly`)
114+
update:
115+
cargo +nightly -Z unstable-options update --breaking
116+
cargo update
117+
118+
# Ensure that a certain command is available
119+
[private]
120+
assert-cmd command:
121+
@if ! type {{command}} > /dev/null; then \
122+
echo "Command '{{command}}' could not be found. Please make sure it has been installed on your computer." ;\
123+
exit 1 ;\
124+
fi
125+
126+
# Make sure the git repo has no uncommitted changes
127+
[private]
128+
assert-git-is-clean:
129+
@if [ -n "$(git status --untracked-files --porcelain)" ]; then \
130+
>&2 echo "ERROR: git repo is no longer clean. Make sure compilation and tests artifacts are in the .gitignore, and no repo files are modified." ;\
131+
>&2 echo "######### git status ##########" ;\
132+
git status ;\
133+
git --no-pager diff ;\
134+
exit 1 ;\
135+
fi
136+
137+
# Check if a certain Cargo command is installed, and install it if needed
138+
[private]
139+
cargo-install $COMMAND $INSTALL_CMD='' *args='':
140+
#!/usr/bin/env bash
141+
set -euo pipefail
142+
if ! command -v $COMMAND > /dev/null; then
143+
if ! command -v cargo-binstall > /dev/null; then
144+
echo "$COMMAND could not be found. Installing it with cargo install ${INSTALL_CMD:-$COMMAND} --locked {{args}}"
145+
cargo install ${INSTALL_CMD:-$COMMAND} --locked {{args}}
146+
else
147+
echo "$COMMAND could not be found. Installing it with cargo binstall ${INSTALL_CMD:-$COMMAND} --locked {{args}}"
148+
cargo binstall ${INSTALL_CMD:-$COMMAND} --locked {{args}}
149+
fi
150+
fi

release-plz.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[workspace]
2+
dependencies_update = true
3+
git_release_name = "v{{ version }}"
4+
git_tag_name = "v{{ version }}"

0 commit comments

Comments
 (0)