From a3215f1f9d3afd4a35973e4df12dc5fca87a3056 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sun, 8 Feb 2026 04:46:09 +0000 Subject: [PATCH] cmd/tailscale,feature/featuretags: make webbrowser and colorable deps omittable Add new "webbrowser" and "colorable" feature tags so that the github.com/toqueteos/webbrowser and mattn/go-colorable packages can be excluded from minbox builds. Updates #12614 Change-Id: Iabd38b242f5a56aa10ef2050113785283f4e1fe8 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/cli.go | 16 ----------- cmd/tailscale/cli/colorable.go | 28 +++++++++++++++++++ cmd/tailscale/cli/colorable_omit.go | 12 ++++++++ cmd/tailscale/cli/open_browser.go | 12 ++++++++ cmd/tailscale/cli/status.go | 7 +++-- cmd/tailscaled/depaware-minbox.txt | 4 +-- cmd/tailscaled/deps_test.go | 2 ++ .../feature_colorable_disabled.go | 13 +++++++++ .../feature_colorable_enabled.go | 13 +++++++++ .../feature_webbrowser_disabled.go | 13 +++++++++ .../feature_webbrowser_enabled.go | 13 +++++++++ feature/featuretags/featuretags.go | 9 ++++-- 12 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 cmd/tailscale/cli/colorable.go create mode 100644 cmd/tailscale/cli/colorable_omit.go create mode 100644 cmd/tailscale/cli/open_browser.go create mode 100644 feature/buildfeatures/feature_colorable_disabled.go create mode 100644 feature/buildfeatures/feature_colorable_enabled.go create mode 100644 feature/buildfeatures/feature_webbrowser_disabled.go create mode 100644 feature/buildfeatures/feature_webbrowser_enabled.go diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 1ba66531a..fda6b4546 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -21,8 +21,6 @@ import ( "text/tabwriter" "time" - "github.com/mattn/go-colorable" - "github.com/mattn/go-isatty" "github.com/peterbourgon/ff/v3/ffcli" "tailscale.com/client/local" "tailscale.com/cmd/tailscale/cli/ffcomplete" @@ -484,20 +482,6 @@ func countFlags(fs *flag.FlagSet) (n int) { return n } -// colorableOutput returns a colorable writer if stdout is a terminal (not, say, -// redirected to a file or pipe), the Stdout writer is os.Stdout (we're not -// embedding the CLI in wasm or a mobile app), and NO_COLOR is not set (see -// https://no-color.org/). If any of those is not the case, ok is false -// and w is Stdout. -func colorableOutput() (w io.Writer, ok bool) { - if Stdout != os.Stdout || - os.Getenv("NO_COLOR") != "" || - !isatty.IsTerminal(os.Stdout.Fd()) { - return Stdout, false - } - return colorable.NewColorableStdout(), true -} - type commandDoc struct { Name string Desc string diff --git a/cmd/tailscale/cli/colorable.go b/cmd/tailscale/cli/colorable.go new file mode 100644 index 000000000..6ecd36b1a --- /dev/null +++ b/cmd/tailscale/cli/colorable.go @@ -0,0 +1,28 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !ts_omit_colorable + +package cli + +import ( + "io" + "os" + + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" +) + +// colorableOutput returns a colorable writer if stdout is a terminal (not, say, +// redirected to a file or pipe), the Stdout writer is os.Stdout (we're not +// embedding the CLI in wasm or a mobile app), and NO_COLOR is not set (see +// https://no-color.org/). If any of those is not the case, ok is false +// and w is Stdout. +func colorableOutput() (w io.Writer, ok bool) { + if Stdout != os.Stdout || + os.Getenv("NO_COLOR") != "" || + !isatty.IsTerminal(os.Stdout.Fd()) { + return Stdout, false + } + return colorable.NewColorableStdout(), true +} diff --git a/cmd/tailscale/cli/colorable_omit.go b/cmd/tailscale/cli/colorable_omit.go new file mode 100644 index 000000000..a821bdbbd --- /dev/null +++ b/cmd/tailscale/cli/colorable_omit.go @@ -0,0 +1,12 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build ts_omit_colorable + +package cli + +import "io" + +func colorableOutput() (w io.Writer, ok bool) { + return Stdout, false +} diff --git a/cmd/tailscale/cli/open_browser.go b/cmd/tailscale/cli/open_browser.go new file mode 100644 index 000000000..a006b9765 --- /dev/null +++ b/cmd/tailscale/cli/open_browser.go @@ -0,0 +1,12 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !ts_omit_webbrowser + +package cli + +import "github.com/toqueteos/webbrowser" + +func init() { + hookOpenURL.Set(webbrowser.Open) +} diff --git a/cmd/tailscale/cli/status.go b/cmd/tailscale/cli/status.go index ae4df4da9..49c565feb 100644 --- a/cmd/tailscale/cli/status.go +++ b/cmd/tailscale/cli/status.go @@ -18,7 +18,6 @@ import ( "text/tabwriter" "github.com/peterbourgon/ff/v3/ffcli" - "github.com/toqueteos/webbrowser" "golang.org/x/net/idna" "tailscale.com/feature" "tailscale.com/ipn" @@ -113,7 +112,9 @@ func runStatus(ctx context.Context, args []string) error { ln.Close() }() if statusArgs.browser { - go webbrowser.Open(statusURL) + if f, ok := hookOpenURL.GetOk(); ok { + go f(statusURL) + } } err = http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.RequestURI != "/" { @@ -252,6 +253,8 @@ func runStatus(ctx context.Context, args []string) error { return nil } +var hookOpenURL feature.Hook[func(string) error] + var hookPrintFunnelStatus feature.Hook[func(context.Context)] // isRunningOrStarting reports whether st is in state Running or Starting. diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index f087e6809..15ba39dba 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -21,8 +21,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de github.com/klauspost/compress/internal/snapref from github.com/klauspost/compress/zstd github.com/klauspost/compress/zstd from tailscale.com/util/zstdframe github.com/klauspost/compress/zstd/internal/xxhash from github.com/klauspost/compress/zstd - github.com/mattn/go-colorable from tailscale.com/cmd/tailscale/cli - github.com/mattn/go-isatty from github.com/mattn/go-colorable+ + github.com/mattn/go-isatty from tailscale.com/util/prompt 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+ 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+ 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink @@ -38,7 +37,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+ github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device 💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+ - github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli 💣 go4.org/mem from tailscale.com/control/controlbase+ go4.org/netipx from tailscale.com/ipn/ipnlocal+ tailscale.com from tailscale.com/version diff --git a/cmd/tailscaled/deps_test.go b/cmd/tailscaled/deps_test.go index 596985043..c7ab01298 100644 --- a/cmd/tailscaled/deps_test.go +++ b/cmd/tailscaled/deps_test.go @@ -295,6 +295,8 @@ func TestMinTailscaledWithCLI(t *testing.T) { "archive/tar": "unexpected archive/tar dep", "tailscale.com/feature/conn25": "unexpected conn25 dep", "regexp": "unexpected regexp dep; bloats binary", + "github.com/toqueteos/webbrowser": "unexpected webbrowser dep with ts_omit_webbrowser", + "github.com/mattn/go-colorable": "unexpected go-colorable dep with ts_omit_colorable", }, }.Check(t) } diff --git a/feature/buildfeatures/feature_colorable_disabled.go b/feature/buildfeatures/feature_colorable_disabled.go new file mode 100644 index 000000000..3a7bc5423 --- /dev/null +++ b/feature/buildfeatures/feature_colorable_disabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build ts_omit_colorable + +package buildfeatures + +// HasColorable is whether the binary was built with support for modular feature "Colorized terminal output". +// Specifically, it's whether the binary was NOT built with the "ts_omit_colorable" build tag. +// It's a const so it can be used for dead code elimination. +const HasColorable = false diff --git a/feature/buildfeatures/feature_colorable_enabled.go b/feature/buildfeatures/feature_colorable_enabled.go new file mode 100644 index 000000000..b6a08366e --- /dev/null +++ b/feature/buildfeatures/feature_colorable_enabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build !ts_omit_colorable + +package buildfeatures + +// HasColorable is whether the binary was built with support for modular feature "Colorized terminal output". +// Specifically, it's whether the binary was NOT built with the "ts_omit_colorable" build tag. +// It's a const so it can be used for dead code elimination. +const HasColorable = true diff --git a/feature/buildfeatures/feature_webbrowser_disabled.go b/feature/buildfeatures/feature_webbrowser_disabled.go new file mode 100644 index 000000000..e6484479c --- /dev/null +++ b/feature/buildfeatures/feature_webbrowser_disabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build ts_omit_webbrowser + +package buildfeatures + +// HasWebBrowser is whether the binary was built with support for modular feature "Open URLs in the user's web browser". +// Specifically, it's whether the binary was NOT built with the "ts_omit_webbrowser" build tag. +// It's a const so it can be used for dead code elimination. +const HasWebBrowser = false diff --git a/feature/buildfeatures/feature_webbrowser_enabled.go b/feature/buildfeatures/feature_webbrowser_enabled.go new file mode 100644 index 000000000..68d80b49f --- /dev/null +++ b/feature/buildfeatures/feature_webbrowser_enabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build !ts_omit_webbrowser + +package buildfeatures + +// HasWebBrowser is whether the binary was built with support for modular feature "Open URLs in the user's web browser". +// Specifically, it's whether the binary was NOT built with the "ts_omit_webbrowser" build tag. +// It's a const so it can be used for dead code elimination. +const HasWebBrowser = true diff --git a/feature/featuretags/featuretags.go b/feature/featuretags/featuretags.go index 45daaec5e..4220c02b7 100644 --- a/feature/featuretags/featuretags.go +++ b/feature/featuretags/featuretags.go @@ -84,7 +84,7 @@ type FeatureMeta struct { Deps []FeatureTag // other features this feature requires // ImplementationDetail is whether the feature is an internal implementation - // detail. That is, it's not something a user wuold care about having or not + // detail. That is, it's not something a user would care about having or not // having, but we'd like to able to omit from builds if no other // user-visible features depend on it. ImplementationDetail bool @@ -130,6 +130,7 @@ var Features = map[FeatureTag]FeatureMeta{ "captiveportal": {Sym: "CaptivePortal", Desc: "Captive portal detection"}, "capture": {Sym: "Capture", Desc: "Packet capture"}, "cli": {Sym: "CLI", Desc: "embed the CLI into the tailscaled binary"}, + "colorable": {Sym: "Colorable", Desc: "Colorized terminal output"}, "cliconndiag": {Sym: "CLIConnDiag", Desc: "CLI connection error diagnostics"}, "clientmetrics": {Sym: "ClientMetrics", Desc: "Client metrics support"}, "clientupdate": { @@ -256,7 +257,7 @@ var Features = map[FeatureTag]FeatureMeta{ "systray": { Sym: "SysTray", Desc: "Linux system tray", - Deps: []FeatureTag{"dbus"}, + Deps: []FeatureTag{"dbus", "webbrowser"}, }, "taildrop": { Sym: "Taildrop", @@ -290,6 +291,10 @@ var Features = map[FeatureTag]FeatureMeta{ Desc: "Usermetrics (documented, stable) metrics support", }, "wakeonlan": {Sym: "WakeOnLAN", Desc: "Wake-on-LAN support"}, + "webbrowser": { + Sym: "WebBrowser", + Desc: "Open URLs in the user's web browser", + }, "webclient": { Sym: "WebClient", Desc: "Web client support", Deps: []FeatureTag{"serve"},