diff --git a/version/.gitignore b/version/.gitignore index 60df6e2d6..58d19bfc2 100644 --- a/version/.gitignore +++ b/version/.gitignore @@ -3,6 +3,7 @@ long.txt short.txt gitcommit.txt extragitcommit.txt +version-info.sh version.h version.xcconfig ver.go diff --git a/version/describe.sh b/version/describe.sh deleted file mode 100755 index 5e12aa266..000000000 --- a/version/describe.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh -# -# Constructs a "git describe" compatible version number by using the -# information in the VERSION file, rather than git tags. - -set -eu - -dir="$(dirname $0)" -verfile="$dir/../VERSION" - -read -r version hash <"$verfile" - -if [ -z "$hash" ]; then - # If no explicit hash was given, use the last time the version - # file changed as the "origin" hash for this version. - hash="$(git rev-list --max-count=1 HEAD -- $verfile)" -fi - -if [ -z "$hash" ]; then - echo "Couldn't find base git hash for version '$version'" >2 - exit 1 -fi - -head="$(git rev-parse --short=9 HEAD)" -changecount="$(git rev-list ${hash}..HEAD | wc -l)" -echo "v${version}-${changecount}-g${head}" diff --git a/version/extragitcommit.txt.do b/version/extragitcommit.txt.do deleted file mode 100644 index d494fd04f..000000000 --- a/version/extragitcommit.txt.do +++ /dev/null @@ -1,6 +0,0 @@ -# --abbrev=200 is an arbitrary large number to capture the entire git -# hash without trying to compact it. -commit=$(cd ../.. && git describe --dirty --exclude "*" --always --abbrev=200) -echo "$commit" >$3 -redo-always -redo-stamp <$3 diff --git a/version/gitcommit.txt.do b/version/gitcommit.txt.do deleted file mode 100644 index f943fa8bf..000000000 --- a/version/gitcommit.txt.do +++ /dev/null @@ -1,6 +0,0 @@ -# --abbrev=200 is an arbitrary large number to capture the entire git -# hash without trying to compact it. -commit=$(git describe --dirty --exclude "*" --always --abbrev=200) -echo "$commit" >$3 -redo-always -redo-stamp <$3 diff --git a/version/long.txt.do b/version/long.txt.do deleted file mode 100644 index 96bd18377..000000000 --- a/version/long.txt.do +++ /dev/null @@ -1,5 +0,0 @@ -redo-ifchange mkversion.sh describe.txt extragitcommit.txt -read -r describe $3 diff --git a/version/mkversion.sh b/version/mkversion.sh deleted file mode 100755 index 84772bc20..000000000 --- a/version/mkversion.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/bin/sh - -set -eu - -mode=$1 -describe=$2 -other=$3 - -# Git describe output overall looks like -# MAJOR.MINOR.PATCH-NUMCOMMITS-GITHASH. Depending on the tag being -# described and the state of the repo, ver can be missing PATCH, -# NUMCOMMITS, or both. -# -# Valid values look like: 1.2.3-1234-abcdef, 0.98-1234-abcdef, -# 1.0.0-abcdef, 0.99-abcdef. -ver="${describe#v}" -stem="${ver%%-*}" # Just the semver-ish bit e.g. 1.2.3, 0.98 -suffix="${ver#$stem}" # The rest e.g. -23-abcdef, -abcdef - -# Normalize the stem into a full major.minor.patch semver. We might -# not use all those pieces depending on what kind of version we're -# making, but it's good to have them all on hand. -case "$stem" in - *.*.*) - # Full SemVer, nothing to do - stem="$stem" - ;; - *.*) - # Old style major.minor, add a .0 - stem="${stem}.0" - ;; - *) - echo "Unparseable version $stem" >&2 - exit 1 - ;; -esac -major=$(echo "$stem" | cut -f1 -d.) -minor=$(echo "$stem" | cut -f2 -d.) -patch=$(echo "$stem" | cut -f3 -d.) - -# Extract the change count and git ID from the suffix. -case "$suffix" in - -*-*) - # Has both a change count and a commit hash. - changecount=$(echo "$suffix" | cut -f2 -d-) - githash=$(echo "$suffix" | cut -f3 -d-) - ;; - -*) - # Git hash only, change count is zero. - changecount="0" - githash=$(echo "$suffix" | cut -f2 -d-) - ;; - *) - echo "Unparseable version suffix $suffix" >&2 - exit 1 - ;; -esac - -# The git hash is of the form "gCOMMITHASH". We want to replace the -# 'g' with a 't', for "tailscale", to convey that it's specifically -# the commit hash of the tailscale repo. -if [ -n "$githash" ]; then - # POSIX shell doesn't understand ${foo:1:9} syntax, gaaah. - githash="$(echo $githash | cut -c2-10)" - githash="t${githash}" -fi - -# "other" is a second git commit hash for another repository used to -# build the Tailscale code. In practice it's either the commit hash in -# the Android repository, or the commit hash of Tailscale's -# proprietary repository (which pins a bunch things like build scripts -# used and Go toolchain version). -if [ -n "$other" ]; then - other="$(echo $other | cut -c1-9)" - other="-g${other}" -fi - -# Validate that the version data makes sense. Rules: -# - Odd number minors are unstable. Patch must be 0, and gets -# replaced by changecount. -# - Even number minors are stable. Changecount must be 0, and -# gets removed. -# -# After this section, we only use major/minor/patch, which have been -# tweaked as needed. -if expr "$minor" : "[0-9]*[13579]$" >/dev/null; then - # Unstable - if [ "$patch" != "0" ]; then - # This is a fatal error, because a non-zero patch number - # indicates that we created an unstable git tag in violation - # of our versioning policy, and we want to blow up loudly to - # get that fixed. - echo "Unstable release $describe has a non-zero patch number, which is not allowed" >&2 - exit 1 - fi - patch="$changecount" -else - # Stable - if [ "$changecount" != "0" ]; then - # This is a commit that's sitting between two stable - # releases. We never want to release such a commit to the - # pbulic, but it's useful to be able to build it for - # debugging. Just force the version to 0.0.0, so that we're - # forced to rely on the git commit hash. - major="0" - minor="0" - patch="0" - fi -fi - -if [ "$minor" -eq 1 ]; then - # Hack for 1.1: add 1000 to the patch number, so that builds that - # use the OSS change count order after the builds that used the - # proprietary repo's changecount. Otherwise, the version numbers - # would go backwards and things would be unhappy. - patch=$((patch + 1000)) -fi - -case "$1" in - long) - echo "${major}.${minor}.${patch}-${githash}${other}" - ;; - short) - echo "${major}.${minor}.${patch}" - ;; - xcode) - # CFBundleShortVersionString: the "short name" used in the App - # Store. eg. 0.92.98 - echo "VERSION_NAME = ${major}.${minor}.${patch}" - # CFBundleVersion: the build number. Needs to be 3 numeric - # sections that increment for each release according to SemVer - # rules. - # - # We start counting at 100 because we submitted using raw - # build numbers before, and Apple doesn't let you start over. - # e.g. 0.98.3 -> 100.98.3 - echo "VERSION_ID = $((major + 100)).${minor}.${patch}" - ;; -esac diff --git a/version/mkversion_test.go b/version/mkversion_test.go index e209b4756..f4ac1fe69 100644 --- a/version/mkversion_test.go +++ b/version/mkversion_test.go @@ -8,22 +8,21 @@ "fmt" "os/exec" "runtime" + "strconv" "strings" "testing" + + "github.com/google/go-cmp/cmp" ) -func xcode(short, long string) string { - return fmt.Sprintf("VERSION_NAME = %s\nVERSION_ID = %s", short, long) -} - -func mkversion(t *testing.T, mode, describe, other string) (string, bool) { +func mkversion(t *testing.T, gitHash, otherHash string, major, minor, patch, changeCount int) (string, bool) { t.Helper() - bs, err := exec.Command("./mkversion.sh", mode, describe, other).CombinedOutput() + bs, err := exec.Command("./version.sh", gitHash, otherHash, strconv.Itoa(major), strconv.Itoa(minor), strconv.Itoa(patch), strconv.Itoa(changeCount)).CombinedOutput() + out := strings.TrimSpace(string(bs)) if err != nil { - t.Logf("mkversion.sh output: %s", string(bs)) - return "", false + return out, false } - return strings.TrimSpace(string(bs)), true + return out, true } func TestMkversion(t *testing.T) { @@ -31,50 +30,73 @@ func TestMkversion(t *testing.T) { t.Skip("skip test on Windows, because there is no shell to execute mkversion.sh.") } tests := []struct { - describe string - other string - ok bool - long string - short string - xcode string + gitHash, otherHash string + major, minor, patch, changeCount int + want string }{ - {"v0.98-gabcdef", "", true, "0.98.0-tabcdef", "0.98.0", xcode("0.98.0", "100.98.0")}, - {"v0.98.1-gabcdef", "", true, "0.98.1-tabcdef", "0.98.1", xcode("0.98.1", "100.98.1")}, - {"v1.1.0-37-gabcdef", "", true, "1.1.1037-tabcdef", "1.1.1037", xcode("1.1.1037", "101.1.1037")}, - {"v1.2.9-gabcdef", "", true, "1.2.9-tabcdef", "1.2.9", xcode("1.2.9", "101.2.9")}, - {"v1.2.9-0-gabcdef", "", true, "1.2.9-tabcdef", "1.2.9", xcode("1.2.9", "101.2.9")}, - {"v1.15.0-129-gabcdef", "", true, "1.15.129-tabcdef", "1.15.129", xcode("1.15.129", "101.15.129")}, - - {"v0.98-123-gabcdef", "", true, "0.0.0-tabcdef", "0.0.0", xcode("0.0.0", "100.0.0")}, - {"v1.0.0-37-gabcdef", "", true, "0.0.0-tabcdef", "0.0.0", xcode("0.0.0", "100.0.0")}, - {"v1.1.0-129-gabcdef", "0123456789abcdef0123456789abcdef", true, "1.1.1129-tabcdef-g012345678", "1.1.1129", xcode("1.1.1129", "101.1.1129")}, - {"v0.99.5-0-gabcdef", "", false, "", "", ""}, // unstable, patch not allowed - {"v0.99.5-123-gabcdef", "", false, "", "", ""}, // unstable, patch not allowed - {"v1-gabcdef", "", false, "", "", ""}, // bad semver - {"v1.0", "", false, "", "", ""}, // missing suffix + {"abcdef", "", 0, 98, 0, 0, ` + VERSION_SHORT="0.98.0" + VERSION_LONG="0.98.0-tabcdef" + VERSION_GIT_HASH="abcdef" + VERSION_EXTRA_HASH="" + VERSION_XCODE="100.98.0" + VERSION_WINRES="0,98,0,0"`}, + {"abcdef", "", 0, 98, 1, 0, ` + VERSION_SHORT="0.98.1" + VERSION_LONG="0.98.1-tabcdef" + VERSION_GIT_HASH="abcdef" + VERSION_EXTRA_HASH="" + VERSION_XCODE="100.98.1" + VERSION_WINRES="0,98,1,0"`}, + {"abcdef", "", 1, 1, 0, 37, ` + VERSION_SHORT="1.1.1037" + VERSION_LONG="1.1.1037-tabcdef" + VERSION_GIT_HASH="abcdef" + VERSION_EXTRA_HASH="" + VERSION_XCODE="101.1.1037" + VERSION_WINRES="1,1,1037,0"`}, + {"abcdef", "", 1, 2, 9, 0, ` + VERSION_SHORT="1.2.9" + VERSION_LONG="1.2.9-tabcdef" + VERSION_GIT_HASH="abcdef" + VERSION_EXTRA_HASH="" + VERSION_XCODE="101.2.9" + VERSION_WINRES="1,2,9,0"`}, + {"abcdef", "", 1, 15, 0, 129, ` + VERSION_SHORT="1.15.129" + VERSION_LONG="1.15.129-tabcdef" + VERSION_GIT_HASH="abcdef" + VERSION_EXTRA_HASH="" + VERSION_XCODE="101.15.129" + VERSION_WINRES="1,15,129,0"`}, + {"abcdef", "", 1, 2, 0, 17, ` + VERSION_SHORT="0.0.0" + VERSION_LONG="0.0.0-tabcdef" + VERSION_GIT_HASH="abcdef" + VERSION_EXTRA_HASH="" + VERSION_XCODE="100.0.0" + VERSION_WINRES="0,0,0,0"`}, + {"abcdef", "defghi", 1, 15, 0, 129, ` + VERSION_SHORT="1.15.129" + VERSION_LONG="1.15.129-tabcdef-gdefghi" + VERSION_GIT_HASH="abcdef" + VERSION_EXTRA_HASH="defghi" + VERSION_XCODE="101.15.129" + VERSION_WINRES="1,15,129,0"`}, + {"abcdef", "", 0, 99, 5, 0, ""}, // unstable, patch number not allowed + {"abcdef", "", 0, 99, 5, 123, ""}, // unstable, patch number not allowed } for _, test := range tests { - gotlong, longOK := mkversion(t, "long", test.describe, test.other) - if longOK != test.ok { - t.Errorf("mkversion.sh long %q ok=%v, want %v", test.describe, longOK, test.ok) + want := strings.ReplaceAll(strings.TrimSpace(test.want), " ", "") + got, ok := mkversion(t, test.gitHash, test.otherHash, test.major, test.minor, test.patch, test.changeCount) + invoc := fmt.Sprintf("version.sh %s %s %d %d %d %d", test.gitHash, test.otherHash, test.major, test.minor, test.patch, test.changeCount) + if want == "" && ok { + t.Errorf("%s ok=true, want false", invoc) + continue } - gotshort, shortOK := mkversion(t, "short", test.describe, test.other) - if shortOK != test.ok { - t.Errorf("mkversion.sh short %q ok=%v, want %v", test.describe, shortOK, test.ok) - } - gotxcode, xcodeOK := mkversion(t, "xcode", test.describe, test.other) - if xcodeOK != test.ok { - t.Errorf("mkversion.sh xcode %q ok=%v, want %v", test.describe, xcodeOK, test.ok) - } - if longOK && gotlong != test.long { - t.Errorf("mkversion.sh long %q: got %q, want %q", test.describe, gotlong, test.long) - } - if shortOK && gotshort != test.short { - t.Errorf("mkversion.sh short %q: got %q, want %q", test.describe, gotshort, test.short) - } - if xcodeOK && gotxcode != test.xcode { - t.Errorf("mkversion.sh xcode %q: got %q, want %q", test.describe, gotxcode, test.xcode) + if diff := cmp.Diff(got, want); want != "" && diff != "" { + t.Errorf("%s wrong output (-got+want):\n%s", invoc, diff) } } } diff --git a/version/short.txt.do b/version/short.txt.do deleted file mode 100644 index ca9426b60..000000000 --- a/version/short.txt.do +++ /dev/null @@ -1,5 +0,0 @@ -redo-ifchange mkversion.sh describe.txt extragitcommit.txt -read -r describe $3 diff --git a/version/ver.go.do b/version/ver.go.do index 28a1bab79..6883fbdfc 100644 --- a/version/ver.go.do +++ b/version/ver.go.do @@ -1,12 +1,9 @@ -redo-ifchange long.txt short.txt gitcommit.txt extragitcommit.txt ver.go.in +redo-ifchange version-info.sh ver.go.in -read -r LONGVER $3 diff --git a/version/describe.txt.do b/version/version-info.sh.do similarity index 54% rename from version/describe.txt.do rename to version/version-info.sh.do index 9fb1095d6..f6e3554e2 100644 --- a/version/describe.txt.do +++ b/version/version-info.sh.do @@ -1,3 +1,3 @@ -./describe.sh >$3 +./version.sh ../.. >$3 redo-always redo-stamp <$3 diff --git a/version/version.h.do b/version/version.h.do index 7d1542b3e..7068f08e3 100644 --- a/version/version.h.do +++ b/version/version.h.do @@ -1,17 +1,9 @@ -redo-ifchange long.txt short.txt -read -r long $3 +cat >$3 <&2 + exit 1 + fi + + # Load the base version and optional corresponding git hash + # from the VERSION file. If there is no git hash in the file, + # we use the hash of the last change to the VERSION file. + version_file="$(dirname $0)/../VERSION" + IFS=".$IFS" read -r major minor patch base_git_hash <"$version_file" + if [ -z "$base_git_hash" ]; then + base_git_hash=$(git rev-list --max-count=1 HEAD -- $version_file) + fi + + # The full git has we're currently building at. --abbrev=200 is an + # arbitrary large number larger than all currently-known hashes, so + # that git displays the full commit hash. + git_hash=$(git describe --always --dirty --exclude '*' --abbrev=200) + # The number of extra commits between the release base to git_hash. + change_count=$(git rev-list ${base_git_hash}..HEAD | wc -l) + ;; + 6) + # Test mode: rather than run git commands and whatnot, take in + # all the version pieces as arguments. + git_hash=$1 + extra_hash=$2 + major=$3 + minor=$4 + patch=$5 + change_count=$6 + ;; + *) + echo "Usage: $0 [extra-git-hash-or-checkout]" + exit 1 +esac + +# Shortened versions of git hashes, so that they fit neatly into an +# "elongated" but still human-readable version number. +short_git_hash=$(echo $git_hash | cut -c-9) +short_extra_hash=$(echo $extra_hash | cut -c-9) + +# Convert major/minor/patch/change_count into an adjusted +# major/minor/patch. This block is where all our policies on +# versioning are. +if expr "$minor" : "[0-9]*[13579]$" >/dev/null; then + # Odd minor numbers are unstable builds. + if [ "$patch" != "0" ]; then + # This is a fatal error, because a non-zero patch number + # indicates that we created an unstable git tag in violation + # of our versioning policy, and we want to blow up loudly to + # get that fixed. + echo "Unstable release $major.$minor.$patch has a non-zero patch number, which is not allowed" >&2 + exit 1 + fi + patch="$change_count" +elif [ "$change_count" != "0" ]; then + # Even minor numbers are stable builds, but stable builds are + # supposed to have a zero change count. Therefore, we're currently + # describing a commit that's on a release branch, but hasn't been + # tagged as a patch release yet. We allow these commits to build + # for testing purposes, but force their version number to 0.0.0, + # to reflect that they're an unreleasable build. The git hashes + # still completely describe the build commit, so we can still + # figure out what this build is if it escapes into the wild. + major="0" + minor="0" + patch="0" +fi + +# Hack for 1.1: add 1000 to the patch number. We switched from using +# the proprietary repo's change_count over to using the OSS repo's +# change_count, and this was necessary to avoid a backwards jump in +# release numbers. +if [ "$major.$minor" = "1.1" ]; then + patch="$((patch + 1000))" +fi + +# At this point, the version number correctly reflects our +# policies. All that remains is to output the various vars that other +# code can use to embed version data. +if [ -z "$extra_hash" ]; then + long_version_suffix="-t$short_git_hash" +else + long_version_suffix="-t${short_git_hash}-g${short_extra_hash}" +fi +cat <$3 +redo-ifchange version-info.sh + +. ./version-info.sh + +# CFBundleShortVersionString: the "short name" used in the App Store. +# eg. 0.92.98 +echo "VERSION_NAME = $VERSION_SHORT" +# CFBundleVersion: the build number. Needs to be 3 numeric sections +# that increment for each release according to SemVer rules. +# +# We start counting at 100 because we submitted using raw build +# numbers before, and Apple doesn't let you start over. e.g. 0.98.3 +# -> 100.98.3 +echo "VERSION_ID = $VERSION_XCODE"