diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..235fc75 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,19 @@ +name: build +on: + push: + branches: ['**'] + tags: ['v*'] +jobs: + go: + uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/go.yaml@v1 + with: + go-version: '1.26' + golangci-lint: true + race: true + test-args: -coverpkg=./internal/...,./cmd/... -covermode=atomic -coverprofile=cover.out + # 100% coverage gate (.testcoverage.yml), then the plain build. + commands: | + go run github.com/vladopajic/go-test-coverage/v2@v2.18.8 --config=.testcoverage.yml + go build ./... + release-commands: scripts/build-release.sh "${GITHUB_REF_NAME#v}" + artifacts: dist/* diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 25c090b..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,121 +0,0 @@ -name: CI - -on: - pull_request: - branches: - - develop - - main - - release/** - - rc/** - push: - branches: - - develop - - main - - release/** - - rc/** - -permissions: - contents: read - -jobs: - go-checks: - name: Go checks - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v6 - - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version: "1.26" - cache: true - - - name: Check formatting - run: | - unformatted="$(gofmt -l .)" - if [ -n "$unformatted" ]; then - echo "The following files are not gofmt-clean:" - echo "$unformatted" - exit 1 - fi - - - name: Run go vet - run: go vet ./... - - - name: Run golangci-lint - uses: golangci/golangci-lint-action@v9 - with: - # v8 installs golangci-lint v2.x, which understands the v2 .golangci.yml - # schema (version/formatters). latest pulls a build made with a current Go - # toolchain, satisfying the module's go directive. - version: latest - - - name: Run tests with coverage profile - run: | - go test ./... -race \ - -coverpkg=./internal/...,./cmd/... \ - -covermode=atomic \ - -coverprofile=cover.out - - - name: Upload coverage profile - uses: actions/upload-artifact@v7 - with: - name: coverage-profile-${{ github.run_id }}-${{ github.sha }} - path: cover.out - if-no-files-found: error - - - name: Enforce 100% coverage gate - # go-test-coverage is a CI-only tool, run via `go run` so it never becomes a - # go.mod dependency. The gate FAILS the build when total coverage on - # ./internal/...,./cmd/... drops below the 100 threshold in .testcoverage.yml, - # which documents the only two exclusions (the main() shim and gen_spdx.go). - run: go run github.com/vladopajic/go-test-coverage/v2@v2.18.8 --config=.testcoverage.yml - - - name: Build - run: go build ./... - - macos-build: - name: macOS branch build - runs-on: macos-15 - if: github.event_name == 'push' - steps: - - uses: actions/checkout@v6 - - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version: "1.26" - cache: true - - - name: Build binary - run: go build -o license-tool ./cmd/license-tool - - - name: Upload branch binary - uses: actions/upload-artifact@v7 - with: - name: license-tool-${{ github.run_id }}-${{ github.sha }}-macos - path: license-tool - if-no-files-found: error - - windows-build: - name: Windows branch build - runs-on: windows-latest - if: github.event_name == 'push' - steps: - - uses: actions/checkout@v6 - - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version: "1.26" - cache: true - - - name: Build binary - run: go build -o license-tool.exe ./cmd/license-tool - - - name: Upload branch binary - uses: actions/upload-artifact@v7 - with: - name: license-tool-${{ github.run_id }}-${{ github.sha }}-windows - path: license-tool.exe - if-no-files-found: error diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 5420c6e..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Release - -# Run whenever a semver release tag is pushed (e.g. v1.0.0, v1.2.3-rc.1). -on: - push: - tags: - - "v[0-9]+.[0-9]+.[0-9]+*" - -permissions: - contents: write - -jobs: - goreleaser: - name: GoReleaser - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - # GoReleaser needs full history and all tags to compute the changelog - # and resolve the previous tag. - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version: "1.26.4" - cache: true - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2 - with: - version: v2.16.0 - args: release --clean - env: - # GITHUB_TOKEN publishes the GitHub Release in this repo. - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # HOMEBREW_TAP_TOKEN pushes the generated cask to KofTwentyTwo/homebrew-tap. - HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} diff --git a/RELEASES.md b/RELEASES.md index d7d6fbf..2a422e7 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -5,8 +5,8 @@ on [RELEASING.md](./RELEASING.md) (which is the short reference) with the comple process: integrating feature work, choosing a version, running the gate, tagging, and verifying the published artifacts. -Releases are built by [GoReleaser](https://goreleaser.com) and published by the -`Release` GitHub Actions workflow. There is **no version constant in source** — the +Releases are built by `scripts/build-release.sh` and published to the Gitea release by +the tag build of `.gitea/workflows/build.yaml` (see RELEASING.md). There is **no version constant in source** — the version is injected at build time via `-ldflags` (`main.version`, `main.commit`, `main.date`) when the workflow runs against a pushed tag. diff --git a/RELEASING.md b/RELEASING.md index bd3c8a5..9a15396 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -4,7 +4,10 @@ > conflict resolution, tagging, and post-release verification) see [RELEASES.md](./RELEASES.md). > This file is the short reference. -Releases are built with [GoReleaser](https://goreleaser.com) and published by GitHub Actions. +CI and releases run on Gitea Actions (`.gitea/workflows/build.yaml`, the shared +`KofTwentyTwo/ci-workflows` Go workflow). A `v*` tag runs every gate, then +`scripts/build-release.sh` builds the archives and CI attaches them with a `SHA256SUMS` +to the tag's Gitea release. `.goreleaser.yaml` remains for local snapshot checks. ## Changelog Management @@ -72,8 +75,8 @@ git tag vX.Y.Z-rc.N git push origin vX.Y.Z-rc.N ``` -The `Release` workflow builds all configured target binaries and creates a GitHub prerelease -(the `release.prerelease: auto` setting marks any tag with a prerelease suffix as a prerelease). +The tag build creates a Gitea prerelease (any tag with a `-` suffix) with the archives and +`SHA256SUMS`. ## Production Release @@ -86,7 +89,7 @@ From `main`: git commit -am "docs: changelog for v$VERSION" ``` -3. Push `main` and wait for CI and CodeQL to pass on the release commit. +3. Push `main` and wait for CI to pass on the release commit. 4. Tag and push. Prefer a signed annotated tag: ```bash @@ -94,41 +97,10 @@ From `main`: git push origin vX.Y.Z ``` -The `Release` workflow runs `goreleaser release --clean`, which cross-compiles the -mac/linux arm64+amd64 and windows amd64 binaries, builds the archives and `checksums.txt`, -creates the GitHub Release, and publishes the Homebrew cask to: +The tag build runs every CI gate, then `scripts/build-release.sh ` cross-compiles the +mac/linux arm64+amd64 and windows amd64 binaries into `dist/` archives, and CI attaches them +and a `SHA256SUMS` to the tag's Gitea release. -```text -KofTwentyTwo/homebrew-tap -``` +The Homebrew cask in `KofTwentyTwo/homebrew-tap` is not updated by CI; update it by hand from +the release's `SHA256SUMS` until the tap has its own workflow. -End users install with: - -```bash -brew install --cask KofTwentyTwo/tap/license-tool -``` - -## Required GitHub Setup - -Create or verify a public Homebrew tap repository: - -```text -KofTwentyTwo/homebrew-tap -``` - -Add a repository Actions secret on `KofTwentyTwo/license-tool` named: - -```text -HOMEBREW_TAP_TOKEN -``` - -The token needs contents write access to `KofTwentyTwo/homebrew-tap`, because the release -workflow checks out that repository and commits cask updates. Do not hardcode this token -in workflow files. - -Before the first production release: - -- Protect or manage the `main` and `develop` branches according to the branch flow above. -- Confirm `HOMEBREW_TAP_TOKEN` can write to `KofTwentyTwo/homebrew-tap`. -- Confirm the local push path works with `git ls-remote --heads origin`. -- Re-run `goreleaser check` and `goreleaser release --snapshot --clean`. diff --git a/scripts/build-release.sh b/scripts/build-release.sh new file mode 100755 index 0000000..c67214f --- /dev/null +++ b/scripts/build-release.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Cross-compile the release archives into dist/ (the Gitea CI runs this on v* tags). +# +# Same targets, names and ldflags as .goreleaser.yaml: darwin/linux amd64+arm64 and +# windows amd64; tar.gz everywhere except Windows, which gets a zip. +# +# Usage: scripts/build-release.sh (for example 1.2.3 or 1.2.3-rc.1) +set -euo pipefail + +version=${1:?usage: scripts/build-release.sh } +commit=$(git rev-parse HEAD) +date=$(date -u +%Y-%m-%dT%H:%M:%SZ) +ldflags="-s -w -X main.version=$version -X main.commit=$commit -X main.date=$date" + +cd "$(dirname "${BASH_SOURCE[0]}")/.." +rm -rf dist && mkdir dist +for target in darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 windows/amd64; do + os=${target%/*} + arch=${target#*/} + name=license-tool_${version}_${os}_${arch} + ext= + [[ $os == windows ]] && ext=.exe + mkdir "dist/$name" + CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -trimpath -ldflags "$ldflags" \ + -o "dist/$name/license-tool$ext" ./cmd/license-tool + cp README.md LICENSE CHANGELOG.md "dist/$name/" + if [[ $os == windows ]]; then + (cd "dist/$name" && python3 -m zipfile -c "../$name.zip" ./*) + else + tar -czf "dist/$name.tar.gz" -C "dist/$name" . + fi + rm -r "dist/${name:?}" +done +ls -l dist