version: greatly simplify redo nonsense, now that we use VERSION.

Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
David Anderson 2020-11-02 18:08:47 -08:00 committed by Dave Anderson
parent 20a357b386
commit 65bad9a8bd
13 changed files with 208 additions and 265 deletions

1
version/.gitignore vendored
View File

@ -3,6 +3,7 @@ long.txt
short.txt
gitcommit.txt
extragitcommit.txt
version-info.sh
version.h
version.xcconfig
ver.go

View File

@ -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}"

View File

@ -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

View File

@ -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

View File

@ -1,5 +0,0 @@
redo-ifchange mkversion.sh describe.txt extragitcommit.txt
read -r describe <describe.txt
read -r other <extragitcommit.txt
ver=$(./mkversion.sh long "$describe" "$other")
echo "$ver" >$3

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -1,5 +0,0 @@
redo-ifchange mkversion.sh describe.txt extragitcommit.txt
read -r describe <describe.txt
read -r other <extragitcommit.txt
ver=$(./mkversion.sh short "$describe" "$other")
echo "$ver" >$3

View File

@ -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 <long.txt
read -r SHORTVER <short.txt
read -r GITCOMMIT <gitcommit.txt
read -r EXTRAGITCOMMIT <extragitcommit.txt
. ./version-info.sh
sed -e "s/{LONGVER}/$LONGVER/g" \
-e "s/{SHORTVER}/$SHORTVER/g" \
-e "s/{GITCOMMIT}/$GITCOMMIT/g" \
-e "s/{EXTRAGITCOMMIT}/$EXTRAGITCOMMIT/g" \
sed -e "s/{LONGVER}/$VERSION_LONG/g" \
-e "s/{SHORTVER}/$VERSION_SHORT/g" \
-e "s/{GITCOMMIT}/$VERSION_GIT_HASH/g" \
-e "s/{EXTRAGITCOMMIT}/$VERSION_EXTRA_HASH/g" \
<ver.go.in >$3

View File

@ -1,3 +1,3 @@
./describe.sh >$3
./version.sh ../.. >$3
redo-always
redo-stamp <$3

View File

@ -1,17 +1,9 @@
redo-ifchange long.txt short.txt
read -r long <long.txt
read -r short <short.txt
redo-ifchange version-info.sh
# get it into "major.minor.patch" format
ver=$(echo "$ver" | sed -e 's/-/./')
. ./version-info.sh
# winres is the MAJOR,MINOR,BUILD,REVISION 4-tuple used to identify
# the version of Windows binaries. We always set REVISION to 0, which
# seems to be how you map SemVer.
winres=$(echo "$short,0" | sed -e 's/\./,/g')
(
printf '#define TAILSCALE_VERSION_LONG "%s"\n' "$long"
printf '#define TAILSCALE_VERSION_SHORT "%s"\n' "$short"
printf '#define TAILSCALE_VERSION_WIN_RES %s\n' "$winres"
) >$3
cat >$3 <<EOF
#define TAILSCALE_VERSION_LONG "$VERSION_LONG"
#define TAILSCALE_VERSION_SHORT "$VERSION_SHORT"
#define TAILSCALE_VERSION_WIN_RES $VERSION_WINRES
EOF

109
version/version.sh Executable file
View File

@ -0,0 +1,109 @@
#!/bin/sh
set -eu
case $# in
0|1)
# extra_hash describes a git repository other than the current
# one. It gets embedded as an additional commit hash in built
# binaries, to help us locate the exact set of tools and code
# that were used.
extra_hash="${1:-}"
if [ -z "$extra_hash" ]; then
# Nothing, empty extra hash is fine.
extra_hash=""
elif [ -d "$extra_hash/.git" ]; then
extra_hash=$(cd "$extra_hash" && git describe --always --dirty --exclude '*' --abbrev=200)
elif ! expr "$extra_hash" : "^[0-9a-f]*$"; then
echo "Invalid extra hash '$extra_hash', must be a git commit hash or path to a git repo" >&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 <<EOF
VERSION_SHORT="${major}.${minor}.${patch}"
VERSION_LONG="${major}.${minor}.${patch}${long_version_suffix}"
VERSION_GIT_HASH="${git_hash}"
VERSION_EXTRA_HASH="${extra_hash}"
VERSION_XCODE="$((major + 100)).${minor}.${patch}"
VERSION_WINRES="${major},${minor},${patch},0"
EOF

View File

@ -1,5 +1,14 @@
redo-ifchange mkversion.sh describe.txt extragitcommit.txt
read -r describe <describe.txt
read -r other <extragitcommit.txt
ver=$(./mkversion.sh xcode "$describe" "$other")
echo "$ver" >$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"