Skip to content

Commit 6d7ab63

Browse files
authored
Merge pull request #50 from linksplatform/issue-49-8e6972ee84cf
Fix CI/CD release pipeline for Cargo workspace repos
2 parents 9bac9a7 + 112462f commit 6d7ab63

18 files changed

Lines changed: 8160 additions & 42 deletions

.github/workflows/release.yml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@ jobs:
6868
id: changes
6969
env:
7070
GITHUB_EVENT_NAME: ${{ github.event_name }}
71-
GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }}
72-
GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
7371
run: rust-script scripts/detect-code-changes.rs
7472

7573
# === CHANGELOG CHECK - only runs on PRs with code changes ===
@@ -121,11 +119,16 @@ jobs:
121119
GITHUB_BASE_REF: ${{ github.base_ref }}
122120
run: rust-script scripts/check-version-modification.rs
123121

122+
# Lint runs independently of changelog check - it's a fast check that should always run
123+
# See: https://github.com/link-assistant/hive-mind/pull/1024 for why this dependency was removed
124124
# === LINT AND FORMAT CHECK ===
125125
lint:
126126
name: Lint and Format Check
127127
runs-on: ubuntu-latest
128128
needs: [detect-changes]
129+
# Note: always() is required because detect-changes is skipped on workflow_dispatch,
130+
# and without always(), this job would also be skipped even though its condition includes workflow_dispatch.
131+
# See: https://github.com/actions/runner/issues/491
129132
if: |
130133
always() && !cancelled() && (
131134
github.event_name == 'push' ||
@@ -168,11 +171,13 @@ jobs:
168171
- name: Check file size limit
169172
run: rust-script scripts/check-file-size.rs
170173

174+
# Test runs independently of changelog check
171175
# === TEST ===
172176
test:
173177
name: Test (${{ matrix.os }})
174178
runs-on: ${{ matrix.os }}
175179
needs: [detect-changes, changelog]
180+
# Run if: push event, OR changelog succeeded, OR changelog was skipped (docs-only PR)
176181
if: always() && !cancelled() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || needs.changelog.result == 'success' || needs.changelog.result == 'skipped')
177182
strategy:
178183
fail-fast: false
@@ -203,6 +208,7 @@ jobs:
203208
- name: Run doc tests
204209
run: cargo test --doc --verbose
205210

211+
# Generate and upload code coverage using cargo-llvm-cov
206212
# === CODE COVERAGE ===
207213
coverage:
208214
name: Code Coverage
@@ -248,6 +254,7 @@ jobs:
248254
files: lcov.info
249255
fail_ci_if_error: false
250256

257+
# Build package - only runs if lint and test pass
251258
# === BUILD ===
252259
build:
253260
name: Build Package
@@ -279,10 +286,13 @@ jobs:
279286
- name: Check package
280287
run: cargo package -p doublets --list --allow-dirty
281288

