version.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  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. dirty bool
  96. }
  97. func (i embeddedInfo) commitAbbrev() string {
  98. if len(i.commit) >= 9 {
  99. return i.commit[:9]
  100. }
  101. return i.commit
  102. }
  103. var getEmbeddedInfo = lazy.SyncFunc(func() embeddedInfo {
  104. bi, ok := debug.ReadBuildInfo()
  105. if !ok {
  106. return embeddedInfo{}
  107. }
  108. ret := embeddedInfo{valid: true}
  109. for _, s := range bi.Settings {
  110. switch s.Key {
  111. case "vcs.revision":
  112. ret.commit = s.Value
  113. case "vcs.time":
  114. if len(s.Value) >= len("yyyy-mm-dd") {
  115. ret.commitDate = s.Value[:len("yyyy-mm-dd")]
  116. ret.commitDate = strings.ReplaceAll(ret.commitDate, "-", "")
  117. }
  118. case "vcs.modified":
  119. ret.dirty = s.Value == "true"
  120. }
  121. }
  122. if ret.commit == "" || ret.commitDate == "" {
  123. // Build info is present in the binary, but has no useful data. Act as
  124. // if it's missing.
  125. return embeddedInfo{}
  126. }
  127. return ret
  128. })
  129. func gitCommit() string {
  130. if gitCommitStamp != "" {
  131. return gitCommitStamp
  132. }
  133. return getEmbeddedInfo().commit
  134. }
  135. func gitDirty() bool {
  136. if gitDirtyStamp {
  137. return true
  138. }
  139. return getEmbeddedInfo().dirty
  140. }
  141. func dirtyString() string {
  142. if gitDirty() {
  143. return "-dirty"
  144. }
  145. return ""
  146. }
  147. func majorMinorPatch() string {
  148. ret, _, _ := strings.Cut(Short(), "-")
  149. return ret
  150. }