| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- // 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-commithash{,-dirty}" for builds made with plain "go
- // build" or "go install"
- // - "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"
- }
- return fmt.Sprintf("%s-dev%s-t%s%s", strings.TrimSpace(tailscaleroot.VersionDotTxt), bi.commitDate, 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
- 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}
- 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
- })
- 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
- }
- 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
- }
|