Skip to content

Commit 80e23c5

Browse files
committed
chore(release): centralize workspace versioning + add cargo-release tooling
Bump the workspace to 0.2.0-alpha.5 and make all-at-once releases a single operation instead of editing ~17 Cargo.toml files by hand. - A. Centralize internal crate deps in root [workspace.dependencies] (path + version); members now depend via { workspace = true }. The version literal lives only in [workspace.package] version + that table. - B. Add cargo-release (release.toml: shared-version, publish=false, push=false, tag v{version}) and Make targets: release-dry, release-version, release, publish-dry. CI still performs the actual crates.io publish on tag push. - C. Release workflow: also match pre-release tags (v0.2.0-alpha.5, -rc.N), and add a workflow_dispatch dry_run mode that runs 'cargo package' (build+verify, no upload) so the publish path can be exercised safely. Exclude the two publish=false FFI crates from the dry-run packaging set. Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
1 parent c44ff53 commit 80e23c5

22 files changed

Lines changed: 195 additions & 61 deletions

File tree

.github/workflows/release.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ on:
1717
push:
1818
tags:
1919
- "v[0-9]+.[0-9]+.[0-9]+"
20+
- "v[0-9]+.[0-9]+.[0-9]+-*" # pre-releases: v0.2.0-alpha.5, v1.0.0-rc.1, …
2021
workflow_dispatch:
22+
inputs:
23+
dry_run:
24+
description: "Package + verify all crates without uploading to crates.io"
25+
type: boolean
26+
default: true
2127

2228
permissions:
2329
contents: read
@@ -68,8 +74,23 @@ jobs:
6874
- name: cargo publish (dependency order)
6975
env:
7076
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
77+
# true only for a manual workflow_dispatch with dry_run left on.
78+
# A real version-tag push has empty DRY_RUN → publishes for real.
79+
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
7180
run: |
7281
set -euo pipefail
82+
# Dry run: build + verify a .crate for every publishable member
83+
# (cargo skips `publish = false` crates and resolves inter-member
84+
# deps against each other's packaged versions) without uploading.
85+
# This is how the workflow is exercised without a real release.
86+
if [ "$DRY_RUN" = "true" ]; then
87+
echo "::group::dry run — cargo package (no upload)"
88+
# Verify the crates.io-published set only; the two publish=false
89+
# FFI crates are not part of the registry release.
90+
cargo package --workspace --locked --exclude cpex-ffi --exclude cpex-demo-ffi
91+
echo "::endgroup::"
92+
exit 0
93+
fi
7394
# Leaf-first topological order. Each `cargo publish` blocks until the
7495
# crate is visible in the index before returning, so the next crate's
7596
# dependency resolves; the short sleep is extra slack for propagation.

Cargo.lock

Lines changed: 17 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Location: ./Cargo.toml
22
# Copyright 2025
33
# SPDX-License-Identifier: Apache-2.0
4-
# Authors: Teryl Taylor
4+
# Authors: Teryl Taylor, Fred Araujo
55
#
66
# Workspace root for the CPEX Rust crates.
77

@@ -58,7 +58,7 @@ default-members = [
5858
]
5959

6060
[workspace.package]
61-
version = "0.2.0"
61+
version = "0.2.0-alpha.5"
6262
edition = "2021"
6363
# MSRV — keep in sync with rust-toolchain.toml `channel` and clippy.toml `msrv`.
6464
rust-version = "1.96"
@@ -95,6 +95,29 @@ serde_bytes = "0.11"
9595
chrono = { version = "0.4", features = ["serde"] }
9696
regex = "1"
9797