289+
# Automatic release on push to main using changelog fragments
290+
# This job automatically bumps version based on fragments in changelog.d/
282291
# === AUTO RELEASE ===
283292
auto-release:
284293
name: Auto Release
285294
needs: [lint, test, build]
295+
# Note: always() ensures consistent behavior with other jobs that depend on jobs using always().
286296
if: |
287297
always() && !cancelled() &&
288298
github.event_name == 'push' &&
@@ -346,16 +356,20 @@ jobs:
346356
env:
347357
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
348358
run: |
359+
# Use new_version from version-and-commit when available (tag-checked), else fall back to Cargo.toml version
349360
RELEASE_VERSION="${{ steps.version.outputs.new_version }}"
350361
if [ -z "$RELEASE_VERSION" ]; then
351362
RELEASE_VERSION="${{ steps.current_version.outputs.version }}"
352363
fi
353364
rust-script scripts/create-github-release.rs --release-version "$RELEASE_VERSION" --repository "${{ github.repository }}"
354365
366+
# Manual release via workflow_dispatch - only after CI passes
355367
# === MANUAL INSTANT RELEASE ===
356368
manual-release:
357369
name: Instant Release
358370
needs: [lint, test, build]
371+
# Note: always() is required to evaluate the condition when dependencies use always().
372+
# The build job ensures lint and test passed before this job runs.
359373
if: |
360374
always() && !cancelled() &&
361375
github.event_name == 'workflow_dispatch' &&
@@ -457,6 +471,7 @@ jobs:
457471
2. Merge this PR to main
458472
3. The automated release workflow will publish to crates.io and create a GitHub release
459473
474+
# Deploy Rust API documentation to GitHub Pages after a successful release
460475
# === DEPLOY DOCUMENTATION ===
461476
deploy-docs:
462477
name: Deploy Rust Documentation
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
bump: patch
3+
---
4+
5+
### Fixed
6+
- Fixed CI/CD release pipeline failing on Cargo workspace repositories
7+
- All release scripts now correctly resolve the publishable member crate's Cargo.toml instead of reading the workspace root manifest
8+
- Fixed `bump-version.rs` failing on versions with pre-release suffixes (e.g. `0.1.0-pre+beta.15`)
9+
- Fixed `publish-crate.rs` to pass `-p <package>` flag for workspace repos
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Case Study: CI/CD Publishing Failed (Issue #49)
2+
3+
## Timeline of Events
4+
5+
1. **2026-04-14T15:13:22Z** - Push to `main` branch triggers CI/CD pipeline (run [#24406907038](https://github.com/linksplatform/doublets-rs/actions/runs/24406907038))
6+
2. **2026-04-14T15:13:22Z - 15:14:10Z** - Jobs `Detect Changes`, `Lint and Format Check`, `Code Coverage`, `Test` (all platforms), `Build Package` pass successfully
7+
3. **2026-04-14T15:14:10Z** - `Auto Release` job starts, passes setup steps (checkout, rust toolchain, rust-script, git config, bump type detection)
8+
4. **2026-04-14T15:14:34Z** - **FAILURE** at step "Check if version already released or no fragments"
9+
- Error: `Could not find name in ./Cargo.toml`
10+
- Exit code: 1
11+
12+
## Root Cause Analysis
13+
14+
### Primary Root Cause: Workspace Cargo.toml incompatibility
15+
16+
The repository uses a **Cargo workspace** structure:
17+
18+
```
19+
./Cargo.toml # [workspace] only - NO [package] section
20+
./doublets/Cargo.toml # [package] with name="doublets", version="0.1.0-pre+beta.15"
21+
./integration/Cargo.toml # [package] with publish=false
22+
```
23+
24+
All CI/CD release scripts (`check-release-needed.rs`, `version-and-commit.rs`, `publish-crate.rs`, `get-version.rs`, `bump-version.rs`, `collect-changelog.rs`, `create-github-release.rs`, `check-version-modification.rs`) were designed for **single-crate repositories** where the root `Cargo.toml` contains both `[package]` and `[workspace]` sections, or is purely a `[package]`.
25+
26+
The scripts use regex `^name\s*=\s*"([^"]+)"` to extract the crate name from root `Cargo.toml`, which fails when the root is a workspace-only manifest.
27+
28+
### Secondary Root Cause: Pre-release version regex in bump-version.rs
29+
30+
The `bump-version.rs` script uses regex `^version\s*=\s*"(\d+)\.(\d+)\.(\d+)"` which requires the version string to end immediately after the patch number. The actual version `0.1.0-pre+beta.15` has a pre-release suffix, causing the regex to not match.
31+
32+
## Affected Components
33+
34+
| Script | Reads Name | Reads Version | Writes Version | Impact |
35+
|--------|-----------|---------------|----------------|--------|
36+
| check-release-needed.rs | Yes | Yes | No | **Blocks all releases** |
37+
| version-and-commit.rs | Yes | Yes | Yes | Blocks version bumping |
38+
| publish-crate.rs | Yes | Yes | No | Blocks crates.io publishing |
39+
| get-version.rs | No | Yes | No | Blocks version reporting |
40+
| bump-version.rs | No | Yes | Yes | Blocks version bumping |
41+
| collect-changelog.rs | No | Yes | No | Blocks changelog generation |
42+
| create-github-release.rs | Yes (optional) | No | No | Missing badges in releases |
43+
| check-version-modification.rs | No | No | No | Checks wrong file for diffs |
44+
| rust-paths.rs | No | No | No | Missing workspace info |
45+
46+
## Solution
47+
48+
Added workspace-aware `Cargo.toml` resolution to all affected scripts:
49+
50+
1. **Detection**: Check if root `Cargo.toml` contains `[workspace]` section
51+
2. **Member parsing**: Extract `members = [...]` list from workspace manifest
52+
3. **Publishable member selection**: Find the first member whose `Cargo.toml` does NOT contain `publish = false`
53+
4. **Path resolution**: Return the publishable member's `Cargo.toml` path instead of root
54+
55+
Additionally:
56+
- Fixed `bump-version.rs` regex to handle pre-release version suffixes
57+
- Added `-p <package>` flag to `cargo publish` command in `publish-crate.rs` for workspace repos
58+
59+
## Verification
60+
61+
All scripts tested locally against the actual workspace structure:
62+
63+
```
64+
$ rust-script scripts/check-release-needed.rs
65+
Detected workspace Cargo.toml, searching for publishable member...
66+
Using publishable workspace member: doublets (doublets/Cargo.toml)
67+
Crate: doublets, Version: 0.1.0-pre+beta.15, Published on crates.io: true
68+
```
69+
70+
## Template Repository Impact
71+
72+
- **Rust template** (`link-foundation/rust-ai-driven-development-pipeline-template`): Same bug confirmed. All 5 release scripts lack workspace awareness. Issue filed: https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/36
73+
- **JS template** (`link-foundation/js-ai-driven-development-pipeline-template`): Not affected. npm/pnpm workspace root `package.json` files still contain `name` and `version`, and the template uses `@changesets/cli` with native workspace support.
74+
75+
## CI Logs
76+
77+
- Full run log: `ci-logs/run-24406907038-full.log`
78+
- Failed step log: `ci-logs/run-24406907038-failed.log`
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2704245Z ##[group]Run rust-script scripts/check-release-needed.rs
2+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2704915Z rust-script scripts/check-release-needed.rs
3+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2725778Z shell: /usr/bin/bash -e {0}
4+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2726103Z env:
5+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2726409Z CARGO_TERM_COLOR: always
6+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2726680Z RUSTFLAGS: -Dwarnings
7+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2782405Z CARGO_REGISTRY_TOKEN: ***
8+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2782920Z CARGO_TOKEN: ***
9+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2783238Z CARGO_HOME: /home/runner/.cargo
10+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2783607Z CARGO_INCREMENTAL: 0
11+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2784175Z HAS_FRAGMENTS: true
12+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2784871Z ##[endgroup]
13+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:34.5924661Z Detected single-language repository (Cargo.toml in root)
14+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:34.5926800Z Error: Could not find name in ./Cargo.toml
15+
Auto Release Check if version already released or no fragments 2026-04-14T15:14:34.5943244Z ##[error]Process completed with exit code 1.

0 commit comments

Comments
 (0)