Skip to content

Instantly share code, notes, and snippets.

@Segmentational
Last active January 25, 2025 02:22
Show Gist options
  • Save Segmentational/af73e877d4868d2ba8acab5bbefbe62c to your computer and use it in GitHub Desktop.
Save Segmentational/af73e877d4868d2ba8acab5bbefbe62c to your computer and use it in GitHub Desktop.
Go, Cobra, CLI, Homebrew, Makefile, GitLab, Go-Releaser
stages:
- Testing
- Version
- Build
- Registry
Unit-Tests:
stage: Testing
image: golang:1.22-alpine
script:
- go install gotest.tools/gotestsum@latest
- gotestsum --junitfile report.xml --format testname
only: [ tags ]
artifacts:
when: always
reports:
junit: report.xml
Version:
stage: Version
image: golang:1.22-alpine
only: [ tags ]
script:
- cat VERSION
needs:
- job: Unit-Tests
artifacts: false
artifacts:
paths:
- VERSION
Darwin:
stage: Build
image: golang:1.22-alpine
only: [ tags ]
before_script:
- apk add --no-cache make bash
script:
- make build-darwin
needs:
- job: Version
artifacts: true
artifacts:
paths:
- VERSION
- "*.tar.gz"
Linux:
stage: Build
image: golang:1.22-alpine
only: [ tags ]
before_script:
- apk add --no-cache make bash
script:
- make build-linux
needs:
- job: Version
artifacts: true
artifacts:
paths:
- VERSION
- "*.tar.gz"
Windows:
stage: Build
image: golang:1.22-alpine
only: [ tags ]
before_script:
- apk add --no-cache make bash zip
script:
- make build-windows
needs:
- job: Version
artifacts: true
artifacts:
paths:
- VERSION
- "*.zip"
Upload (Linux ARM64):
stage: Registry
image: curlimages/curl:latest
only: [ tags ]
variables:
EXT: "tar.gz"
PLATFORM: "linux"
ARCHITECTURE: "arm64"
script: |
curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file "${CI_PROJECT_NAME}-${PLATFORM}-${ARCHITECTURE}.${EXT}" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${CI_PROJECT_NAME}/$(cat VERSION)/${CI_PROJECT_NAME}-${PLATFORM}-${ARCHITECTURE}.${EXT}"
needs:
- job: Linux
artifacts: true
Upload Latest (Linux ARM64):
stage: Registry
image: curlimages/curl:latest
only: [ tags ]
variables:
EXT: "tar.gz"
PLATFORM: "linux"
ARCHITECTURE: "arm64"
script: |
curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file "${CI_PROJECT_NAME}-${PLATFORM}-${ARCHITECTURE}.${EXT}" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${CI_PROJECT_NAME}/latest/${CI_PROJECT_NAME}-${PLATFORM}-${ARCHITECTURE}.${EXT}"
needs:
- job: Linux
artifacts: true
Upload (Linux AMD64):
stage: Registry
image: curlimages/curl:latest
only: [ tags ]
variables:
EXT: "tar.gz"
PLATFORM: "linux"
ARCHITECTURE: "amd64"
script: |
curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file "${CI_PROJECT_NAME}-${PLATFORM}-${ARCHITECTURE}.${EXT}" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${CI_PROJECT_NAME}/$(cat VERSION)/${CI_PROJECT_NAME}-${PLATFORM}-${ARCHITECTURE}.${EXT}"
needs:
- job: Linux
artifacts: true
Upload Latest (Linux AMD64):
stage: Registry
image: curlimages/curl:latest
only: [ tags ]
variables:
EXT: "tar.gz"
PLATFORM: "linux"
ARCHITECTURE: "amd64"
script: |
curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file "${CI_PROJECT_NAME}-${PLATFORM}-${ARCHITECTURE}.${EXT}" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${CI_PROJECT_NAME}/latest/${CI_PROJECT_NAME}-${PLATFORM}-${ARCHITECTURE}.${EXT}"
needs:
- job: Linux
artifacts: true
Upload (Windows AMD64):
stage: Registry
image: curlimages/curl:latest
only: [ tags ]
variables:
EXT: "zip"
PLATFORM: "windows"
ARCHITECTURE: "amd64"
script: |
curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file "${CI_PROJECT_NAME}-${PLATFORM}-${ARCHITECTURE}.${EXT}" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${CI_PROJECT_NAME}/$(cat VERSION)/${CI_PROJECT_NAME}-${PLATFORM}-${ARCHITECTURE}.${EXT}"
needs:
- job: Windows
artifacts: true
Upload Latest (Windows AMD64):
stage: Registry
image: curlimages/curl:latest
only: [ tags ]
variables:
EXT: "zip"
PLATFORM: "windows"
ARCHITECTURE: "amd64"
script: |
curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file "${CI_PROJECT_NAME}-${PLATFORM}-${ARCHITECTURE}.${EXT}" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${CI_PROJECT_NAME}/latest/${CI_PROJECT_NAME}-${PLATFORM}-${ARCHITECTURE}.${EXT}"
needs:
- job: Windows
artifacts: true
version: 2
project_name: example-cli
env_files:
gitlab_token: ~/.config/goreleaser/gitlab_token
before:
hooks:
- go vet
- go mod tidy
- go mod vendor
- go generate ./...
builds:
- env:
- CGO_ENABLED=0
binary: example-cli
goos:
- linux
- windows
- darwin
ldflags:
- "-s"
- "-w"
- "-X main.version={{ .Tag }}"
- "-X main.commit={{ .Commit }}"
- "-X main.date={{ .Date }}"
archives:
- format: tar.gz
name_template: >-
{{ .ProjectName }}-
{{- .Os }}-
{{- if eq .Arch "amd64" }}x86-64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
format_overrides:
- goos: windows
format: zip
brews:
- name: example-cli
commit_author:
name: Jacob B. Sanders
commit_msg_template: "[Chore]: Brew Formula Bump ({{ .ProjectName }}), {{ .Tag }}"
caveats: |
\nExample LLC. - Internal Usage Only
Execute the following command for additional usage details:
\texample-cli --help\n
homepage: https://gitlab.com/example-organization/group-1/group-2/example-cli.git
description: "Internal CLI Utilities"
directory: Formula
skip_upload: false # true
download_strategy: GitDownloadStrategy
url_template: "[email protected]:example-organization/group-1/group-2/example-cli.git"
install: | # https://github.com/Homebrew/brew/blob/master/docs/Formula-Cookbook.md
ENV["GOPATH"] = buildpath
# bin_path = buildpath/"src/gitlab.com:example-organization/group-1/group-2/example-cli"
bin_path = buildpath/"src/gitlab.com:example-organization/group-1/group-2/example-cli.git"
bin_path.install Dir["*"]
cd bin_path do
system "go", "build", *std_go_args(ldflags: "-s -w -X=main.version={{ .Tag }} -X=main.commit={{ .Commit }} -X=main.date={{ .Date }}"), "-o", bin/"example-cli", "."
end
dependencies:
- name: git
os: mac
type: build
- name: go
version: "1.22"
type: build
- name: zsh
type: optional
- name: fish
type: optional
- name: bash
type: optional
conflicts:
- example-cli
repository:
branch: main
owner: example-organization/group-1/group-2
name: homebrew-taps
changelog:
use: gitlab
sort: asc
abbrev: 0
groups:
- title: "Features"
regexp: '^.*?(f|F)eature(\([[:word:]]+\))??!?(\ |)?(\:|\-)?(\ |).+$'
order: 0
- title: "Enhancements"
regexp: '^.*?(e|E)nhancement(\([[:word:]]+\))??!?(\ |)?(\:|\-)?(\ |).+$'
order: 1
- title: "Bug Fixes"
regexp: '^.*?(b|B)ug(\([[:word:]]+\))??!?(\ |)?(\:|\-)?(\ |).+$'
order: 2
- title: "Rollback"
regexp: '^.*?(r|R)evert(\([[:word:]]+\))??!?(\ |)?(\:|\-)?(\ |).+$'
order: 3
- title: "Documentation"
regexp: '^.*?(d|D)ocumentation(\([[:word:]]+\))??!?(\ |)?(\:|\-)?(\ |).+$'
order: 4
- title: "Refactor"
regexp: '^.*?(r|R)efactor(\([[:word:]]+\))??!?(\ |)?(\:|\-)?(\ |).+$'
order: 5
- title: "CI"
regexp: '^.*?(c|C)(i|I)(\([[:word:]]+\))??!?(\ |)?(\:|\-)?(\ |).+$'
order: 6
- title: "Chore"
regexp: '^.*?(c|C)hore(\([[:word:]]+\))??!?(\ |)?(\:|\-)?(\ |).+$'
order: 7
- title: Others
order: 999

