Something went wrong. Try again.
forked from tailscale/tailscale
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246// Copyright (c) Tailscale Inc & contributors// SPDX-License-Identifier: BSD-3-Clause
// Package version provides the version that the binary was built at.package version
import ( "fmt" "runtime/debug" "strconv" "strings" "sync"
tailscaleroot "tailscale.com" "tailscale.com/types/lazy")
// Stamp vars can have their value set at build time by linker flags (see// build_dist.sh for an example). When set, these stamps serve as additional// inputs to computing the binary's version as returned by the functions in this// package.//// All stamps are optional.var ( // longStamp is the full version identifier of the build. If set, it is // returned verbatim by Long() and other functions that return Long()'s // output. longStamp string
// shortStamp is the short version identifier of the build. If set, it // is returned verbatim by Short() and other functions that return Short()'s // output. shortStamp string
// gitCommitStamp is the git commit of the github.com/tailscale/tailscale // repository at which Tailscale was built. Its format is the one returned // by `git rev-parse <commit>`. If set, it is used instead of any git commit // information embedded by the Go tool. gitCommitStamp string
// gitDirtyStamp is whether the git checkout from which the code was built // was dirty. Its value is ORed with the dirty bit embedded by the Go tool. // // We need this because when we build binaries from another repo that // imports tailscale.com, the Go tool doesn't stamp any dirtiness info into // the binary. Instead, we have to inject the dirty bit ourselves here. gitDirtyStamp bool
// extraGitCommit, is the git commit of a "supplemental" repository at which // Tailscale was built. Its format is the same as gitCommit. // // extraGitCommit is used to track the source revision when the main // Tailscale repository is integrated into and built from another repository // (for example, Tailscale's proprietary code, or the Android OSS // repository). Together, gitCommit and extraGitCommit exactly describe what // repositories and commits were used in a build. extraGitCommitStamp string)
var long lazy.SyncValue[string]
// Long returns a full version number for this build, of one of the forms://// - "x.y.z-commithash-otherhash" for release builds distributed by Tailscale// - "x.y.z-commithash" for release builds built with build_dist.sh// - "x.y.z-changecount-commithash-otherhash" for untagged release branch// builds by Tailscale (these are not distributed).// - "x.y.z-changecount-commithash" for untagged release branch builds// built with build_dist.sh// - "x.y.z-devYYYYMMDD-tcommithash{,-dirty}" for builds of tailscale.com// made with plain "go build" or "go install", or// "x.y.z-devYYYYMMDD-gcommithash{,-dirty}" for such builds of a repo// that imports tailscale.com// - "x.y.z-ERR-BuildInfo" for builds made by plain "go run"func Long() string { return long.Get(func() string { if longStamp != "" { return longStamp } bi := getEmbeddedInfo() if !bi.valid { return strings.TrimSpace(tailscaleroot.VersionDotTxt) + "-ERR-BuildInfo" } // The Go tool's embedded VCS info describes the main module's // repo. By convention (matching mkversion's stamped versions), // "t" always prefixes a tailscale.com commit and "g" a commit of // some other repo that imports tailscale.com, such as Tailscale's // proprietary repo. hashPrefix := "t" if bi.mainModule != "tailscale.com" { hashPrefix = "g" } return fmt.Sprintf("%s-dev%s-%s%s%s", strings.TrimSpace(tailscaleroot.VersionDotTxt), bi.commitDate, hashPrefix, bi.commitAbbrev(), dirtyString()) })}
var short lazy.SyncValue[string]
// Short returns a short version number for this build, of the forms://// - "x.y.z" for builds distributed by Tailscale or built with build_dist.sh// - "x.y.z-devYYYYMMDD" for builds made with plain "go build" or "go install"// - "x.y.z-ERR-BuildInfo" for builds made by plain "go run"func Short() string { return short.Get(func() string { if shortStamp != "" { return shortStamp } bi := getEmbeddedInfo() if !bi.valid { return strings.TrimSpace(tailscaleroot.VersionDotTxt) + "-ERR-BuildInfo" } return strings.TrimSpace(tailscaleroot.VersionDotTxt) + "-dev" + bi.commitDate })}
type embeddedInfo struct { valid bool mainModule string // module path of the main module, e.g. "tailscale.com" commit string commitDate string commitTime string dirty bool}
func (i embeddedInfo) commitAbbrev() string { if len(i.commit) >= 9 { return i.commit[:9] } return i.commit}
var getEmbeddedInfo = sync.OnceValue(func() embeddedInfo { bi, ok := debug.ReadBuildInfo() if !ok { return embeddedInfo{} } ret := embeddedInfo{valid: true, mainModule: bi.Main.Path} for _, s := range bi.Settings { switch s.Key { case "vcs.revision": ret.commit = s.Value case "vcs.time": ret.commitTime = s.Value if len(s.Value) >= len("yyyy-mm-dd") { ret.commitDate = s.Value[:len("yyyy-mm-dd")] ret.commitDate = strings.ReplaceAll(ret.commitDate, "-", "") } case "vcs.modified": ret.dirty = s.Value == "true" } } if ret.commit == "" || ret.commitDate == "" { // Build info is present in the binary, but has no useful data. Act as // if it's missing. return embeddedInfo{} } return ret})
// tailscaleToolchainRev returns the git hash of the Tailscale Go toolchain// used to build this binary, if any. It is read separately from getEmbeddedInfo// because that function discards build info when VCS fields are missing (e.g.// in test binaries), but the toolchain rev is still present.var tailscaleToolchainRev = sync.OnceValue(func() string { bi, ok := debug.ReadBuildInfo() if !ok { return "" } for _, s := range bi.Settings { if s.Key == "tailscale.toolchain.rev" { return s.Value } } return ""})
func gitCommit() string { if gitCommitStamp != "" { return gitCommitStamp } return getEmbeddedInfo().commit}
func gitDirty() bool { if gitDirtyStamp { return true } return getEmbeddedInfo().dirty}
func dirtyString() string { if gitDirty() { return "-dirty" } return ""}
func majorMinorPatch() string { ret, _, _ := strings.Cut(Short(), "-") return ret}
// IsTailscaleGo reports whether the current binary was built with// Tailscale's custom Go toolchain.func IsTailscaleGo() bool { return isTailscaleGo }
func isValidLongWithTwoRepos(v string) bool { s := strings.Split(v, "-") if len(s) != 3 { return false } hexChunk := func(s string) bool { if len(s) < 6 { return false } for i := range len(s) { b := s[i] if (b < '0' || b > '9') && (b < 'a' || b > 'f') { return false } } return true }
v, t, g := s[0], s[1], s[2] if !strings.HasPrefix(t, "t") || !strings.HasPrefix(g, "g") || !hexChunk(t[1:]) || !hexChunk(g[1:]) { return false } nums := strings.Split(v, ".") if len(nums) != 3 { return false } for i, n := range nums { bits := 8 if i == 2 { bits = 16 } if _, err := strconv.ParseUint(n, 10, bits); err != nil { return false } } return true}