Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

makefile: add command to generate git release tags #720

Merged
merged 8 commits into from
Feb 1, 2024
20 changes: 16 additions & 4 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,28 @@ jobs:
with:
fetch-depth: 0

- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV

- name: Validate release tag ${{ env.RELEASE_VERSION }}
run: |
expected_tag=$(./scripts/get-git-tag-name.sh version.go)
actual_tag=${{ env.RELEASE_VERSION }}

if [ "$actual_tag" = "$expected_tag" ]; then
echo "Git tag release string is as expected."
else
echo "Error: Versions are not equal. Actual: $actual_tag, Expected: $expected_tag"
exit 1
fi

- name: setup go ${{ env.GO_VERSION }}
uses: actions/setup-go@v2
with:
go-version: '${{ env.GO_VERSION }}'

- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV

- name: build release for all architectures
run: SKIP_VERSION_CHECK=1 make release tag=${{ env.RELEASE_VERSION }}
run: make release tag=${{ env.RELEASE_VERSION }}
ffranr marked this conversation as resolved.
Show resolved Hide resolved

- name: Create Release
uses: lightninglabs/gh-actions/action-gh-release@2021.01.25.00
Expand Down
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ GOACC_BIN := $(GO_BIN)/go-acc
GOIMPORTS_BIN := $(GO_BIN)/gosimports
MIGRATE_BIN := $(GO_BIN)/migrate

# VERSION_GO_FILE is the golang file which defines the current project version.
VERSION_GO_FILE := "version.go"

COMMIT := $(shell git describe --tags --dirty)

GOBUILD := GOEXPERIMENT=loopvar GO111MODULE=on go build -v
Expand Down Expand Up @@ -115,6 +118,17 @@ release:
$(VERSION_CHECK)
./scripts/release.sh build-release "$(VERSION_TAG)" "$(BUILD_SYSTEM)" "$(RELEASE_TAGS)" "$(RELEASE_LDFLAGS)"

release-tag:
@$(call print, "Adding release tag.")

tag=$$(./scripts/get-git-tag-name.sh ${VERSION_GO_FILE}); \
exit_status=$$?; \
if [ $$exit_status -ne 0 ]; then \
echo "Script encountered an error with exit status $$exit_status."; \
fi; \
echo "Adding git tag: $$tag"; \
git tag -as -m "Tag generated using command \`make release-tag\`." "$$tag";

docker-release:
@$(call print, "Building release helper docker image.")
if [ "$(tag)" = "" ]; then echo "Must specify tag=<commit_or_tag>!"; exit 1; fi
Expand Down
14 changes: 7 additions & 7 deletions make/release_flags.mk
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# One can either specify a git tag as the version suffix or one that is
# generated from the current date.
VERSION_TAG = $(shell date +%Y%m%d)-01
VERSION_CHECK = @$(call print, "Building master with date version tag")

ifneq ($(tag),)
VERSION_TAG = $(tag)
VERSION_CHECK = ./scripts/release.sh check-tag "$(VERSION_TAG)" "$(VERSION_GO_FILE)"
endif

DOCKER_RELEASE_HELPER = docker run \
-it \
--rm \
Expand All @@ -22,13 +29,6 @@ windows-amd64

RELEASE_TAGS = monitoring

# One can either specify a git tag as the version suffix or one is generated
# from the current date.
ifneq ($(tag),)
VERSION_TAG = $(tag)
VERSION_CHECK = ./scripts/release.sh check-tag "$(VERSION_TAG)"
endif

# By default we will build all systems. But with the 'sys' tag, a specific
# system can be specified. This is useful to release for a subset of
# systems/architectures.
Expand Down
77 changes: 77 additions & 0 deletions scripts/get-git-tag-name.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/bin/bash

# This script derives a git tag name from the version fields found in a given Go
# file. It also checks if the derived git tag name is a valid SemVer compliant
# version string.

