version.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package version provides the version that the binary was built at.
  4. package version
  5. import (
  6. "fmt"
  7. "runtime/debug"
  8. "strings"
  9. tailscaleroot "tailscale.com"
  10. "tailscale.com/types/lazy"
  11. )
  12. // Stamp vars can have their value set at build time by linker flags (see
  13. // build_dist.sh for an example). When set, these stamps serve as additional
  14. // inputs to computing the binary's version as returned by the functions in this
  15. // package.
  16. //
  17. // All stamps are optional.
  18. var (
  19. // longStamp is the full version identifier of the build. If set, it is
  20. // returned verbatim by Long() and other functions that return Long()'s
  21. // output.
  22. longStamp string
  23. // shortStamp is the short version identifier of the build. If set, it
  24. // is returned verbatim by Short() and other functions that return Short()'s
  25. // output.
  26. shortStamp string
  27. // gitCommitStamp is the git commit of the github.com/tailscale/tailscale
  28. // repository at which Tailscale was built. Its format is the one returned
  29. // by `git rev-parse <commit>`. If set, it is used instead of any git commit
  30. // information embedded by the Go tool.
  31. gitCommitStamp string
  32. // gitDirtyStamp is whether the git checkout from which the code was built
  33. // was dirty. Its value is ORed with the dirty bit embedded by the Go tool.
  34. //
  35. // We need this because when we build binaries from another repo that
  36. // imports tailscale.com, the Go tool doesn't stamp any dirtiness info into
  37. // the binary. Instead, we have to inject the dirty bit ourselves here.
  38. gitDirtyStamp bool
  39. // extraGitCommit, is the git commit of a "supplemental" repository at which
  40. // Tailscale was built. Its format is the same as gitCommit.
  41. //
  42. // extraGitCommit is used to track the source revision when the main
  43. // Tailscale repository is integrated into and built from another repository
  44. // (for example, Tailscale's proprietary code, or the Android OSS
  45. // repository). Together, gitCommit and extraGitCommit exactly describe what
  46. // repositories and commits were used in a build.
  47. extraGitCommitStamp string
  48. )
  49. var long lazy.SyncValue[string]
  50. // Long returns a full version number for this build, of one of the forms:
  51. //
  52. // - "x.y.z-commithash-otherhash" for release builds distributed by Tailscale
  53. // - "x.y.z-commithash" for release builds built with build_dist.sh
  54. // - "x.y.z-changecount-commithash-otherhash" for untagged release branch
  55. // builds by Tailscale (these are not distributed).
  56. // - "x.y.z-changecount-commithash" for untagged release branch builds
  57. // built with build_dist.sh
  58. // - "x.y.z-devYYYYMMDD-commithash{,-dirty}" for builds made with plain "go
  59. // build" or "go install"
  60. // - "x.y.z-ERR-BuildInfo" for builds made by plain "go run"
  61. func Long() string {
  62. return long.Get(func() string {
  63. if longStamp != "" {
  64. return longStamp
  65. }
  66. bi := getEmbeddedInfo()
  67. if !bi.valid {
  68. return strings.TrimSpace(tailscaleroot.VersionDotTxt) + "-ERR-BuildInfo"
  69. }
  70. return fmt.Sprintf("%s-dev%s-t%s%s", strings.TrimSpace(tailscaleroot.VersionDotTxt), bi.commitDate, bi.commitAbbrev(), dirtyString())
  71. })
  72. }
  73. var short lazy.SyncValue[string]
  74. // Short returns a short version number for this build, of the forms:
  75. //
  76. // - "x.y.z" for builds distributed by Tailscale or built with build_dist.sh
  77. // - "x.y.z-devYYYYMMDD" for builds made with plain "go build" or "go install"
  78. // - "x.y.z-ERR-BuildInfo" for builds made by plain "go run"
  79. func Short() string {
  80. return short.Get(func() string {
  81. if shortStamp != "" {
  82. return shortStamp
  83. }
  84. bi := getEmbeddedInfo()
  85. if !bi.valid {
  86. return strings.TrimSpace(tailscaleroot.VersionDotTxt) + "-ERR-BuildInfo"
  87. }
  88. return strings.TrimSpace(tailscaleroot.VersionDotTxt) + "-dev" + bi.commitDate
  89. })
  90. }
  91. type embeddedInfo struct {
  92. valid bool
  93. commit string
  94. commitDate string
  95. commitTime string
  96. dirty bool
  97. }
  98. func (i embeddedInfo) commitAbbrev() string {
  99. if len(i.commit) >= 9 {
  100. return i.commit[:9]
  101. }
  102. return i.commit
  103. }
  104. var getEmbeddedInfo = lazy.SyncFunc(func() embeddedInfo {
  105. bi, ok := debug.ReadBuildInfo()
  106. if !ok {
  107. return embeddedInfo{}
  108. }
  109. ret := embeddedInfo{valid: true}
  110. for _, s := range bi.Settings {
  111. switch s.Key {
  112. case "vcs.revision":
  113. ret.commit = s.Value
  114. case "vcs.time":
  115. ret.commitTime = s.Value
  116. if len(s.Value) >= len("yyyy-mm-dd") {
  117. ret.commitDate = s.Value[:len("yyyy-mm-dd")]
  118. ret.commitDate = strings.ReplaceAll(ret.commitDate, "-", "")
  119. }
  120. case "vcs.modified":
  121. ret.dirty = s.Value == "true"
  122. }
  123. }
  124. if ret.commit == "" || ret.commitDate == "" {
  125. // Build info is present in the binary, but has no useful data. Act as
  126. // if it's missing.
  127. return embeddedInfo{}
  128. }
  129. return ret
  130. })
  131. func gitCommit() string {
  132. if gitCommitStamp != "" {
  133. return gitCommitStamp
  134. }
  135. return getEmbeddedInfo().commit
  136. }
  137. func gitDirty() bool {
  138. if gitDirtyStamp {
  139. return true
  140. }
  141. return getEmbeddedInfo().dirty
  142. }
  143. func dirtyString() string {
  144. if gitDirty() {
  145. return "-dirty"
  146. }
  147. return ""
  148. }
  149. func majorMinorPatch() string {
  150. ret, _, _ := strings.Cut(Short(), "-")
  151. return ret
  152. }