Skip to content

Commit 377f6dd

Browse files
committed
Don't use templating to allow for dead-code elimination
When templating is used, the linker cannot know which functions are called and which one are not; this is because tamplating use calls to "MethodByName()" which can be used to call any function (similar to reflection). With the release of Cobra 1.9.1, templates are no longer used by default in Cobra. By also not using templates in the tanzu-plugin-runtime it now gives an opportunity for plugins to try to avoid templates to allow dead-code elimination to work, if they so choose. Ref: spf13/cobra#1956 Signed-off-by: Marc Khouzam <[email protected]>
1 parent 44e67d0 commit 377f6dd

File tree

3 files changed

+76
-11
lines changed

3 files changed

+76
-11
lines changed

plugin/root.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,12 @@ func newRootCmd(descriptor *PluginDescriptor) *cobra.Command {
4545
cobra.CommandDisplayNameAnnotation: cmdName,
4646
},
4747
}
48+
// Instead of using templates, use go functions.
49+
// This allows for dead-code-elimination.
50+
// The below call will set the format for both usage and help printouts.
51+
cmd.SetUsageFunc(UsageFunc)
52+
// Keep this call for backwards-compatibility, in case a plugin uses the templating
4853
cobra.AddTemplateFuncs(TemplateFuncs)
49-
cmd.SetUsageTemplate(cmdTemplate)
5054

5155
cmd.AddCommand(
5256
newDescribeCmd(descriptor.Description),

plugin/root_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
package plugin
55

66
import (
7+
"os"
8+
"os/exec"
9+
"path/filepath"
10+
"runtime"
11+
"strings"
712
"testing"
813

914
"github.com/stretchr/testify/assert"
@@ -27,3 +32,63 @@ func Test_newRootCmd(t *testing.T) {
2732
assert.Equal("Test Plugin", cmd.Use)
2833
assert.Equal(("Description of the plugin"), cmd.Short)
2934
}
35+
36+
// TestDeadcodeElimination checks that a simple program using the tanzu-plugin-runtime
37+
// is linked taking full advantage of the linker's deadcode elimination step.
38+
//
39+
// If reflect.Value.MethodByName/reflect.Value.Method are reachable the
40+
// linker will not always be able to prove that exported methods are
41+
// unreachable, making deadcode elimination less effective. Using
42+
// text/template and html/template makes reflect.Value.MethodByName
43+
// reachable.
44+
//
45+
// This test checks that those function can be proven to be unreachable by
46+
// the linker.
47+
//
48+
// Taken from https://github.com/spf13/cobra/blob/f98cf4216d3cb5235e6e0cd00ee00959deb1dc65/cobra_test.go#L245
49+
// See also: https://github.com/spf13/cobra/pull/1956
50+
func TestDeadcodeElimination(t *testing.T) {
51+
if runtime.GOOS == "windows" {
52+
t.Skip("go tool nm fails on windows")
53+
}
54+
55+
// check that a simple program using tanzu-plugin-runtime is
56+
// linked with deadcode elimination enabled.
57+
const (
58+
dirname = "test_deadcode"
59+
progname = "test_deadcode_elimination"
60+
)
61+
_ = os.Mkdir(dirname, 0770)
62+
defer os.RemoveAll(dirname)
63+
filename := filepath.Join(dirname, progname+".go")
64+
err := os.WriteFile(filename, []byte(`package main
65+
66+
import "github.com/vmware-tanzu/tanzu-plugin-runtime/plugin"
67+
68+
func main() {
69+
p, _ := plugin.NewPlugin(&plugin.PluginDescriptor{
70+
Name: "deadcode-check",
71+
Description: "some desc",
72+
Group: "Manage",
73+
Target: "global",
74+
Version: "v0.0.0",
75+
})
76+
_ = p.Execute()
77+
}
78+
`), 0600)
79+
if err != nil {
80+
t.Fatalf("could not write test program: %v", err)
81+
}
82+
buf, err := exec.Command("go", "build", filename).CombinedOutput()
83+
if err != nil {
84+
t.Fatalf("could not compile test program: %s", string(buf))
85+
}
86+
defer os.Remove(progname)
87+
buf, err = exec.Command("go", "tool", "nm", progname).CombinedOutput()
88+
if err != nil {
89+
t.Fatalf("could not run go tool nm: %v", err)
90+
}
91+
if strings.Contains(string(buf), "MethodByName") {
92+
t.Error("compiled programs contains MethodByName symbol")
93+
}
94+
}

plugin/usage.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package plugin
55

66
import (
77
"fmt"
8-
"os"
98
"strings"
109
"text/template"
1110

@@ -17,11 +16,11 @@ import (
1716

1817
// UsageFunc is the usage func for a plugin.
1918
var UsageFunc = func(c *cobra.Command) error {
20-
t, err := template.New("usage").Funcs(TemplateFuncs).Parse(cmdTemplate)
21-
if err != nil {
22-
return err
23-
}
24-
return t.Execute(os.Stdout, c)
19+
// Instead of using templates, use a go function to generate the usage string.
20+
// This allows for dead-code-elimination.
21+
helpMsg := printHelp(c)
22+
_, err := fmt.Fprintf(c.OutOrStdout(), "%s", helpMsg)
23+
return err
2524
}
2625

2726
// CmdTemplate is the template for plugin commands.
@@ -50,9 +49,6 @@ const CmdTemplate = `{{ bold "Usage:" }}
5049
{{ $target := index .Annotations "target" }}{{ if or (eq $target "kubernetes") (eq $target "k8s") }}Use "{{if beginsWith .CommandPath "tanzu "}}{{.CommandPath}}{{else}}tanzu {{.CommandPath}}{{end}} [command] --help" for more information about a command.{{end}}Use "{{if beginsWith .CommandPath "tanzu "}}{{.CommandPath}}{{else}}tanzu{{ $target := index .Annotations "target" }}{{ if and (ne $target "global") (ne $target "") }} {{ $target }} {{ else }} {{ end }}{{.CommandPath}}{{end}} [command] --help" for more information about a command.{{end}}
5150
`
5251

53-
// cmdTemplate is the template for plugin commands.
54-
const cmdTemplate = `{{ printHelp . }}`
55-
5652
// Constants for help text labels
5753
const (
5854
usageStr = "Usage:"
@@ -367,9 +363,9 @@ func printHelp(cmd *cobra.Command) string {
367363

368364
// TemplateFuncs are the template usage funcs.
369365
var TemplateFuncs = template.FuncMap{
370-
"printHelp": printHelp,
371366
// The below are not needed but are kept for backwards-compatibility
372367
// in case it is being used through the API
368+
"printHelp": printHelp,
373369
"rpad": stringutils.Rpad,
374370
"bold": stringutils.Sboldf,
375371
"underline": stringutils.Sunderlinef,

0 commit comments

Comments
 (0)