# get_git_tag_name reads the version fields from the given file and then
# constructs and returns a git tag name.
get_git_tag_name() {
local file_path="$1"

# Check if the file exists
if [ ! -f "$file_path" ]; then
echo "Error: File not found at $file_path" >&2
exit 1
fi

# Read and parse the version fields. We interpret these fields using regex
# matching which effectively serves as a basic sanity check.
local app_major
app_major=$(grep -oP 'AppMajor\s*uint\s*=\s*\K\d+' "$file_path")

local app_minor
app_minor=$(grep -oP 'AppMinor\s*uint\s*=\s*\K\d+' "$file_path")

local app_patch
app_patch=$(grep -oP 'AppPatch\s*uint\s*=\s*\K\d+' "$file_path")

local app_status
app_status=$(grep -oP 'AppStatus\s*=\s*"\K([a-z]*)' "$file_path")

local app_pre_release
app_pre_release=$(grep -oP 'AppPreRelease\s*=\s*"\K([a-z0-9]*)' "$file_path")

# Parse the GitTagIncludeStatus field.
local git_tag_include_status
git_tag_include_status=false

if grep -q 'GitTagIncludeStatus = true' "$file_path"; then
git_tag_include_status=true
elif grep -q 'GitTagIncludeStatus = false' "$file_path"; then
git_tag_include_status=false
else
echo "Error: GitTagIncludeStatus is not present in the Go version file."
exit 1
fi

# Construct the git tag name with conditional inclusion of app_status and
# app_pre_release.
tag_name="v${app_major}.${app_minor}.${app_patch}"

# Append app_status if git_tag_include_status is true and app_status if
# specified.
if [ "$git_tag_include_status" = true ] && [ -n "$app_status" ]; then
tag_name+="-${app_status}"

# Append app_pre_release if specified.
if [ -n "$app_pre_release" ]; then
tag_name+=".${app_pre_release}"
fi
else
# If the app_status field is not specified, then append
# app_pre_release (if specified) using a dash prefix.
if [ -n "$app_pre_release" ]; then
tag_name+="-${app_pre_release}"
fi
fi

echo "$tag_name"
}

file_path="$1"
echo "Reading version fields from file: $file_path" >&2
tag_name=$(get_git_tag_name "$file_path")
echo "Derived git tag name: $tag_name" >&2

