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
63 changes: 50 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ jobs:
with:
install-only: true

- name: Build (split)
- name: Build (single-target)
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: goreleaser build --split --clean --timeout 60m
run: goreleaser build --single-target --clean --timeout 60m

- name: Upload artifacts
uses: actions/upload-artifact@v4
Expand All @@ -70,24 +70,61 @@ jobs:
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: dist
pattern: build-*
merge-multiple: true

- name: Set up GoReleaser
uses: goreleaser/goreleaser-action@v7
with:
install-only: true
- name: Get version
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

- name: Create archives and checksums
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
mkdir -p release

# Find all built binaries and create tar.gz archives
for build_dir in dist/*/; do
# Skip non-directory entries
[ -d "$build_dir" ] || continue

binary="$build_dir/lango"
[ -f "$binary" ] || continue

# Extract dir name: e.g. lango_linux_amd64_v1 or lango-extended_darwin_arm64
dirname=$(basename "$build_dir")

- name: Release (merge)
# Normalize: strip GOAMD64 suffix (_v1, _v2, etc.)
normalized=$(echo "$dirname" | sed 's/_v[0-9]*$//')

# Build archive name: lango_{VERSION}_{os}_{arch}.tar.gz
# dirname format: {build_id}_{os}_{arch}
build_id=$(echo "$normalized" | cut -d_ -f1)
os_arch=$(echo "$normalized" | cut -d_ -f2-)
archive_name="${build_id}_${VERSION}_${os_arch}.tar.gz"

# Create tar.gz with binary + docs
tar -czf "release/${archive_name}" \
-C "$build_dir" lango \
-C "$GITHUB_WORKSPACE" LICENSE README.md 2>/dev/null || \
tar -czf "release/${archive_name}" \
-C "$build_dir" lango
done

# Generate checksums
cd release
sha256sum *.tar.gz > checksums.txt

- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: goreleaser continue --merge --timeout 60m
VERSION: ${{ steps.version.outputs.version }}
run: |
gh release create "$GITHUB_REF_NAME" \
--title "lango v${VERSION}" \
--generate-notes \
release/*
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-28
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## Context

The release workflow uses `goreleaser build --split` and `goreleaser continue --merge`, which are GoReleaser Pro-only features. The `goreleaser/goreleaser-action@v7` installs the OSS version, causing `unknown flag: --split` errors. This project requires CGO_ENABLED=1 (mattn/go-sqlite3), making cross-compilation impossible and requiring native per-platform builds.

## Goals / Non-Goals

**Goals:**
- Make the release workflow work with GoReleaser OSS (free tier)
- Maintain the same release artifact naming convention
- Keep the native runner matrix strategy for CGO cross-platform builds
- Produce identical release output (tar.gz archives + checksums)

**Non-Goals:**
- Migrating to GoReleaser Pro
- Changing the `.goreleaser.yaml` build configuration
- Modifying the archive naming convention
- Adding new platforms or architectures

## Decisions

### Decision 1: Use `--single-target` instead of `--split`
`goreleaser build --single-target` is an OSS-compatible flag that builds only for the current GOOS/GOARCH. Combined with the existing matrix of native runners, this produces the same result as `--split` — each runner builds only its own platform binaries.

**Alternative considered**: Running `go build` directly — rejected because it would duplicate ldflags/tags/env logic already defined in `.goreleaser.yaml`.

### Decision 2: Manual archive + release instead of `--merge`
Since `goreleaser continue --merge` is Pro-only, the release job manually:
1. Finds built binaries in downloaded `dist/` directories
2. Normalizes GOAMD64 suffixes (e.g., `_v1`) from directory names
3. Creates tar.gz archives with the same naming convention
4. Generates SHA256 checksums
5. Uses `gh release create --generate-notes` for the GitHub Release

**Alternative considered**: Using a separate release tool (e.g., `nfpm`) — rejected as overkill for tar.gz archives.

### Decision 3: Remove Go setup from release job
The release job no longer runs GoReleaser, so Go is not needed. Only shell tools (`tar`, `sha256sum`, `gh`) are required.

## Risks / Trade-offs

- [Risk] GOAMD64 suffix normalization via `sed 's/_v[0-9]*$//'` could match unintended patterns → Mitigation: Pattern is specific to trailing `_v` + digits, matching GoReleaser's known convention
- [Risk] LICENSE/README.md may not exist in some builds → Mitigation: Fallback tar command without docs files
- [Risk] `sha256sum` not available on all runners → Mitigation: Release job runs on ubuntu-latest where it's always available
- [Trade-off] Changelog grouping from `.goreleaser.yaml` is no longer used; `--generate-notes` uses GitHub's default format → Acceptable since GitHub's auto-generated notes are sufficient
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## Why

The `release.yml` workflow uses `goreleaser build --split` and `goreleaser continue --merge`, which are **GoReleaser Pro-only** flags. The `goreleaser/goreleaser-action@v7` installs the OSS version, causing CI failures with `unknown flag: --split`.

## What Changes

- Replace `goreleaser build --split` with `goreleaser build --single-target` (OSS-compatible) in the build job
- Replace `goreleaser continue --merge` with manual archive creation + `gh release create` in the release job
- Remove unnecessary Go setup step from the release job
- Add version extraction, tar.gz archive creation, SHA256 checksum generation, and GitHub Release creation steps

## Capabilities

### New Capabilities

(none)

### Modified Capabilities

- `release-workflow`: Replace Pro-only split/merge strategy with OSS-compatible single-target build + manual archive/release pipeline

## Impact

- **File**: `.github/workflows/release.yml` — build and release jobs restructured
- **No code changes**: `.goreleaser.yaml` unchanged; build settings (ldflags, CGO_ENABLED, tags) still used by `--single-target`
- **Release artifacts**: Same naming convention maintained (`lango_{VERSION}_{os}_{arch}.tar.gz`, `checksums.txt`)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## MODIFIED Requirements

### Requirement: Split build execution
Each matrix runner SHALL execute `goreleaser build --single-target --clean --timeout 60m` to produce binaries only for its native platform. The `--single-target` flag uses the GOOS and GOARCH environment variables set by the matrix to determine the build target.

#### Scenario: Single-target build produces platform-specific artifacts
- **WHEN** `goreleaser build --single-target` runs on `macos-14` with GOOS=darwin GOARCH=arm64
- **THEN** it SHALL produce darwin/arm64 binaries for both build IDs (lango and lango-extended) and upload the dist/ directory as artifacts

### Requirement: Merge and release job
A separate `release` job SHALL download all build artifacts and create a GitHub Release with manually assembled archives and checksums. The job SHALL NOT require Go or GoReleaser installation.

#### Scenario: Manual archive creation and release
- **WHEN** all 4 build jobs complete successfully
- **THEN** the release job SHALL:
1. Download artifacts with `merge-multiple: true`
2. Extract version from the git tag (strip `v` prefix)
3. Find built binaries in dist/ subdirectories
4. Normalize directory names by stripping GOAMD64 suffixes (e.g., `_v1`)
5. Create tar.gz archives named `{build_id}_{VERSION}_{os}_{arch}.tar.gz`
6. Generate SHA256 checksums in `checksums.txt`
7. Create a GitHub Release using `gh release create` with `--generate-notes`

#### Scenario: Archive naming convention
- **WHEN** archives are created for version 0.3.0
- **THEN** they SHALL follow the naming pattern `lango_0.3.0_linux_amd64.tar.gz` and `lango-extended_0.3.0_linux_amd64.tar.gz`
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## 1. Build Job Fix

- [x] 1.1 Replace `goreleaser build --split` with `goreleaser build --single-target --clean --timeout 60m` in the build job
- [x] 1.2 Update build step name from "Build (split)" to "Build (single-target)"

## 2. Release Job Rewrite

- [x] 2.1 Remove Go setup and GoReleaser setup steps from the release job
- [x] 2.2 Add version extraction step (strip `v` prefix from GITHUB_REF_NAME)
- [x] 2.3 Add archive creation step: iterate dist/ dirs, normalize GOAMD64 suffixes, create tar.gz archives
- [x] 2.4 Add SHA256 checksum generation step
- [x] 2.5 Replace `goreleaser continue --merge` with `gh release create --generate-notes`

## 3. Verification

- [x] 3.1 Run `goreleaser check` to validate .goreleaser.yaml remains valid
- [x] 3.2 Verify `--single-target` flag is available in GoReleaser OSS
27 changes: 19 additions & 8 deletions openspec/specs/release-workflow/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Purpose

Defines the GitHub Actions release pipeline that uses a native runner matrix with split/merge strategy for CGO-dependent cross-platform binary builds on tag push.
Defines the GitHub Actions release pipeline that uses a native runner matrix with single-target builds and manual archive/release for CGO-dependent cross-platform binary builds on tag push.

## Requirements

Expand Down Expand Up @@ -36,18 +36,29 @@ The workflow SHALL install `libsqlite3-dev` on Linux runners before building.
- **THEN** it SHALL NOT run apt-get (macOS uses system frameworks)

### Requirement: Split build execution
Each matrix runner SHALL execute `goreleaser build --split --clean` to produce binaries only for its native platform.
Each matrix runner SHALL execute `goreleaser build --single-target --clean --timeout 60m` to produce binaries only for its native platform. The `--single-target` flag uses the GOOS and GOARCH environment variables set by the matrix to determine the build target.

#### Scenario: Split build produces platform-specific artifacts
- **WHEN** `goreleaser build --split` runs on `macos-14`
- **THEN** it SHALL produce darwin/arm64 binaries only and upload them as artifacts
#### Scenario: Single-target build produces platform-specific artifacts
- **WHEN** `goreleaser build --single-target` runs on `macos-14` with GOOS=darwin GOARCH=arm64
- **THEN** it SHALL produce darwin/arm64 binaries for both build IDs (lango and lango-extended) and upload the dist/ directory as artifacts

### Requirement: Merge and release job
A separate `release` job SHALL download all build artifacts, merge them into `dist/`, and run `goreleaser continue --merge` to create the GitHub Release.
A separate `release` job SHALL download all build artifacts and create a GitHub Release with manually assembled archives and checksums. The job SHALL NOT require Go or GoReleaser installation.

#### Scenario: Artifact merge and release creation
#### Scenario: Manual archive creation and release
- **WHEN** all 4 build jobs complete successfully
- **THEN** the release job SHALL download artifacts with `merge-multiple: true`, run `goreleaser continue --merge`, and create a GitHub Release with all 8 archives + checksums
- **THEN** the release job SHALL:
1. Download artifacts with `merge-multiple: true`
2. Extract version from the git tag (strip `v` prefix)
3. Find built binaries in dist/ subdirectories
4. Normalize directory names by stripping GOAMD64 suffixes (e.g., `_v1`)
5. Create tar.gz archives named `{build_id}_{VERSION}_{os}_{arch}.tar.gz`
6. Generate SHA256 checksums in `checksums.txt`
7. Create a GitHub Release using `gh release create` with `--generate-notes`

#### Scenario: Archive naming convention
- **WHEN** archives are created for version 0.3.0
- **THEN** they SHALL follow the naming pattern `lango_0.3.0_linux_amd64.tar.gz` and `lango-extended_0.3.0_linux_amd64.tar.gz`

### Requirement: Write permissions for release
The workflow SHALL request `contents: write` permission for creating GitHub Releases.
Expand Down