Automating Software Releases via GitHub Actions
Introduction
Automating software releases streamlines the development workflow, ensuring consistency and reliability. GitHub Actions provides a powerful mechanism to automate testing, building, and releasing software. This article explores a CI/CD pipeline using two GitHub Actions workflows: one for continuous integration (CI) and another for releasing software.
Continuous Integration (CI) Workflow
The CI workflow ensures that every code push and pull request meets quality standards before merging. The workflow file (.github/workflows/ci.yml
) automates tasks such as dependency installation, linting, formatting checks, vetting, building, and testing.
CI Workflow Breakdown
name: CI
on:
push:
branches: [ "**" ]
pull_request:
branches: [ "**" ]
This triggers the workflow on every push and pull request to any branch.
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
The job runs on Ubuntu and starts by checking out the repository.
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.23'
This sets up the Go environment with version 1.23.
- name: Cache Dependencies
uses: actions/cache@v3
id: gomod-cache
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('go.mod') }}
restore-keys: |
${{ runner.os }}-go-
Caches Go modules to speed up dependency installation in future runs.
- name: Install Dependencies
run: go mod tidy
Ensures the module dependencies are correct.
- name: Lint
run: |
go install golang.org/x/lint/golint@latest
golint ./...
Runs golint
to check for style violations.
- name: Format Check
run: |
unformatted=$(go fmt ./...)
if [ -n "$unformatted" ]; then
echo "The following files are not formatted:"
echo "$unformatted"
exit 1
fi
Ensures the code is properly formatted; fails if any files are not.
- name: Vet
run: go vet ./...
Runs go vet
to catch possible issues.
- name: Build
run: go build -v ./...
Builds the project.
- name: Test
run: go test -v ./...
Runs unit tests.
Problems and Solutions
Slow CI due to dependency installation
- Solution: Use caching to speed up builds.
Linting and formatting failures
- Solution: Developers should run
golint
andgo fmt
locally before pushing code.
Release Workflow
The Release workflow automates software versioning and packaging when a CI workflow completes successfully on the stable
branch.
Release Workflow Breakdown
name: Release
on:
workflow_run:
workflows: ["CI"]
types:
- completed
branches:
- stable
This workflow runs after the CI workflow finishes successfully on the stable
branch.
jobs:
release:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
Ensures that the workflow only runs if the CI pipeline passes.
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
Fetches the full repository history, which is needed for proper versioning.
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.23'
Sets up Go.
- name: Build for Multiple Platforms
run: |
mkdir -p releases
PLATFORMS=("windows/amd64" "linux/amd64" "linux/arm64" "darwin/amd64" "darwin/arm64")
for platform in "${PLATFORMS[@]}"; do
OS=${platform%/*}
ARCH=${platform#*/}
output_name="releases/tinyass-${OS}-${ARCH}"
if [ $OS = "windows" ]; then
output_name="$output_name.exe"
fi
GOOS=$OS GOARCH=$ARCH go build -o "$output_name" .
done
Compiles the project for multiple platforms, including Windows, Linux, and macOS. You can change the value of PLATFORM
for specific target OS.
- name: Generate Next Version
id: semver
uses: mathieudutour/github-tag-action@v6.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
dry_run: true
Determines the next semantic version without actually tagging the repository.
- name: Create Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.semver.outputs.new_tag }}
name: Release ${{ steps.semver.outputs.new_tag }}
body: ${{ steps.semver.outputs.changelog }}
files: |
releases/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Publishes a new release with the generated version and includes the compiled binaries.
Problems and Solutions
Incorrect versioning
- Solution: Ensure that
mathieudutour/github-tag-action
correctly increments versions based on commit history.
Build failures for different OS/architectures
- Solution: Verify platform compatibility and cross-compilation settings in Go.
Permissions issues
- Solution: Ensure
GITHUB_TOKEN
has the necessary permissions in GitHub repository settings.
Here is the full code at once
CI.yml
name: CI
on:
push:
branches: [ "**" ]
pull_request:
branches: [ "**" ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.23'
- name: Cache Dependencies
uses: actions/cache@v3
id: gomod-cache
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('go.mod') }}
restore-keys: |
${{ runner.os }}-go-
- name: Install Dependencies
run: go mod tidy
- name: Lint
run: |
go install golang.org/x/lint/golint@latest
golint ./...
- name: Format Check
run: |
unformatted=$(go fmt ./...)
if [ -n "$unformatted" ]; then
echo "The following files are not formatted:"
echo "$unformatted"
exit 1
else
echo "Code is formatted."
fi
- name: Vet
run: go vet ./...
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
Release.yml
name: Release
on:
workflow_run:
workflows: ["CI"]
types:
- completed
branches:
- stable
jobs:
release:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.23'
- name: Build for Multiple Platforms
run: |
mkdir -p releases
PLATFORMS=("windows/amd64" "linux/amd64" "linux/arm64" "darwin/amd64" "darwin/arm64")
for platform in "${PLATFORMS[@]}"; do
OS=${platform%/*}
ARCH=${platform#*/}
output_name="releases/tinyass-${OS}-${ARCH}"
if [ $OS = "windows" ]; then
output_name="$output_name.exe"
fi
GOOS=$OS GOARCH=$ARCH go build -o "$output_name" .
done
- name: Generate Next Version
id: semver
uses: mathieudutour/github-tag-action@v6.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
dry_run: true
- name: Create Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.semver.outputs.new_tag }}
name: Release ${{ steps.semver.outputs.new_tag }}
body: ${{ steps.semver.outputs.changelog }}
files: |
releases/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Conclusion
Using GitHub Actions for CI/CD ensures a seamless workflow for building, testing, and releasing software. The CI workflow validates code quality, while the release workflow automates software packaging and deployment. By following these structured steps, teams can streamline their development process and maintain high-quality releases efficiently.