Contributing Guide

Note

The following documentation is intended only for project maintainers & developers.

Initial Setup

Install pre-requisites, ci tools, and local development hooks:

make pre-requisites

Deployment

Makefile

  1. Prior to any releases, commit and publish all changes.

  2. Install goreleaser and test the deployment.

    make pre-requisites test-release
  3. Execute a release target - options include: patch-release, minor-release, major-release. These commands will publish various changes and perform a version bump.

    make patch-release

The default Makefile target includes the testing commands, and an additional target to patch-release.

SHELL := /usr/bin/env bash
# ====================================================================================
# Project Specific Globals
# ------------------------------------------------------------------------------------
#
# - It's assumed the $(name) is the same literal as the compiled binary.
# - Override the defaults if not available in a pipeline's environment variables.
#
name := example
ifdef CI_PROJECT_NAME
override name = $(CI_PROJECT_NAME)
endif
homebrew-tap := example-organization/example-cli
ifdef HOMEBREW_TAP
override homebrew-tap = $(HOMEBREW_TAP)
endif
homebrew-tap-repository := gitlab.com:example-organization/group-1/group-2/homebrew-taps.git
ifdef HOMEBREW_TAP_REPOSITORY
override homebrew-tap-repository = $(HOMEBREW_TAP_REPOSITORY)
endif
type := patch
ifdef RELEASE_TYPE
override type = RELEASE_TYPE
endif
# ====================================================================================
# Colors
# ------------------------------------------------------------------------------------
black := $(shell printf "\033[30m")
black-bold := $(shell printf "\033[30;1m")
red := $(shell printf "\033[31m")
red-bold := $(shell printf "\033[31;1m")
green := $(shell printf "\033[32m")
green-bold := $(shell printf "\033[32;1m")
yellow := $(shell printf "\033[33m")
yellow-bold := $(shell printf "\033[33;1m")
blue := $(shell printf "\033[34m")
blue-bold := $(shell printf "\033[34;1m")
magenta := $(shell printf "\033[35m")
magenta-bold := $(shell printf "\033[35;1m")
cyan := $(shell printf "\033[36m")
cyan-bold := $(shell printf "\033[36;1m")
white := $(shell printf "\033[37m")
white-bold := $(shell printf "\033[37;1m")
reset := $(shell printf "\033[0m")
# ====================================================================================
# Logger
# ------------------------------------------------------------------------------------
time-long = $(date +%Y-%m-%d' '%H:%M:%S)
time-short = $(date +%H:%M:%S)
time = $(time-short)
information = echo $(time) $(blue)[ DEBUG ]$(reset)
warning = echo $(time) $(yellow)[ WARNING ]$(reset)
exception = echo $(time) $(red)[ ERROR ]$(reset)
complete = echo $(time) $(green)[ COMPLETE ]$(reset)
fail = (echo $(time) $(red)[ FAILURE ]$(reset) && false)
# ====================================================================================
# Utility Command(s)
# ------------------------------------------------------------------------------------
url = $(shell git config --get remote.origin.url | sed -r 's/.*(\@|\/\/)(.*)(\:|\/)([^:\/]*)\/([^\/\.]*)\.git/https:\/\/\2\/\4\/\5/')
repository = $(shell basename -s .git $(shell git config --get remote.origin.url))
organization = $(shell git remote -v | grep "(fetch)" | sed 's/.*\/\([^ ]*\)\/.*/\1/')
package = $(shell printf "github.com/%s/%s" "$(organization)" "$(repository)")
version = $(shell [ -f VERSION ] && head VERSION || echo "0.0.0")
tag = $(shell echo "v$(version)")
major = $(shell echo $(version) | sed "s/^\([0-9]*\).*/\1/")
minor = $(shell echo $(version) | sed "s/[0-9]*\.\([0-9]*\).*/\1/")
patch = $(shell echo $(version) | sed "s/[0-9]*\.[0-9]*\.\([0-9]*\).*/\1/")
zero = $(shell printf "%s" "0")
major-upgrade = $(shell expr $(major) + 1).$(zero).$(zero)
minor-upgrade = $(major).$(shell expr $(minor) + 1).$(zero)
patch-upgrade = $(major).$(minor).$(shell expr $(patch) + 1)
dirty = $(shell git diff --quiet)
dirty-contents = $(shell git diff --shortstat 2>/dev/null 2>/dev/null | tail -n1)
# ====================================================================================
# Build Command(s)
# ------------------------------------------------------------------------------------
compile = go build --mod "vendor" --ldflags "-s -w -X=main.version=$(tag) -X=main.date=$(shell date +%Y-%m-%d:%H-%M-%S) -X=main.source=false" -o "./build/$(name)-$(GOOS)-$(GOARCH)/$(name)"
compile-windows = go build --mod "vendor" --ldflags "-s -w -X=main.version=$(tag) -X=main.date=$(shell date +%Y-%m-%d:%H-%M-%S) -X=main.source=false" -o "./build/$(name)-$(GOOS)-$(GOARCH)/$(name).exe"
archive = tar -czvf "$(name)-$(GOOS)-$(GOARCH).tar.gz" -C "./build/$(name)-$(GOOS)-$(GOARCH)" .
archive-windows = cd "./build/$(name)-$(GOOS)-$(GOARCH)" && zip -r "../../$(name)-$(GOOS)-$(GOARCH).zip" "." && cd -
distribute = mkdir -p distribution && mv *.tar.gz distribution
distribute-windows = mkdir -p distribution && mv *.zip distribution
# ====================================================================================
# Default
# ------------------------------------------------------------------------------------
all: pre-requisites test-release $(type)-release
# ====================================================================================
# Pre-Requisites
# ------------------------------------------------------------------------------------
pre-requisites:
@command -v brew 2>&1> /dev/null || bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
@command -v goreleaser 2>&1> /dev/null || brew install goreleaser
@command -v pre-commit 2>&1> /dev/null || brew install pre-commit
@command -v go 2>&1> /dev/null || brew install go
# ====================================================================================
# Utilities
# ------------------------------------------------------------------------------------
escape-hatch:
@rm -rf ./.upstreams
@sed -i -e "s/using: GitDownloadStrategy/using: GitDownloadStrategy, tag: \"$(tag)\"/g" ./dist/homebrew/Formula/$(name).rb
@mkdir -p .upstreams
@git clone $(homebrew-tap-repository) ./.upstreams/homebrew-taps
@rm -f ./.upstreams/homebrew-taps/Formula/$(name).rb
@cp -f ./dist/homebrew/Formula/$(name).rb ./.upstreams/homebrew-taps/Formula/$(name).rb
@cd ./.upstreams/homebrew-taps && git add ./Formula/$(name).rb && git commit -m "[Chore] - Overwrote URL + Tag" && git push -u origin main
@cd "$(git rev-parse --show-toplevel)"
@rm -rf ./.upstreams
# ====================================================================================
# CI-CD Build Targets
# ------------------------------------------------------------------------------------
build: build-darwin build-linux build-windows
# (Darwin) Build Targets
build-darwin: build-darwin-amd64 build-darwin-arm64
build-darwin-arm64: export GOOS := darwin
build-darwin-arm64: export GOARCH := arm64
build-darwin-arm64:
$(compile)
$(archive)
build-darwin-amd64: export GOOS := darwin
build-darwin-amd64: export GOARCH := amd64
build-darwin-amd64:
$(compile)
$(archive)
# (Linux) Build Targets
build-linux: build-linux-amd64 build-linux-arm64 build-linux-386
build-linux-arm64: export GOOS := linux
build-linux-arm64: export GOARCH := arm64
build-linux-arm64:
$(compile)
$(archive)
build-linux-amd64: export GOOS := linux
build-linux-amd64: export GOARCH := amd64
build-linux-amd64:
$(compile)
$(archive)
build-linux-386: export GOOS := linux
build-linux-386: export GOARCH := 386
build-linux-386:
$(compile)
$(archive)
# (Windows) Build Targets
build-windows: build-windows-amd64 build-windows-386
build-windows-amd64: export GOOS := windows
build-windows-amd64: export GOARCH := amd64
build-windows-amd64:
$(compile-windows)
$(archive-windows)
build-windows-386: export GOOS := windows
build-windows-386: export GOARCH := 386
build-windows-386:
$(compile-windows)
$(archive-windows)
# Additional Build Targets
clean:
rm *.tar.gz && rm *.zip
# ====================================================================================
# Local Development & Package-Specific Targets
# ------------------------------------------------------------------------------------
uninstall:
@rm -rf /opt/homebrew/etc/gitconfig
@brew uninstall $(name) --force || true
@brew untap $(homebrew-tap) --force || true
install: uninstall
@brew tap $(homebrew-tap) $(homebrew-tap-repository) --force-auto-update --force
@brew update
@brew install $(name)
tidy:
@go mod tidy && go mod vendor
test: tidy
@go test ./...
test-release:
@goreleaser release --snapshot --clean
# (Patch) Release Targets
bump-patch: test
@if ! git diff --quiet --exit-code; then \
echo "$(red-bold)Dirty Working Tree$(reset) - Commit Changes and Try Again"; \
exit 1; \
else \
echo "$(patch-upgrade)" > VERSION; \
fi
commit-patch: bump-patch
@echo "$(blue-bold)Tag-Release (Patch)$(reset): \"$(yellow-bold)$(package)$(reset)\" - $(white-bold)$(version)$(reset)"
@git add VERSION
@git commit --message "Chore (Patch) - Tag Release - $(version)"
@git push --set-upstream origin main
@git tag "v$(version)"
@git push origin "v$(version)"
@echo "$(green-bold)Published Tag$(reset): $(version)"
release-patch: commit-patch
@echo "$(blue-bold)Deployment (Patch)$(reset): \"$(yellow-bold)$(package)$(reset)\" - $(white-bold)$(version)$(reset)"
@goreleaser release --clean
@echo "$(green-bold)Successful$(reset): $(version)"
patch-release: pre-requisites release-patch escape-hatch install
# (Minor) Release Targets
bump-minor: test
@if ! git diff --quiet --exit-code; then \
echo "$(red-bold)Dirty Working Tree$(reset) - Commit Changes and Try Again"; \
exit 1; \
else \
echo "$(minor-upgrade)" > VERSION; \
fi
commit-minor: bump-minor
@echo "$(blue-bold)Tag-Release (Minor)$(reset): \"$(yellow-bold)$(package)$(reset)\" - $(white-bold)$(version)$(reset)"
@git add VERSION
@git commit --message "Chore (Minor) - Tag Release - $(version)"
@git push --set-upstream origin main
@git tag "v$(version)"
@git push origin "v$(version)"
@echo "$(green-bold)Published Tag$(reset): $(version)"
release-minor: commit-minor
@echo "$(blue-bold)Deployment (Minor)$(reset): \"$(yellow-bold)$(package)$(reset)\" - $(white-bold)$(version)$(reset)"
@goreleaser release --clean
@echo "$(green-bold)Successful$(reset): $(version)"
minor-release: pre-requisites release-minor escape-hatch install
# (Major) Release Targets
bump-major: test
@if ! git diff --quiet --exit-code; then \
echo "$(red-bold)Dirty Working Tree$(reset) - Commit Changes and Try Again"; \
exit 1; \
else \
echo "$(major-upgrade)" > VERSION; \
fi
commit-major: bump-major
@echo "$(blue-bold)Tag-Release (Major)$(reset): \"$(yellow-bold)$(package)$(reset)\" - $(white-bold)$(version)$(reset)"
@git add VERSION
@git commit --message "Chore (Major) - Tag Release - $(version)"
@git push --set-upstream origin main
@git tag "v$(version)"
@git push origin "v$(version)"
@echo "$(green-bold)Published Tag$(reset): $(version)"
release-major: commit-major
@echo "$(blue-bold)Deployment (Major)$(reset): \"$(yellow-bold)$(package)$(reset)\" - $(white-bold)$(version)$(reset)"
@goreleaser release --clean
@echo "$(green-bold)Successful$(reset): $(version)"
major-release: pre-requisites release-major escape-hatch install
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment