feature/featuretags,cmd/omitsize: support feature dependencies
This produces the following omitsizes output:
Starting with everything and removing a feature...
tailscaled tailscale combined (linux/amd64)
27005112 18153656 39727288
- 7696384 - 7282688 -19607552 .. remove *
- 167936 - 110592 - 245760 .. remove acme
- 1925120 - 0 - 7340032 .. remove aws
- 4096 - 0 - 8192 .. remove bird
- 20480 - 12288 - 32768 .. remove capture
- 0 - 57344 - 61440 .. remove completion
- 249856 - 696320 - 692224 .. remove debugeventbus
- 12288 - 4096 - 24576 .. remove debugportmapper
- 0 - 0 - 0 .. remove desktop_sessions
- 815104 - 8192 - 544768 .. remove drive
- 65536 - 356352 - 425984 .. remove kube
- 233472 - 286720 - 311296 .. remove portmapper (and debugportmapper)
- 90112 - 0 - 110592 .. remove relayserver
- 655360 - 712704 - 598016 .. remove serve (and webclient)
- 937984 - 0 - 950272 .. remove ssh
- 708608 - 401408 - 344064 .. remove syspolicy
- 0 - 4071424 -11132928 .. remove systray
- 159744 - 61440 - 225280 .. remove taildrop
- 618496 - 454656 - 757760 .. remove tailnetlock
- 122880 - 0 - 131072 .. remove tap
- 442368 - 0 - 483328 .. remove tpm
- 16384 - 0 - 20480 .. remove wakeonlan
- 278528 - 368640 - 286720 .. remove webclient
Starting at a minimal binary and adding one feature back...
tailscaled tailscale combined (linux/amd64)
19308728 10870968 20119736 omitting everything
+ 352256 + 454656 + 643072 .. add acme
+ 2035712 + 0 + 2035712 .. add aws
+ 8192 + 0 + 8192 .. add bird
+ 20480 + 12288 + 36864 .. add capture
+ 0 + 57344 + 61440 .. add completion
+ 262144 + 274432 + 266240 .. add debugeventbus
+ 344064 + 118784 + 360448 .. add debugportmapper (and portmapper)
+ 0 + 0 + 0 .. add desktop_sessions
+ 978944 + 8192 + 991232 .. add drive
+ 61440 + 364544 + 425984 .. add kube
+ 331776 + 110592 + 335872 .. add portmapper
+ 122880 + 0 + 102400 .. add relayserver
+ 598016 + 155648 + 737280 .. add serve
+ 1142784 + 0 + 1142784 .. add ssh
+ 708608 + 860160 + 720896 .. add syspolicy
+ 0 + 4079616 + 6221824 .. add systray
+ 180224 + 65536 + 237568 .. add taildrop
+ 647168 + 393216 + 720896 .. add tailnetlock
+ 122880 + 0 + 126976 .. add tap
+ 446464 + 0 + 454656 .. add tpm
+ 20480 + 0 + 24576 .. add wakeonlan
+ 1011712 + 1011712 + 1138688 .. add webclient (and serve)
Fixes #17139
Change-Id: Ia91be2da00de8481a893243d577d20e988a0920a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
4f211ea5c5
commit
78035fb9d2
@@ -4,6 +4,8 @@
|
||||
// The featuretags package is a registry of all the ts_omit-able build tags.
|
||||
package featuretags
|
||||
|
||||
import "tailscale.com/util/set"
|
||||
|
||||
// CLI is a special feature in the [Features] map that works opposite
|
||||
// from the others: it is opt-in, rather than opt-out, having a different
|
||||
// build tag format.
|
||||
@@ -32,37 +34,90 @@ func (ft FeatureTag) OmitTag() string {
|
||||
return "ts_omit_" + string(ft)
|
||||
}
|
||||
|
||||
// Requires returns the set of features that must be included to
|
||||
// use the given feature, including the provided feature itself.
|
||||
func Requires(ft FeatureTag) set.Set[FeatureTag] {
|
||||
s := set.Set[FeatureTag]{}
|
||||
var add func(FeatureTag)
|
||||
add = func(ft FeatureTag) {
|
||||
if !ft.IsOmittable() {
|
||||
return
|
||||
}
|
||||
s.Add(ft)
|
||||
for _, dep := range Features[ft].Deps {
|
||||
add(dep)
|
||||
}
|
||||
}
|
||||
add(ft)
|
||||
return s
|
||||
}
|
||||
|
||||
// RequiredBy is the inverse of Requires: it returns the set of features that
|
||||
// depend on the given feature (directly or indirectly), including the feature
|
||||
// itself.
|
||||
func RequiredBy(ft FeatureTag) set.Set[FeatureTag] {
|
||||
s := set.Set[FeatureTag]{}
|
||||
for f := range Features {
|
||||
if featureDependsOn(f, ft) {
|
||||
s.Add(f)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// featureDependsOn reports whether feature a (directly or indirectly) depends on b.
|
||||
// It returns true if a == b.
|
||||
func featureDependsOn(a, b FeatureTag) bool {
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
for _, dep := range Features[a].Deps {
|
||||
if featureDependsOn(dep, b) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FeatureMeta describes a modular feature that can be conditionally linked into
|
||||
// the binary.
|
||||
type FeatureMeta struct {
|
||||
Sym string // exported Go symbol for boolean const
|
||||
Desc string // human-readable description
|
||||
Sym string // exported Go symbol for boolean const
|
||||
Desc string // human-readable description
|
||||
Deps []FeatureTag // other features this feature requires
|
||||
}
|
||||
|
||||
// Features are the known Tailscale features that can be selectively included or
|
||||
// excluded via build tags, and a description of each.
|
||||
var Features = map[FeatureTag]FeatureMeta{
|
||||
"acme": {"ACME", "ACME TLS certificate management"},
|
||||
"aws": {"AWS", "AWS integration"},
|
||||
"bird": {"Bird", "Bird BGP integration"},
|
||||
"capture": {"Capture", "Packet capture"},
|
||||
"cli": {"CLI", "embed the CLI into the tailscaled binary"},
|
||||
"completion": {"Completion", "CLI shell completion"},
|
||||
"debugeventbus": {"DebugEventBus", "eventbus debug support"},
|
||||
"debugportmapper": {"DebugPortMapper", "portmapper debug support"},
|
||||
"desktop_sessions": {"DesktopSessions", "Desktop sessions support"},
|
||||
"drive": {"Drive", "Tailscale Drive (file server) support"},
|
||||
"kube": {"Kube", "Kubernetes integration"},
|
||||
"portmapper": {"PortMapper", "NAT-PMP/PCP/UPnP port mapping support"},
|
||||
"relayserver": {"RelayServer", "Relay server"},
|
||||
"serve": {"Serve", "Serve and Funnel support"},
|
||||
"ssh": {"SSH", "Tailscale SSH support"},
|
||||
"syspolicy": {"SystemPolicy", "System policy configuration (MDM) support"},
|
||||
"systray": {"SysTray", "Linux system tray"},
|
||||
"taildrop": {"Taildrop", "Taildrop (file sending) support"},
|
||||
"tailnetlock": {"TailnetLock", "Tailnet Lock support"},
|
||||
"tap": {"Tap", "Experimental Layer 2 (ethernet) support"},
|
||||
"tpm": {"TPM", "TPM support"},
|
||||
"wakeonlan": {"WakeOnLAN", "Wake-on-LAN support"},
|
||||
"webclient": {"WebClient", "Web client support"},
|
||||
"acme": {"ACME", "ACME TLS certificate management", nil},
|
||||
"aws": {"AWS", "AWS integration", nil},
|
||||
"bird": {"Bird", "Bird BGP integration", nil},
|
||||
"capture": {"Capture", "Packet capture", nil},
|
||||
"cli": {"CLI", "embed the CLI into the tailscaled binary", nil},
|
||||
"completion": {"Completion", "CLI shell completion", nil},
|
||||
"debugeventbus": {"DebugEventBus", "eventbus debug support", nil},
|
||||
"debugportmapper": {
|
||||
Sym: "DebugPortMapper",
|
||||
Desc: "portmapper debug support",
|
||||
Deps: []FeatureTag{"portmapper"},
|
||||
},
|
||||
"desktop_sessions": {"DesktopSessions", "Desktop sessions support", nil},
|
||||
"drive": {"Drive", "Tailscale Drive (file server) support", nil},
|
||||
"kube": {"Kube", "Kubernetes integration", nil},
|
||||
"portmapper": {"PortMapper", "NAT-PMP/PCP/UPnP port mapping support", nil},
|
||||
"relayserver": {"RelayServer", "Relay server", nil},
|
||||
"serve": {"Serve", "Serve and Funnel support", nil},
|
||||
"ssh": {"SSH", "Tailscale SSH support", nil},
|
||||
"syspolicy": {"SystemPolicy", "System policy configuration (MDM) support", nil},
|
||||
"systray": {"SysTray", "Linux system tray", nil},
|
||||
"taildrop": {"Taildrop", "Taildrop (file sending) support", nil},
|
||||
"tailnetlock": {"TailnetLock", "Tailnet Lock support", nil},
|
||||
"tap": {"Tap", "Experimental Layer 2 (ethernet) support", nil},
|
||||
"tpm": {"TPM", "TPM support", nil},
|
||||
"wakeonlan": {"WakeOnLAN", "Wake-on-LAN support", nil},
|
||||
"webclient": {
|
||||
Sym: "WebClient", Desc: "Web client support",
|
||||
Deps: []FeatureTag{"serve"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package featuretags
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/util/set"
|
||||
)
|
||||
|
||||
func TestRequires(t *testing.T) {
|
||||
for tag, meta := range Features {
|
||||
for _, dep := range meta.Deps {
|
||||
if _, ok := Features[dep]; !ok {
|
||||
t.Errorf("feature %q has unknown dependency %q", tag, dep)
|
||||
}
|
||||
}
|
||||
|
||||
// And indirectly check for cycles. If there were a cycle,
|
||||
// this would infinitely loop.
|
||||
deps := Requires(tag)
|
||||
t.Logf("deps of %q: %v", tag, slices.Sorted(maps.Keys(deps)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDepSet(t *testing.T) {
|
||||
var setOf = set.Of[FeatureTag]
|
||||
tests := []struct {
|
||||
in FeatureTag
|
||||
want set.Set[FeatureTag]
|
||||
}{
|
||||
{
|
||||
in: "drive",
|
||||
want: setOf("drive"),
|
||||
},
|
||||
{
|
||||
in: "serve",
|
||||
want: setOf("serve"),
|
||||
},
|
||||
{
|
||||
in: "webclient",
|
||||
want: setOf("webclient", "serve"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := Requires(tt.in)
|
||||
if !maps.Equal(got, tt.want) {
|
||||
t.Errorf("DepSet(%q) = %v, want %v", tt.in, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiredBy(t *testing.T) {
|
||||
var setOf = set.Of[FeatureTag]
|
||||
tests := []struct {
|
||||
in FeatureTag
|
||||
want set.Set[FeatureTag]
|
||||
}{
|
||||
{
|
||||
in: "drive",
|
||||
want: setOf("drive"),
|
||||
},
|
||||
{
|
||||
in: "webclient",
|
||||
want: setOf("webclient"),
|
||||
},
|
||||
{
|
||||
in: "serve",
|
||||
want: setOf("webclient", "serve"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := RequiredBy(tt.in)
|
||||
if !maps.Equal(got, tt.want) {
|
||||
t.Errorf("FeaturesWhichDependOn(%q) = %v, want %v", tt.in, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user