diff --git a/version/prop.go b/version/prop.go index 0077c4e76..95f3e774d 100644 --- a/version/prop.go +++ b/version/prop.go @@ -4,34 +4,126 @@ package version import ( + "os" + "path/filepath" + "runtime" + "strconv" "strings" + "sync" "tailscale.com/tailcfg" ) // IsMobile reports whether this is a mobile client build. -func IsMobile() bool { return isMobile } +func IsMobile() bool { + return runtime.GOOS == "android" || runtime.GOOS == "ios" +} + +// OS returns runtime.GOOS, except instead of returning "darwin" it returns +// "iOS" or "macOS". +func OS() string { + // If you're wondering why we have this function that just returns + // runtime.GOOS written differently: in the old days, Go reported + // GOOS=darwin for both iOS and macOS, so we needed this function to + // differentiate them. Then a later Go release added GOOS=ios as a separate + // platform, but by then the "iOS" and "macOS" values we'd picked, with that + // exact capitalization, were already baked into databases. + if runtime.GOOS == "ios" { + return "iOS" + } + if runtime.GOOS == "darwin" { + return "macOS" + } + return runtime.GOOS +} + +var ( + macFlavorOnce sync.Once + isMacSysExt bool + isMacSandboxed bool +) -// OS returns runtime.GOOS, except instead of returning "darwin" it -// returns "iOS" or "macOS". -func OS() string { return legacyOS } +func initMacFlavor() { + exe, err := os.Executable() + if err != nil { + return + } + isMacSysExt = filepath.Base(exe) == "io.tailscale.ipn.macsys.network-extension" + isMacSandboxed = isMacSysExt || strings.HasSuffix(exe, "/Contents/MacOS/Tailscale") || strings.HasSuffix(exe, "/Contents/MacOS/IPNExtension") +} // IsSandboxedMacOS reports whether this process is a sandboxed macOS // process (either the app or the extension). It is true for the Mac App Store // and macsys (System Extension) version on macOS, and false for // tailscaled-on-macOS. -func IsSandboxedMacOS() bool { return isSandboxedMacOS } +func IsSandboxedMacOS() bool { + if runtime.GOOS != "darwin" { + return false + } + macFlavorOnce.Do(initMacFlavor) + return isMacSandboxed +} // IsMacSysExt whether this binary is from the standalone "System // Extension" (a.k.a. "macsys") version of Tailscale for macOS. -func IsMacSysExt() bool { return isMacSysExt } +func IsMacSysExt() bool { + if runtime.GOOS != "darwin" { + return false + } + macFlavorOnce.Do(initMacFlavor) + return isMacSysExt +} + +var ( + winFlavorOnce sync.Once + isWindowsGUI bool +) + +func initWinFlavor() { + exe, err := os.Executable() + if err != nil { + return + } + isWindowsGUI = strings.EqualFold(exe, "tailscale-ipn.exe") || strings.EqualFold(exe, "tailscale-ipn") +} // IsWindowsGUI reports whether the current process is the Windows GUI. -func IsWindowsGUI() bool { return isWindowsGUI } +func IsWindowsGUI() bool { + if runtime.GOOS != "windows" { + return false + } + exe, _ := os.Executable() + exe = filepath.Base(exe) + return strings.EqualFold(exe, "tailscale-ipn.exe") || strings.EqualFold(exe, "tailscale-ipn") +} + +var ( + isUnstableOnce sync.Once + isUnstableBuild bool +) // IsUnstableBuild reports whether this is an unstable build. // That is, whether its minor version number is odd. -func IsUnstableBuild() bool { return isUnstable } +func IsUnstableBuild() bool { + isUnstableOnce.Do(initUnstable) + return isUnstableBuild +} + +func initUnstable() { + _, rest, ok := strings.Cut(Short, ".") + if !ok { + return + } + minorStr, _, ok := strings.Cut(rest, ".") + if !ok { + return + } + minor, err := strconv.Atoi(minorStr) + if err != nil { + return + } + isUnstableBuild = minor%2 == 1 +} // Meta is a JSON-serializable type that contains all the version // information. diff --git a/version/version.go b/version/version.go index 9c32e7d87..04e3d79c4 100644 --- a/version/version.go +++ b/version/version.go @@ -5,93 +5,29 @@ package version import ( - "os" - "path/filepath" - "runtime" "runtime/debug" - "strconv" "strings" tailscaleroot "tailscale.com" ) -var ( - // Long is a full version number for this build, of the form - // "x.y.z-commithash" for builds stamped in the usual way (see build_dist.sh - // in the root) or, for binaries built by hand with the go tool, it's of the - // form "1.23.0-dev20220316-t29837428937{,-dirty}" where "1.23.0" comes from - // ../VERSION.txt and the part after dev is YYYYMMDD of the commit time, and - // the part after -t is the commit hash. The dirty suffix is whether there - // are uncommitted changes. - Long string - - // Short is a short version number for this build, of the form - // "x.y.z" for builds stamped in the usual way (see - // build_dist.sh in the root) or, for binaries built by hand with the - // go tool, it's like Long's dev form, but ending at the date part, - // of the form "1.23.0-dev20220316". - Short string - - // GitCommit, if non-empty, is the git commit of the - // github.com/tailscale/tailscale repository at which Tailscale was - // built. Its format is the one returned by `git describe --always - // --exclude "*" --dirty --abbrev=200`. - GitCommit string - - // GitDirty is whether Go stamped the binary as having dirty version - // control changes in the working directory (debug.ReadBuildInfo - // setting "vcs.modified" was true). - GitDirty bool - - // ExtraGitCommit, if non-empty, 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. - ExtraGitCommit = "" - - // isUnstable is whether the current build appears to be an unstable, i.e. with - // an odd minor version number. - isUnstable bool - - // legacyOS is runtime.GOOS, except on apple devices where it's either "iOS" or - // "macOS" (with that exact case). - // - // This used to be a thing because Go reported both macOS and iOS as "darwin" - // and we needed to tell them apart. But then Go learned GOOS=ios and - // GOOS=darwin as separate things, but we're still stuck with this function - // because of the odd casing we picked, which has ossified into databases. - legacyOS string - - // isMobile is whether the current build is for a mobile device. - isMobile bool - - // isSandboxedMacOS is whether the current binary is any binary in the mac store - // or standalone sysext mac apps. - isSandboxedMacOS bool - - // isMacSysExt is whether the current binary is the mac system extension binary. - isMacSysExt bool - - // isWindowsGUI is whether the current binary is the Windows GUI binary. - isWindowsGUI bool - - // majorMinorPatch is the major.minor.patch portion of Short. - majorMinorPatch string -) +// Long is a full version number for this build, of the form +// "x.y.z-commithash" for builds stamped in the usual way (see +// build_dist.sh in the root) or, for binaries built by hand with the +// go tool, it's of the form "1.23.0-dev20220316-t29837428937{,-dirty}" +// where "1.23.0" comes from ../VERSION.txt and the part after dev +// is YYYYMMDD of the commit time, and the part after -t is the commit +// hash. The dirty suffix is whether there are uncommitted changes. +var Long = "" + +// Short is a short version number for this build, of the form +// "x.y.z" for builds stamped in the usual way (see +// build_dist.sh in the root) or, for binaries built by hand with the +// go tool, it's like Long's dev form, but ending at the date part, +// of the form "1.23.0-dev20220316". +var Short = "" func init() { - initVersion() - initUnstable() - initMiscTraits() -} - -func initVersion() { defer func() { // Must be run after Short has been initialized, easiest way to do that // is a defer. @@ -139,38 +75,28 @@ func initVersion() { Long = Short + "-t" + commitHashAbbrev + dirty } -func initUnstable() { - _, rest, ok := strings.Cut(Short, ".") - if !ok { - return - } - minorStr, _, ok := strings.Cut(rest, ".") - if !ok { - return - } - minor, err := strconv.Atoi(minorStr) - if err != nil { - return - } - isUnstable = minor%2 == 1 -} - -func initMiscTraits() { - exe, _ := os.Executable() - base := filepath.Base(exe) - - legacyOS = runtime.GOOS - switch runtime.GOOS { - case "darwin": - legacyOS = "macOS" - isMacSysExt = strings.HasPrefix(base, "io.tailscale.ipn.macsys.network-extension") - isSandboxedMacOS = isMacSysExt || strings.HasSuffix(exe, "/Contents/MacOS/Tailscale") || strings.HasSuffix(exe, "/Contents/MacOS/IPNExtension") - case "ios": - legacyOS = "iOS" - isMobile = true - case "android": - isMobile = true - case "windows": - isWindowsGUI = strings.EqualFold(base, "tailscale-ipn.exe") || strings.EqualFold(base, "tailscale-ipn") - } -} +// GitCommit, if non-empty, is the git commit of the +// github.com/tailscale/tailscale repository at which Tailscale was +// built. Its format is the one returned by `git describe --always +// --exclude "*" --dirty --abbrev=200`. +var GitCommit = "" + +// GitDirty is whether Go stamped the binary as having dirty version +// control changes in the working directory (debug.ReadBuildInfo +// setting "vcs.modified" was true). +var GitDirty bool + +// ExtraGitCommit, if non-empty, 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. +var ExtraGitCommit = "" + +// majorMinorPatch is the major.minor.patch portion of Short. +var majorMinorPatch string