Browse Source

version/mkversion: allow collecting version only from this repo

With this change, you can collect version info from either a git
checkout of the tailscale.com Go module (this repo), or a git
checkout of a repo that imports the tailscale.com Go module.

Signed-off-by: David Anderson <[email protected]>
David Anderson 3 years ago
parent
commit
45d9784f9d
3 changed files with 126 additions and 210 deletions
  1. 1 1
      go.mod
  2. 117 159
      version/mkversion/mkversion.go
  3. 8 50
      version/mkversion/mkversion_test.go

+ 1 - 1
go.mod

@@ -72,6 +72,7 @@ require (
 	go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf
 	golang.org/x/crypto v0.6.0
 	golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
+	golang.org/x/mod v0.7.0
 	golang.org/x/net v0.7.0
 	golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
 	golang.org/x/sync v0.1.0
@@ -304,7 +305,6 @@ require (
 	go.uber.org/multierr v1.6.0 // indirect
 	golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect
 	golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect
-	golang.org/x/mod v0.7.0 // indirect
 	golang.org/x/text v0.7.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect

+ 117 - 159
version/mkversion/mkversion.go

@@ -8,19 +8,17 @@ package mkversion
 
 import (
 	"bytes"
-	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"os/exec"
 	"path/filepath"
-	"runtime"
 	"strconv"
 	"strings"
 	"time"
 
 	"github.com/google/uuid"
+	"golang.org/x/mod/modfile"
 )
 
 // VersionInfo is version information extracted from a git checkout.
@@ -86,15 +84,16 @@ func (v VersionInfo) String() string {
 	f(&b, "VERSION_SHORT=%q\n", v.Short)
 	f(&b, "VERSION_LONG=%q\n", v.Long)
 	f(&b, "VERSION_GIT_HASH=%q\n", v.GitHash)
-	f(&b, "VERSION_EXTRA_HASH=%q\n", v.OtherHash)
-	f(&b, "VERSION_XCODE=%q\n", v.Xcode)
-	f(&b, "VERSION_XCODE_MACOS=%q\n", v.XcodeMacOS)
-	f(&b, "VERSION_WINRES=%q\n", v.Winres)
 	f(&b, "VERSION_TRACK=%q\n", v.Track)
-
-	// Ensure a predictable order for these variables for testing purposes.
-	for _, k := range []string{"amd64", "arm64", "x86"} {
-		f(&b, "VERSION_MSIPRODUCT_%s=%q\n", strings.ToUpper(k), v.MSIProductCodes[k])
+	if v.OtherHash != "" {
+		f(&b, "VERSION_EXTRA_HASH=%q\n", v.OtherHash)
+		f(&b, "VERSION_XCODE=%q\n", v.Xcode)
+		f(&b, "VERSION_XCODE_MACOS=%q\n", v.XcodeMacOS)
+		f(&b, "VERSION_WINRES=%q\n", v.Winres)
+		// Ensure a predictable order for these variables for testing purposes.
+		for _, k := range []string{"amd64", "arm64", "x86"} {
+			f(&b, "VERSION_MSIPRODUCT_%s=%q\n", strings.ToUpper(k), v.MSIProductCodes[k])
+		}
 	}
 
 	return b.String()
@@ -114,37 +113,83 @@ func Info() VersionInfo {
 func InfoFrom(dir string) (VersionInfo, error) {
 	runner := dirRunner(dir)
 
-	var err error
+	gitRoot, err := runner.output("git", "rev-parse", "--show-toplevel")
+	if err != nil {
+		return VersionInfo{}, fmt.Errorf("finding git root: %w", err)
+	}
+	runner = dirRunner(gitRoot)
+
+	modBs, err := os.ReadFile(filepath.Join(gitRoot, "go.mod"))
+	if err != nil {
+		return VersionInfo{}, fmt.Errorf("reading go.mod: %w", err)
+	}
+	modPath := modfile.ModulePath(modBs)
+
+	if modPath == "" {
+		return VersionInfo{}, fmt.Errorf("no module path in go.mod")
+	}
+	if modPath == "tailscale.com" {
+		// Invoked in the tailscale.com repo directly, just no further info to
+		// collect.
+		v, err := infoFromDir(gitRoot)
+		if err != nil {
+			return VersionInfo{}, err
+		}
+		return mkOutput(v)
+	}
+
+	// We seem to be in a repo that imports tailscale.com. Find the
+	// tailscale.com repo and collect additional info from it.
 	otherHash, err := runner.output("git", "rev-parse", "HEAD")
 	if err != nil {
-		return VersionInfo{}, err
+		return VersionInfo{}, fmt.Errorf("getting git hash: %w", err)
 	}
 	otherDate, err := runner.output("git", "log", "-n1", "--format=%ct", "HEAD")
 	if err != nil {
-		return VersionInfo{}, err
-	}
-	if !runner.ok("git", "diff-index", "--quiet", "HEAD") {
-		otherHash = otherHash + "-dirty"
+		return VersionInfo{}, fmt.Errorf("getting git date: %w", err)
 	}
 
-	var v verInfo
-	hash, dir, err := parseGoMod(runner)
+	// Note, this mechanism doesn't correctly support go.mod replacements,
+	// or go workdirs. We only parse out the commit hash from go.mod's
+	// "require" line, nothing else.
+	tailscaleHash, err := tailscaleModuleHash(modBs)
 	if err != nil {
 		return VersionInfo{}, err
 	}
-	if hash != "" {
-		v, err = infoFromCache(hash, runner)
-	} else {
-		v, err = infoFromDir(dir)
-	}
+
+	v, err := infoFromCache(tailscaleHash, runner)
 	if err != nil {
 		return VersionInfo{}, err
 	}
 	v.otherHash = otherHash
 	v.otherDate = otherDate
 
+	if !runner.ok("git", "diff-index", "--quiet", "HEAD") {
+		v.otherHash = v.otherHash + "-dirty"
+	}
+
 	return mkOutput(v)
+}
 
+// tailscaleModuleHash returns the git hash of the 'require tailscale.com' line
+// in the given go.mod bytes.
+func tailscaleModuleHash(modBs []byte) (string, error) {
+	mod, err := modfile.Parse("go.mod", modBs, nil)
+	if err != nil {
+		return "", err
+	}
+	for _, req := range mod.Require {
+		if req.Mod.Path != "tailscale.com" {
+			continue
+		}
+		// Get the last - separated part of req.Mod.Version
+		// (which is the git hash).
+		if i := strings.LastIndexByte(req.Mod.Version, '-'); i != -1 {
+			return req.Mod.Version[i+1:], nil
+		}
+		return "", fmt.Errorf("couldn't parse git hash from tailscale.com version %q", req.Mod.Version)
+	}
+	return "", fmt.Errorf("no require tailscale.com line in go.mod")
 }
 
 func mkOutput(v verInfo) (VersionInfo, error) {
@@ -183,40 +228,51 @@ func mkOutput(v verInfo) (VersionInfo, error) {
 		track = "stable"
 	}
 
-	// Generate a monotonically increasing version number for the macOS app, as
-	// expected by Apple. We use the date so that it's always increasing (if we
-	// based it on the actual version number we'd run into issues when doing
-	// cherrypick stable builds from a release branch after unstable builds from
-	// HEAD).
-	otherSec, err := strconv.ParseInt(v.otherDate, 10, 64)
-	if err != nil {
-		return VersionInfo{}, fmt.Errorf("Culd not parse otherDate %q: %w", v.otherDate, err)
-	}
-	otherTime := time.Unix(otherSec, 0).UTC()
-	// We started to need to do this in 2023, and the last Apple-generated
-	// incrementing build number was 273. To avoid using up the space, we
-	// use <year - 1750> as the major version (thus 273.*, 274.* in 2024, etc.),
-	// so that we we're still in the same range. This way if Apple goes back to
-	// auto-incrementing the number for us, we can go back to it with
-	// reasonable-looking numbers.
-	xcodeMacOS := fmt.Sprintf("%d.%d.%d", otherTime.Year()-1750, otherTime.YearDay(), otherTime.Hour()*60*60+otherTime.Minute()*60+otherTime.Second())
-
-	return VersionInfo{
-		Major:           v.major,
-		Minor:           v.minor,
-		Patch:           v.patch,
-		Short:           fmt.Sprintf("%d.%d.%d", v.major, v.minor, v.patch),
-		Long:            fmt.Sprintf("%d.%d.%d%s%s", v.major, v.minor, v.patch, changeSuffix, hashes),
-		GitHash:         fmt.Sprintf("%s", v.hash),
-		GitDate:         fmt.Sprintf("%s", v.date),
-		OtherHash:       fmt.Sprintf("%s", v.otherHash),
-		OtherDate:       fmt.Sprintf("%s", v.otherDate),
-		Xcode:           fmt.Sprintf("%d.%d.%d", v.major+100, v.minor, v.patch),
-		XcodeMacOS:      xcodeMacOS,
-		Winres:          fmt.Sprintf("%d,%d,%d,0", v.major, v.minor, v.patch),
-		Track:           track,
-		MSIProductCodes: makeMSIProductCodes(v, track),
-	}, nil
+	ret := VersionInfo{
+		Major:   v.major,
+		Minor:   v.minor,
+		Patch:   v.patch,
+		Short:   fmt.Sprintf("%d.%d.%d", v.major, v.minor, v.patch),
+		Long:    fmt.Sprintf("%d.%d.%d%s%s", v.major, v.minor, v.patch, changeSuffix, hashes),
+		GitHash: fmt.Sprintf("%s", v.hash),
+		GitDate: fmt.Sprintf("%s", v.date),
+		Track:   track,
+	}
+
+	if v.otherHash != "" {
+		ret.OtherHash = fmt.Sprintf("%s", v.otherHash)
+
+		// Technically we could populate these fields without the otherHash, but
+		// these version numbers only make sense when building from Tailscale's
+		// proprietary repo, so don't clutter open-source-only outputs with
+		// them.
+		ret.Xcode = fmt.Sprintf("%d.%d.%d", v.major+100, v.minor, v.patch)
+		ret.Winres = fmt.Sprintf("%d,%d,%d,0", v.major, v.minor, v.patch)
+		ret.MSIProductCodes = makeMSIProductCodes(v, track)
+	}
+	if v.otherDate != "" {
+		ret.OtherDate = fmt.Sprintf("%s", v.otherDate)
+
+		// Generate a monotonically increasing version number for the macOS app, as
+		// expected by Apple. We use the date so that it's always increasing (if we
+		// based it on the actual version number we'd run into issues when doing
+		// cherrypick stable builds from a release branch after unstable builds from
+		// HEAD).
+		otherSec, err := strconv.ParseInt(v.otherDate, 10, 64)
+		if err != nil {
+			return VersionInfo{}, fmt.Errorf("Could not parse otherDate %q: %w", v.otherDate, err)
+		}
+		otherTime := time.Unix(otherSec, 0).UTC()
+		// We started to need to do this in 2023, and the last Apple-generated
+		// incrementing build number was 273. To avoid using up the space, we
+		// use <year - 1750> as the major version (thus 273.*, 274.* in 2024, etc.),
+		// so that we we're still in the same range. This way if Apple goes back to
+		// auto-incrementing the number for us, we can go back to it with
+		// reasonable-looking numbers.
+		ret.XcodeMacOS = fmt.Sprintf("%d.%d.%d", otherTime.Year()-1750, otherTime.YearDay(), otherTime.Hour()*60*60+otherTime.Minute()*60+otherTime.Second())
+	}
+
+	return ret, nil
 }
 
 // makeMSIProductCodes produces per-architecture v5 UUIDs derived from the pkgs
@@ -237,60 +293,6 @@ func makeMSIProductCodes(v verInfo, track string) map[string]string {
 	return result
 }
 
-func gitRootDir() (string, error) {
-	top, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
-	if err != nil {
-		return "", fmt.Errorf("failed to find git top level: %w", err)
-	}
-	return strings.TrimSpace(string(top)), nil
-}
-
-func parseGoMod(runner dirRunner) (shortHash, localCheckout string, err error) {
-	goBin := filepath.Join(runtime.GOROOT(), "bin", "go"+exe())
-	if !strings.HasPrefix(goBin, "/") {
-		// GOROOT got -trimpath'd, fall back to hoping $PATH has a
-		// working go.
-		goBin = "go"
-	}
-	mod, err := runner.output(goBin, "mod", "edit", "--json")
-	if err != nil {
-		return "", "", err
-	}
-	var mj modJSON
-	if err := json.Unmarshal([]byte(mod), &mj); err != nil {
-		return "", "", fmt.Errorf("parsing go.mod: %w", err)
-	}
-
-	for _, r := range mj.Replace {
-		if r.Old.Path != "tailscale.com" {
-			continue
-		}
-		if filepath.IsAbs(r.New.Path) {
-			return "", r.New.Path, nil
-		}
-		gitRoot, err := gitRootDir()
-		if err != nil {
-			return "", "", err
-		}
-		return "", filepath.Join(gitRoot, r.New.Path), nil
-	}
-	for _, r := range mj.Require {
-		if r.Path != "tailscale.com" {
-			continue
-		}
-		shortHash = r.Version[strings.LastIndex(r.Version, "-")+1:]
-		return shortHash, "", nil
-	}
-	return "", "", fmt.Errorf("failed to find tailscale.com module in go.mod")
-}
-
-func exe() string {
-	if runtime.GOOS == "windows" {
-		return ".exe"
-	}
-	return ""
-}
-
 type verInfo struct {
 	major, minor, patch int
 	changeCount         int
@@ -308,15 +310,6 @@ type verInfo struct {
 // sentinel patch number.
 const unknownPatchVersion = 9999999
 
-func isBareRepo(r dirRunner) (bool, error) {
-	s, err := r.output("git", "rev-parse", "--is-bare-repository")
-	if err != nil {
-		return false, err
-	}
-	o := strings.TrimSpace(s)
-	return o == "true", nil
-}
-
 func infoFromCache(shortHash string, runner dirRunner) (verInfo, error) {
 	cacheDir, err := os.UserCacheDir()
 	if err != nil {
@@ -325,25 +318,9 @@ func infoFromCache(shortHash string, runner dirRunner) (verInfo, error) {
 	tailscaleCache := filepath.Join(cacheDir, "tailscale-oss")
 	r := dirRunner(tailscaleCache)
 
-	cloneRequired := false
 	if _, err := os.Stat(tailscaleCache); err != nil {
-		cloneRequired = true
-	} else {
-		isBare, err := isBareRepo(r)
-		if err != nil {
-			return verInfo{}, err
-		}
-		if isBare {
-			cloneRequired = true
-			if err := os.RemoveAll(tailscaleCache); err != nil {
-				return verInfo{}, fmt.Errorf("removing old cache dir failed: %w", err)
-			}
-		}
-	}
-
-	if cloneRequired {
 		if !runner.ok("git", "clone", "https://github.com/tailscale/tailscale", tailscaleCache) {
-			return verInfo{}, fmt.Errorf("cloning OSS repo failed")
+			return verInfo{}, fmt.Errorf("cloning tailscale.com repo failed")
 		}
 	}
 
@@ -396,11 +373,11 @@ func infoFromDir(dir string) (verInfo, error) {
 	gitDir := filepath.Join(dir, ".git")
 	if _, err := os.Stat(gitDir); err != nil {
 		// Raw directory fetch, get as much info as we can and make up the rest.
-		s, err := readFile(filepath.Join(dir, "VERSION.txt"))
+		bs, err := os.ReadFile(filepath.Join(dir, "VERSION.txt"))
 		if err != nil {
 			return verInfo{}, err
 		}
-		major, minor, patch, err := parseVersion(s)
+		major, minor, patch, err := parseVersion(strings.TrimSpace(string(bs)))
 		return verInfo{
 			major:       major,
 			minor:       minor,
@@ -448,20 +425,6 @@ func infoFromDir(dir string) (verInfo, error) {
 	}, nil
 }
 
-type modJSON struct {
-	Require []goPath
-	Replace []modReplace
-}
-
-type modReplace struct {
-	Old, New goPath
-}
-
-type goPath struct {
-	Path    string
-	Version string
-}
-
 func parseVersion(s string) (major, minor, patch int, err error) {
 	fs := strings.Split(strings.TrimSpace(s), ".")
 	if len(fs) != 3 {
@@ -488,11 +451,6 @@ func shortHash(hash string) string {
 	return hash[:9]
 }
 
-func readFile(path string) (string, error) {
-	bs, err := ioutil.ReadFile(path)
-	return strings.TrimSpace(string(bs)), err
-}
-
 // dirRunner executes commands in the specified dir.
 type dirRunner string
 

+ 8 - 50
version/mkversion/mkversion_test.go

@@ -38,14 +38,7 @@ func TestMkversion(t *testing.T) {
            VERSION_SHORT="0.98.0"
            VERSION_LONG="0.98.0-tabcdef"
            VERSION_GIT_HASH="abcdef"
-           VERSION_EXTRA_HASH=""
-           VERSION_XCODE="100.98.0"
-           VERSION_XCODE_MACOS="273.27.3723"
-           VERSION_WINRES="0,98,0,0"
-           VERSION_TRACK="stable"
-           VERSION_MSIPRODUCT_AMD64="C653B075-AD91-5265-9DF8-0087D35D148D"
-           VERSION_MSIPRODUCT_ARM64="1C41380B-A742-5A3C-AF5D-DF7894DD0FB8"
-           VERSION_MSIPRODUCT_X86="4ABDDA14-7499-5C2E-A62A-DD435C50C4CB"`},
+           VERSION_TRACK="stable"`},
 		{mkInfo("abcdef", "", otherDate, 0, 98, 1, 0), `
            VERSION_MAJOR=0
            VERSION_MINOR=98
@@ -53,14 +46,7 @@ func TestMkversion(t *testing.T) {
            VERSION_SHORT="0.98.1"
            VERSION_LONG="0.98.1-tabcdef"
            VERSION_GIT_HASH="abcdef"
-           VERSION_EXTRA_HASH=""
-           VERSION_XCODE="100.98.1"
-           VERSION_XCODE_MACOS="273.27.3723"
-           VERSION_WINRES="0,98,1,0"
-           VERSION_TRACK="stable"
-           VERSION_MSIPRODUCT_AMD64="DFD6DCF2-06D8-5D19-BDA0-FAF31E44EC23"
-           VERSION_MSIPRODUCT_ARM64="A4CCF19C-372B-5007-AFD8-1AF661DFF670"
-           VERSION_MSIPRODUCT_X86="FF12E937-DDC4-5868-9B63-D35B2050D4EA"`},
+           VERSION_TRACK="stable"`},
 		{mkInfo("abcdef", "", otherDate, 1, 2, 9, 0), `
            VERSION_MAJOR=1
            VERSION_MINOR=2
@@ -68,14 +54,7 @@ func TestMkversion(t *testing.T) {
            VERSION_SHORT="1.2.9"
            VERSION_LONG="1.2.9-tabcdef"
            VERSION_GIT_HASH="abcdef"
-           VERSION_EXTRA_HASH=""
-           VERSION_XCODE="101.2.9"
-           VERSION_XCODE_MACOS="273.27.3723"
-           VERSION_WINRES="1,2,9,0"
-           VERSION_TRACK="stable"
-           VERSION_MSIPRODUCT_AMD64="D47B5157-FF26-5A10-A94E-50E4529303A9"
-           VERSION_MSIPRODUCT_ARM64="91D16F75-2A12-5E12-820A-67B89BF858E7"
-           VERSION_MSIPRODUCT_X86="8F1AC1C6-B93B-5C70-802E-6AE9591FA0D6"`},
+           VERSION_TRACK="stable"`},
 		{mkInfo("abcdef", "", otherDate, 1, 15, 0, 129), `
            VERSION_MAJOR=1
            VERSION_MINOR=15
@@ -83,14 +62,7 @@ func TestMkversion(t *testing.T) {
            VERSION_SHORT="1.15.129"
            VERSION_LONG="1.15.129-tabcdef"
            VERSION_GIT_HASH="abcdef"
-           VERSION_EXTRA_HASH=""
-           VERSION_XCODE="101.15.129"
-           VERSION_XCODE_MACOS="273.27.3723"
-           VERSION_WINRES="1,15,129,0"
-           VERSION_TRACK="unstable"
-           VERSION_MSIPRODUCT_AMD64="89C96952-1FB8-5A4D-B02E-16A8060C56AA"
-           VERSION_MSIPRODUCT_ARM64="DB1A2E86-66C4-5CEC-8F4C-7DB805370F3A"
-           VERSION_MSIPRODUCT_X86="DC57C0C3-5164-5C92-86B3-2800CEFF0540"`},
+           VERSION_TRACK="unstable"`},
 		{mkInfo("abcdef", "", otherDate, 1, 2, 0, 17), `
            VERSION_MAJOR=1
            VERSION_MINOR=2
@@ -98,14 +70,7 @@ func TestMkversion(t *testing.T) {
            VERSION_SHORT="1.2.0"
            VERSION_LONG="1.2.0-17-tabcdef"
            VERSION_GIT_HASH="abcdef"
-           VERSION_EXTRA_HASH=""
-           VERSION_XCODE="101.2.0"
-           VERSION_XCODE_MACOS="273.27.3723"
-           VERSION_WINRES="1,2,0,0"
-           VERSION_TRACK="stable"
-           VERSION_MSIPRODUCT_AMD64="0F9709AE-0E5E-51AF-BCCD-A25314B4CE8B"
-           VERSION_MSIPRODUCT_ARM64="39D5D46E-E644-5C80-9EF8-224AC1AD5969"
-           VERSION_MSIPRODUCT_X86="4487136B-2D11-5E42-BD80-B8529F3326F4"`},
+           VERSION_TRACK="stable"`},
 		{mkInfo("abcdef", "defghi", otherDate, 1, 15, 0, 129), `
            VERSION_MAJOR=1
            VERSION_MINOR=15
@@ -113,11 +78,11 @@ func TestMkversion(t *testing.T) {
            VERSION_SHORT="1.15.129"
            VERSION_LONG="1.15.129-tabcdef-gdefghi"
            VERSION_GIT_HASH="abcdef"
+           VERSION_TRACK="unstable"
            VERSION_EXTRA_HASH="defghi"
            VERSION_XCODE="101.15.129"
            VERSION_XCODE_MACOS="273.27.3723"
            VERSION_WINRES="1,15,129,0"
-           VERSION_TRACK="unstable"
            VERSION_MSIPRODUCT_AMD64="89C96952-1FB8-5A4D-B02E-16A8060C56AA"
            VERSION_MSIPRODUCT_ARM64="DB1A2E86-66C4-5CEC-8F4C-7DB805370F3A"
            VERSION_MSIPRODUCT_X86="DC57C0C3-5164-5C92-86B3-2800CEFF0540"`},
@@ -128,14 +93,7 @@ func TestMkversion(t *testing.T) {
            VERSION_SHORT="1.2.0"
            VERSION_LONG="1.2.0-17-tabcdef"
            VERSION_GIT_HASH="abcdef"
-           VERSION_EXTRA_HASH=""
-           VERSION_XCODE="101.2.0"
-           VERSION_XCODE_MACOS="273.27.3723"
-           VERSION_WINRES="1,2,0,0"
-           VERSION_TRACK="stable"
-           VERSION_MSIPRODUCT_AMD64="0F9709AE-0E5E-51AF-BCCD-A25314B4CE8B"
-           VERSION_MSIPRODUCT_ARM64="39D5D46E-E644-5C80-9EF8-224AC1AD5969"
-           VERSION_MSIPRODUCT_X86="4487136B-2D11-5E42-BD80-B8529F3326F4"`},
+           VERSION_TRACK="stable"`},
 		{mkInfo("abcdef", "defghi", otherDate, 1, 15, 0, 129), `
            VERSION_MAJOR=1
            VERSION_MINOR=15
@@ -143,11 +101,11 @@ func TestMkversion(t *testing.T) {
            VERSION_SHORT="1.15.129"
            VERSION_LONG="1.15.129-tabcdef-gdefghi"
            VERSION_GIT_HASH="abcdef"
+           VERSION_TRACK="unstable"
            VERSION_EXTRA_HASH="defghi"
            VERSION_XCODE="101.15.129"
            VERSION_XCODE_MACOS="273.27.3723"
            VERSION_WINRES="1,15,129,0"
-           VERSION_TRACK="unstable"
            VERSION_MSIPRODUCT_AMD64="89C96952-1FB8-5A4D-B02E-16A8060C56AA"
            VERSION_MSIPRODUCT_ARM64="DB1A2E86-66C4-5CEC-8F4C-7DB805370F3A"
            VERSION_MSIPRODUCT_X86="DC57C0C3-5164-5C92-86B3-2800CEFF0540"`},