echo "$tag_name"
34 changes: 10 additions & 24 deletions scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,10 @@ function red() {

# check_tag_correct makes sure the given git tag is checked out and the git tree
# is not dirty.
# arguments: <version-tag>
# arguments: <version-tag> <version-file-path>
function check_tag_correct() {
local tag=$1
local version_file_path=$2

# For automated builds we can skip this check as they will only be triggered
# on tags.
Expand All @@ -102,31 +103,16 @@ function check_tag_correct() {
echo "Tag $tag checked out. Git commit: $commit_hash"
fi

# Build tapd to extract version.
go build ${PKG}/cmd/tapd
# Ensure that the git tag matches the version string derived from the version
# file.
local expected_tag
expected_tag=$(./scripts/get-git-tag-name.sh "$version_file_path")

# Extract version command output.
tapd_version_output=$(./tapd --version)

# Use a regex to isolate the version string.
if [[ $tapd_version_output =~ $TAPD_VERSION_REGEX ]]; then
# Prepend 'v' to match git tag naming scheme.
tapd_version="v${BASH_REMATCH[1]}"
green "version: $tapd_version"

# If the tapd reported version contains a suffix, remove it, so we can match
# the tag properly.
# shellcheck disable=SC2001
tapd_version=$(echo "$tapd_version" | sed -e 's/-\(alpha\|beta\)\(\.rc[0-9]\+\)\?//g')

# Match git tag with tapd version.
if [[ $tag != "${tapd_version}" ]]; then
red "tapd version $tapd_version does not match tag $tag"
exit 1
fi
else
red "malformed tapd version output"
if [[ $tag != "$expected_tag" ]]; then
red "Error: tag $tag does not match git tag version string derived from $version_file_path"
exit 1
else
green "tag $tag matches git tag version string derived from $version_file_path"
fi
}

Expand Down
72 changes: 56 additions & 16 deletions version.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ var (
GoVersion string
)

// semanticAlphabet is the set of characters that are permitted for use in an
// AppPreRelease.
const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-."
// versionFieldsAlphabet is the set of characters that are permitted for use in
// a version string field.
const versionFieldsAlphabet = "0123456789abcdefghijklmnopqrstuvwxyz"

// These constants define the application version and follow the semantic
// versioning 2.0.0 spec (http://semver.org/).
Expand All @@ -47,9 +47,21 @@ const (
// AppPatch defines the application patch for this binary.
AppPatch uint = 2

// AppPreRelease MUST only contain characters from semanticAlphabet
// per the semantic versioning spec.
AppPreRelease = "alpha"
// AppStatus defines the release status of this binary (e.g. beta).
AppStatus = "alpha"

// AppPreRelease defines the pre-release version of this binary.
// It MUST only contain characters from the semantic versioning spec.
AppPreRelease = ""

// GitTagIncludeStatus indicates whether the status should be included
// in the git tag name.
//
// Including the app version status in the git tag may be problematic
// for golang projects when importing them as dependencies. We therefore
// include this flag to allow toggling the status on and off in a
// standardised way across our projects.
ffranr marked this conversation as resolved.
Show resolved Hide resolved
GitTagIncludeStatus = false

// defaultAgentName is the default name of the software that is added as
// the first part of the user agent string.
Expand All @@ -66,8 +78,10 @@ var agentName = defaultAgentName
// the software tapd is bundled in (for example LiT). This function panics if
// the agent name contains characters outside of the allowed semantic alphabet.
func SetAgentName(newAgentName string) {
agentNameAlphabet := versionFieldsAlphabet + "-. "

for _, r := range newAgentName {
if !strings.ContainsRune(semanticAlphabet, r) {
if !strings.ContainsRune(agentNameAlphabet, r) {
panic(fmt.Errorf("rune: %v is not in the semantic "+
"alphabet", r))
}
Expand All @@ -81,7 +95,7 @@ func SetAgentName(newAgentName string) {
func UserAgent(initiator string) string {
// We'll only allow "safe" characters in the initiator portion of the
// user agent string and spaces only if surrounded by other characters.
initiatorAlphabet := semanticAlphabet + ". "
initiatorAlphabet := versionFieldsAlphabet + "-. "
cleanInitiator := normalizeVerString(
strings.TrimSpace(initiator), initiatorAlphabet,
)
Expand All @@ -104,12 +118,22 @@ func UserAgent(initiator string) string {
}

func init() {
// Assert that AppStatus is valid according to the semantic versioning
// guidelines for pre-release version and build metadata strings. In
// particular, it MUST only contain characters in versionFieldsAlphabet.
for _, r := range AppStatus {
if !strings.ContainsRune(versionFieldsAlphabet, r) {
panic(fmt.Errorf("rune: %v is not in the semantic "+
"alphabet", r))
}
}

// Assert that AppPreRelease is valid according to the semantic
// versioning guidelines for pre-release version and build metadata
// strings. In particular it MUST only contain characters in
// semanticAlphabet.
// strings. In particular, it MUST only contain characters in
// versionFieldsAlphabet.
for _, r := range AppPreRelease {
if !strings.ContainsRune(semanticAlphabet, r) {
if !strings.ContainsRune(versionFieldsAlphabet, r) {
panic(fmt.Errorf("rune: %v is not in the semantic "+
"alphabet", r))
}
Expand Down Expand Up @@ -162,12 +186,28 @@ func semanticVersion() string {
// Start with the major, minor, and patch versions.
version := fmt.Sprintf("%d.%d.%d", AppMajor, AppMinor, AppPatch)

// Append pre-release version if there is one. The hyphen called for
// by the semantic versioning spec is automatically appended and should
// not be contained in the pre-release string. The pre-release version
// If defined, we will now sanitise the release status string. The
// hyphen called for by the semantic versioning spec is automatically
// appended and should not be contained in the status string. The status
// is not appended if it contains invalid characters.
preRelease := normalizeVerString(AppPreRelease, semanticAlphabet)
if preRelease != "" {
appStatus := normalizeVerString(AppStatus, versionFieldsAlphabet)

// If defined, we will now sanitise the pre-release version string. The
// hyphen called for by the semantic versioning spec is automatically
// appended and should not be contained in the pre-release string.
// The pre-release version is not appended if it contains invalid
// characters.
preRelease := normalizeVerString(AppPreRelease, versionFieldsAlphabet)

// Append any status and pre-release strings to the version string.
switch {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bit confused on this switch, do we ever want appStatus without preRelease? From the tapd tags it looks like we only do both or neither, ex. v0.3.2, v0.3.0-alpha.rc3, but no v0.3.0-alpha nor v0.3.0-rc3.

Or is this to also match a different pattern used for a different project?

Copy link
Contributor Author

@ffranr ffranr Jan 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is this to also match a different pattern used for a different project?

I think we should make sure that possibility is available to us. I don't think we have a strict org wide rule around the use of status without pre-release. For instance, v0.3.0-alpha seems like a reasonable tag to me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

K, I thought leaving out the rc1 suffix when using alpha or beta is what caused issues previously? I don't recall exactly which tag caused those though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What caused the issue previously was leaving out -alpha while just using an rc tag (e.g. 0.3.2-rc2), because then the version check of the release script didn't work as it just did a substring comparison with what the app version string reported (v0.3.2-alpha.rc2).
So I think we should also update the check_tag_correct function in scripts/release.sh to use the new get-git-tag-name.sh script and compare it to the checked out tag.

And to answer your other question, just having the status is what we want for non-RC releases in all other projects than this one (e.g. lnd).

case appStatus != "" && preRelease != "":
version = fmt.Sprintf(
"%s-%s.%s", version, appStatus, preRelease,
)
case appStatus != "":
version = fmt.Sprintf("%s-%s", version, appStatus)
case preRelease != "":
version = fmt.Sprintf("%s-%s", version, preRelease)
}

Expand Down
Loading