build.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. // Copyright (C) 2019 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package build
  7. import (
  8. "fmt"
  9. "log"
  10. "os"
  11. "regexp"
  12. "runtime"
  13. "slices"
  14. "strconv"
  15. "strings"
  16. "time"
  17. )
  18. const Codename = "Hafnium Hornet"
  19. var (
  20. // Injected by build script
  21. Version = "unknown-dev"
  22. Host = "unknown"
  23. User = "unknown"
  24. Stamp = "0"
  25. Tags = ""
  26. // Added to by other packages
  27. extraTags []string
  28. // Set by init()
  29. Date time.Time
  30. IsRelease bool
  31. IsCandidate bool
  32. IsBeta bool
  33. LongVersion string
  34. Extra string
  35. allowedVersionExp = regexp.MustCompile(`^v\d+\.\d+\.\d+(-[a-z0-9]+)*(\.\d+)*(\+\d+-g[0-9a-f]+|\+[0-9a-z]+)?(-[^\s]+)?$`)
  36. envTags = []string{
  37. "STGUIASSETS",
  38. "STNORESTART",
  39. "STNOUPGRADE",
  40. }
  41. replaceTags = map[string]string{
  42. "sqlite_omit_load_extension": "",
  43. "sqlite_dbstat": "",
  44. "netgo": "",
  45. "osusergo": "",
  46. }
  47. )
  48. const versionExtraAllowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-. "
  49. func init() {
  50. if Version != "unknown-dev" {
  51. // If not a generic dev build, version string should come from git describe
  52. if !allowedVersionExp.MatchString(Version) {
  53. log.Fatalf("Invalid version string %q;\n\tdoes not match regexp %v", Version, allowedVersionExp)
  54. }
  55. }
  56. setBuildData()
  57. }
  58. func setBuildData() {
  59. // Check for a clean release build. A release is something like
  60. // "v0.1.2", with an optional suffix of letters and dot separated
  61. // numbers like "-beta3.47". If there's more stuff, like a plus sign and
  62. // a commit hash and so on, then it's not a release. If it has a dash in
  63. // it, it's some sort of beta, release candidate or special build. If it
  64. // has "-rc." in it, like "v0.14.35-rc.42", then it's a candidate build.
  65. //
  66. // So, every build that is not a stable release build has IsBeta = true.
  67. // This is used to enable some extra debugging (the deadlock detector).
  68. //
  69. // Release candidate builds are also "betas" from this point of view and
  70. // will have that debugging enabled. In addition, some features are
  71. // forced for release candidates - auto upgrade, and usage reporting.
  72. exp := regexp.MustCompile(`^v\d+\.\d+\.\d+(-[a-z]+[\d\.]+)?$`)
  73. IsRelease = exp.MatchString(Version)
  74. IsCandidate = strings.Contains(Version, "-rc.")
  75. IsBeta = strings.Contains(Version, "-")
  76. Extra = filterString(os.Getenv("STVERSIONEXTRA"), versionExtraAllowedChars)
  77. stamp, _ := strconv.Atoi(Stamp)
  78. Date = time.Unix(int64(stamp), 0)
  79. LongVersion = LongVersionFor("syncthing")
  80. }
  81. // LongVersionFor returns the long version string for the given program name.
  82. func LongVersionFor(program string) string {
  83. // This string and date format is essentially part of our external API. Never change it.
  84. date := Date.UTC().Format("2006-01-02 15:04:05 MST")
  85. v := fmt.Sprintf(`%s %s "%s" (%s %s-%s) %s@%s %s`, program, Version, Codename, runtime.Version(), runtime.GOOS, runtime.GOARCH, User, Host, date)
  86. if tags := TagsList(); len(tags) > 0 {
  87. v = fmt.Sprintf("%s [%s]", v, strings.Join(tags, ", "))
  88. }
  89. return v
  90. }
  91. func TagsList() []string {
  92. tags := strings.Split(Tags, ",")
  93. if len(tags) == 1 && tags[0] == "" {
  94. tags = tags[:0]
  95. }
  96. for _, envVar := range envTags {
  97. if os.Getenv(envVar) != "" {
  98. tags = append(tags, strings.ToLower(envVar))
  99. }
  100. }
  101. if Extra != "" {
  102. tags = append(tags, Extra)
  103. }
  104. tags = append(tags, extraTags...)
  105. // Replace any tag values we want to have more user friendly versions,
  106. // or be removed
  107. for i, tag := range tags {
  108. if repl, ok := replaceTags[tag]; ok {
  109. tags[i] = repl
  110. }
  111. }
  112. slices.Sort(tags)
  113. // Remove any empty tags, which will be at the front of the list now
  114. for len(tags) > 0 && tags[0] == "" {
  115. tags = tags[1:]
  116. }
  117. tags = slices.Compact(tags)
  118. return tags
  119. }
  120. // filterString returns a copy of s with all characters not in allowedChars
  121. // removed.
  122. func filterString(s, allowedChars string) string {
  123. var res strings.Builder
  124. for _, c := range s {
  125. if strings.ContainsRune(allowedChars, c) {
  126. res.WriteRune(c)
  127. }
  128. }
  129. return res.String()
  130. }
  131. func AddTag(tag string) {
  132. extraTags = append(extraTags, tag)
  133. LongVersion = LongVersionFor("syncthing")
  134. }