98+
# Internal workspace crates — single source of truth for their path + version
99+
# requirement. Members depend on these via `{ workspace = true }` so a version
100+
# bump touches only this table (plus `[workspace.package] version`). The
101+
# `version` is required because these crates publish to crates.io: at publish
102+
# time cargo substitutes it for the `path`. `cargo release` keeps these in sync
103+
# with the workspace version. Keys are package names (builtins differ from their
104+
# directory names), paths are workspace-root-relative.
105+
cpex-core = { path = "crates/cpex-core", version = "0.2.0-alpha.5" }
106+
cpex-orchestration = { path = "crates/cpex-orchestration", version = "0.2.0-alpha.5" }
107+
cpex-sdk = { path = "crates/cpex-sdk", version = "0.2.0-alpha.5" }
108+
cpex-builtins = { path = "crates/cpex-builtins", version = "0.2.0-alpha.5" }
109+
apl-core = { path = "crates/apl-core", version = "0.2.0-alpha.5" }
110+
apl-cmf = { path = "crates/apl-cmf", version = "0.2.0-alpha.5" }
111+
apl-cpex = { path = "crates/apl-cpex", version = "0.2.0-alpha.5" }
112+
cpex-plugin-pii-scanner = { path = "builtins/plugins/pii-scanner", version = "0.2.0-alpha.5" }
113+
cpex-plugin-audit-logger = { path = "builtins/plugins/audit-logger", version = "0.2.0-alpha.5" }
114+
cpex-plugin-identity-jwt = { path = "builtins/plugins/identity-jwt", version = "0.2.0-alpha.5" }
115+
cpex-plugin-delegator-oauth = { path = "builtins/plugins/delegator-oauth", version = "0.2.0-alpha.5" }
116+
cpex-plugin-delegator-biscuit = { path = "builtins/plugins/delegator-biscuit", version = "0.2.0-alpha.5" }
117+
cpex-pdp-cedar-direct = { path = "builtins/pdps/cedar-direct", version = "0.2.0-alpha.5" }
118+
cpex-pdp-cel = { path = "builtins/pdps/cel", version = "0.2.0-alpha.5" }
119+
cpex-session-valkey = { path = "builtins/session/valkey", version = "0.2.0-alpha.5" }
120+
98121
# Size-first release profile. The FFI artifact (libcpex_ffi.a) is linked
99122
# statically into host binaries, so its compiled size flows straight into
100123
# those images. The default release profile leaves symbols + debug info in

Makefile

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ help:
6363
@echo ""
6464
@echo "End-to-end:"
6565
@echo " ci Lint + tests + examples-build (CI gate)"
66+
@echo ""
67+
@echo "Release (version bump + tag locally; CI publishes on tag push):"
68+
@echo " release-dry Preview the release (no changes)"
69+
@echo " release-version Set the version everywhere (no commit/tag)"
70+
@echo " release Bump + commit + tag (then: git push origin vX.Y.Z)"
71+
@echo " publish-dry Local packaging dry-run (mirrors CI dry-run)"
72+
@echo " Pass LEVEL=alpha|patch|minor|major|rc|release or VERSION=X.Y.Z"
6673

6774
# =============================================================================
6875
# Build
@@ -253,3 +260,56 @@ examples-run: examples-build
253260
.PHONY: ci
254261
ci: lint test examples-build
255262
@echo "✅ CI gate passed (lint + tests + examples)"
263+
264+
# =============================================================================
265+
# Release
266+
# =============================================================================
267+
#
268+
# This workspace versions and releases every publishable crate together. The
269+
# version lives in ONE place — `[workspace.package] version` plus the
270+
# `[workspace.dependencies]` table in the root Cargo.toml — and cargo-release
271+
# keeps both in sync. Config (shared-version, tag name, publish=false) lives in
272+
# release.toml; the actual crates.io publish runs in CI on the pushed tag.
273+
#
274+
# Bump level (LEVEL) or explicit VERSION:
275+
# make release-dry # preview, no changes (default LEVEL=alpha)
276+
# make release LEVEL=patch # 0.2.0 -> 0.2.1
277+
# make release VERSION=0.2.0 # drop the pre-release suffix
278+
# git push origin "v$(...)" # push the tag to let CI publish
279+
280+
LEVEL ?= alpha
281+
VERSION ?=
282+
# Explicit VERSION wins over LEVEL when set.
283+
RELEASE_ARG = $(if $(VERSION),$(VERSION),$(LEVEL))
284+
285+
.PHONY: release-tool
286+
release-tool:
287+
@command -v cargo-release >/dev/null 2>&1 || $(CARGO) install cargo-release --locked
288+
289+
# Preview only — cargo-release makes NO changes without --execute.
290+
.PHONY: release-dry
291+
release-dry: release-tool
292+
@$(CARGO) release $(RELEASE_ARG) --workspace
293+
294+
# Rewrite the version in [workspace.package] + [workspace.dependencies] only;
295+
# no commit, no tag. Useful for a manual, reviewed bump.
296+
.PHONY: release-version
297+
release-version: release-tool
298+
@$(CARGO) release version $(RELEASE_ARG) --workspace --execute --no-confirm
299+
300+
# Bump + commit + tag, then stop. --no-publish/--no-push enforce the
301+
# "CI publishes on tag push" model at the CLI level too (release.toml already
302+
# sets publish=false/push=false; this makes the guarantee not depend on config
303+
# parsing). Afterwards: `git push origin vX.Y.Z` to trigger the CI publish.
304+
.PHONY: release
305+
release: release-tool
306+
@$(CARGO) release $(RELEASE_ARG) --workspace --no-publish --no-push --execute
307+
308+
# Build + verify a .crate for every crates.io-published member without
309+
# uploading — the same check the release workflow's dry-run runs. The two
310+
# `publish = false` FFI crates are excluded (cpex-ffi ships as signed prebuilt
311+
# artifacts; cpex-demo-ffi is an example). CI runs this on a clean checkout;
312+
# --allow-dirty lets you run it locally with work in progress.
313+
.PHONY: publish-dry
314+
publish-dry:
315+
@$(CARGO) package --workspace --locked --allow-dirty --exclude cpex-ffi --exclude cpex-demo-ffi

