Files
syncthing/lib/build/build.go
2026-04-06 09:44:49 +02:00

180 lines
5.0 KiB
Go

// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package build
import (
"fmt"
"log"
"os"
"regexp"
"runtime"
"slices"
"strconv"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var buildInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: "syncthing",
Subsystem: "build",
Name: "info",
Help: "A metric with a constant '1' value labeled by version information from when the binary was built.",
}, []string{"program", "version", "codename", "goversion", "builduser", "builddate", "tags"})
const Codename = "Hafnium Hornet"
var (
// Injected by build script
Version = "unknown-dev"
Host = "unknown"
User = "unknown"
Stamp = "0"
Tags = ""
// Added to by other packages
extraTags []string
// Set by init()
Date time.Time
IsRelease bool
IsCandidate bool
IsBeta bool
LongVersion string
Extra string
allowedVersionExp = regexp.MustCompile(`^v\d+\.\d+\.\d+(-[a-z0-9]+)*(\.\d+)*(\+\d+-g[0-9a-f]+|\+[0-9a-z]+)?(-[^\s]+)?$`)
envTags = []string{
"STGUIASSETS",
"STNORESTART",
"STNOUPGRADE",
}
replaceTags = map[string]string{
"sqlite_omit_load_extension": "",
"sqlite_dbstat": "",
"netgo": "",
"osusergo": "",
}
)
const versionExtraAllowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-. "
func init() {
if Version != "unknown-dev" {
// If not a generic dev build, version string should come from git describe
if !allowedVersionExp.MatchString(Version) {
log.Fatalf("Invalid version string %q;\n\tdoes not match regexp %v", Version, allowedVersionExp)
}
}
setBuildData()
}
func setBuildData() {
// Check for a clean release build. A release is something like
// "v0.1.2", with an optional suffix of letters and dot separated
// numbers like "-beta3.47". If there's more stuff, like a plus sign and
// a commit hash and so on, then it's not a release. If it has a dash in
// it, it's some sort of beta, release candidate or special build. If it
// has "-rc." in it, like "v0.14.35-rc.42", then it's a candidate build.
//
// So, every build that is not a stable release build has IsBeta = true.
// This is used to enable some extra debugging (the deadlock detector).
//
// Release candidate builds are also "betas" from this point of view and
// will have that debugging enabled. In addition, some features are
// forced for release candidates - auto upgrade, and usage reporting.
exp := regexp.MustCompile(`^v\d+\.\d+\.\d+(-[a-z]+[\d\.]+)?$`)
IsRelease = exp.MatchString(Version)
IsCandidate = strings.Contains(Version, "-rc.")
IsBeta = strings.Contains(Version, "-")
Extra = filterString(os.Getenv("STVERSIONEXTRA"), versionExtraAllowedChars)
stamp, _ := strconv.Atoi(Stamp)
Date = time.Unix(int64(stamp), 0)
LongVersion = LongVersionFor("syncthing")
}
// LongVersionFor returns the long version string for the given program name.
func LongVersionFor(program string) string {
// This string and date format is essentially part of our external API. Never change it.
date := Date.UTC().Format("2006-01-02 15:04:05 MST")
v := fmt.Sprintf(`%s %s "%s" (%s %s-%s) %s@%s %s`, program, Version, Codename, runtime.Version(), runtime.GOOS, runtime.GOARCH, User, Host, date)
tags := TagsList()
if len(tags) > 0 {
v = fmt.Sprintf("%s [%s]", v, strings.Join(tags, ", "))
}
buildInfo.Reset()
buildInfo.With(prometheus.Labels{
"program": program,
"version": Version,
"codename": Codename,
"goversion": runtime.Version(),
"builduser": fmt.Sprintf("%s@%s", User, Host),
"builddate": Date.UTC().Format("2006-01-02 15:04:05 MST"),
"tags": strings.Join(tags, ","),
}).Set(1)
return v
}
func TagsList() []string {
tags := strings.Split(Tags, ",")
if len(tags) == 1 && tags[0] == "" {
tags = tags[:0]
}
for _, envVar := range envTags {
if os.Getenv(envVar) != "" {
tags = append(tags, strings.ToLower(envVar))
}
}
if Extra != "" {
tags = append(tags, Extra)
}
tags = append(tags, extraTags...)
// Replace any tag values we want to have more user friendly versions,
// or be removed
for i, tag := range tags {
if repl, ok := replaceTags[tag]; ok {
tags[i] = repl
}
}
slices.Sort(tags)
// Remove any empty tags, which will be at the front of the list now
for len(tags) > 0 && tags[0] == "" {
tags = tags[1:]
}
tags = slices.Compact(tags)
return tags
}
// filterString returns a copy of s with all characters not in allowedChars
// removed.
func filterString(s, allowedChars string) string {
var res strings.Builder
for _, c := range s {
if strings.ContainsRune(allowedChars, c) {
res.WriteRune(c)
}
}
return res.String()
}
func AddTag(tag string) {
extraTags = append(extraTags, tag)
LongVersion = LongVersionFor("syncthing")
}