builtins/pdps/cedar-direct/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ categories.workspace = true
2626
rust-version.workspace = true
2727

2828
[dependencies]
29-
apl-core = { path = "../../../crates/apl-core", version = "0.2.0" }
29+
apl-core = { workspace = true }
3030
# Permissive caret spec — `"4"` means "any 4.x that Cargo can find."
3131
#
3232
# Code-side note: we use `Request::new(...)` (added in 4.11 alongside
@@ -52,9 +52,9 @@ thiserror = { workspace = true }
5252
# dev-dep edges only exist for tests — the crate itself stays
5353
# apl-core-only at compile time so it can be used standalone (e.g. in a
5454
# custom orchestrator that doesn't go through apl-cpex at all).
55-
apl-cmf = { path = "../../../crates/apl-cmf", version = "0.2.0" }
56-
apl-cpex = { path = "../../../crates/apl-cpex", version = "0.2.0" }
57-
cpex-core = { path = "../../../crates/cpex-core", version = "0.2.0" }
55+
apl-cmf = { workspace = true }
56+
apl-cpex = { workspace = true }
57+
cpex-core = { workspace = true }
5858
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
5959
# Minimal executor for the small-stack regression test (tests/small_stack_eval.rs):
6060
# drives the async `evaluate` without tokio's larger per-call stack footprint, so

builtins/pdps/cel/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ categories.workspace = true
2626
rust-version.workspace = true
2727

2828
[dependencies]
29-
apl-core = { path = "../../../crates/apl-core", version = "0.2.0" }
29+
apl-core = { workspace = true }
3030
# The CEL interpreter from cel-rust/cel-rust (formerly
3131
# clarkmcc/cel-rust). Sync eval, comprehension macros (`has`, `all`,
3232
# `exists`, `map`, `filter`), custom functions. Caret spec tracks 0.x
@@ -55,9 +55,9 @@ tracing = { workspace = true }
5555
# edges only exist for tests — the crate itself stays apl-core-only at
5656
# compile time so it can be used standalone (e.g. in a custom orchestrator
5757
# that doesn't go through apl-cpex at all).
58-
apl-cmf = { path = "../../../crates/apl-cmf", version = "0.2.0" }
59-
apl-cpex = { path = "../../../crates/apl-cpex", version = "0.2.0" }
60-
cpex-core = { path = "../../../crates/cpex-core", version = "0.2.0" }
58+
apl-cmf = { workspace = true }
59+
apl-cpex = { workspace = true }
60+
cpex-core = { workspace = true }
6161
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
6262

6363
[lints]

builtins/plugins/audit-logger/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ categories.workspace = true
2222
rust-version.workspace = true
2323

2424
[dependencies]
25-
cpex-core = { path = "../../../crates/cpex-core", version = "0.2.0" }
25+
cpex-core = { workspace = true }
2626

2727
async-trait = { workspace = true }
2828
chrono = { workspace = true }

builtins/plugins/delegator-biscuit/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ categories.workspace = true
4848
rust-version.workspace = true
4949

5050
[dependencies]
51-
cpex-core = { path = "../../../crates/cpex-core", version = "0.2.0" }
51+
cpex-core = { workspace = true }
5252

5353
# biscuit-auth v6 — current major. Maintained by Clever Cloud +
5454
# community. Ed25519 + Datalog. No default-features off needed; the

builtins/plugins/delegator-oauth/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ categories.workspace = true
4343
rust-version.workspace = true
4444

4545
[dependencies]
46-
cpex-core = { path = "../../../crates/cpex-core", version = "0.2.0" }
46+
cpex-core = { workspace = true }
4747

4848
# `reqwest` for the HTTP POST to the IdP token endpoint. Default
4949
# features pull `rustls` for TLS — we explicitly disable the

builtins/plugins/identity-jwt/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ categories.workspace = true
2929
rust-version.workspace = true
3030

3131
[dependencies]
32-
cpex-core = { path = "../../../crates/cpex-core", version = "0.2.0" }
32+
cpex-core = { workspace = true }
3333

3434
# `jsonwebtoken` is the de facto JWT library for Rust. Supports
3535
# RS256/RS384/RS512, ES256/ES384, EdDSA, HS256/HS384/HS512. Default

0 commit comments

Comments
 (0)