Skip to content

Commit

Permalink
Strip CLI and plugin binaries to reduce their size (#596)
Browse files Browse the repository at this point in the history
* Use the local "builder" version

When running the tests for the sample-plugin, the "builder" plugin needs
to be installed. However, it may happen that there was a change in the
sample-plugin Makefile and the builder plugin code.  This means we must
use the local "builder" version and not the one currently published to
the central repository.

This commit does this by:
1. building the "builder" plugin under bin/
2. using bin/builder to build the "builder" plugin so it can be
   installed under the tanzu CLI

Signed-off-by: Marc Khouzam <[email protected]>

* Optimize CLI and plugin binaries to reduce their size

Overall binary reduction achieved by this commit:
- 28% decrease for Darwin
- 36% decrease for Linux and Windows

This commit does two optimizations described below.

Note that these optimizations are done directly within the builder
plugin and not as part of the "plugin-tooling.mk.tmpl" file. This
approach will make it easier for plugin authors to get their hands on
these improvements, without having to update their copy of the
"plugin-tooling.mk" file (which they may not do very often).

Optimizations:
=============
1. Remove symbol tables (reduce about 30% for Linux/Windows, 22% for Mac)
--
When compiling the CLI and plugins, this commit adds the "-s" and "-w"
flags.  The "-s" flag omits the symbol table and debug information
while "-w" omits the DWARF symbol table.  These symbol tables are used
by debuggers.  The impact in removing them will be that it will not be
possible to use a debugger on a production binary; this is acceptable
and it is actually a standard approach to ship production binaries
without debug symbols.  If a debugger needs to be used, a binary can
built with the symbol tables.

Note that the removal of debug symbols does not prevent proper traces
from being shown in a go "panic".

To build a CLI with debug symbols use TANZU_CLI_ENABLE_DEBUG=1
To build plugins with debug symbols use PLUGIN_ENABLE_DEBUG=1

2. Disable function inlining (reduce about 6% on every OS)
--
Adding the flag "-gcflags=all=-l" removes function inlining.  This
normally reduces performance a little but saves space.  Considering we
are dealing with a CLI tool which does not perform CPU intensive
operations, the slight possible performance degradation is not of
concern.

Signed-off-by: Marc Khouzam <[email protected]>

---------

Signed-off-by: Marc Khouzam <[email protected]>
  • Loading branch information
marckhouzam authored Dec 7, 2023
1 parent d1ab769 commit 6f528f5
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 7 deletions.
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-cli/pkg/buildinfo.Date=$(BUILD_DAT
LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-cli/pkg/buildinfo.SHA=$(BUILD_SHA)'
LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-cli/pkg/buildinfo.Version=$(BUILD_VERSION)'

# Remove debug symbols to reduce binary size
# To build with debug symbols: TANZU_CLI_ENABLE_DEBUG=1
ifneq ($(strip $(TANZU_CLI_ENABLE_DEBUG)),1)
LD_FLAGS += -w -s
endif

APT_IMAGE=ubuntu
ifdef APT_BUILDER_IMAGE
APT_IMAGE=$(APT_BUILDER_IMAGE)
Expand Down Expand Up @@ -133,9 +139,9 @@ build-cli-%: ##Build the Tanzu Core CLI for a platform
fi

@if [ "$(OS)" = "windows" ]; then \
GOOS=$(OS) GOARCH=$(ARCH) $(GO) build --ldflags "$(LD_FLAGS)" -o "$(ARTIFACTS_DIR)/$(OS)/$(ARCH)/cli/core/$(BUILD_VERSION)/tanzu-cli-$(OS)_$(ARCH).exe" ./cmd/tanzu/main.go;\
GOOS=$(OS) GOARCH=$(ARCH) $(GO) build -gcflags=all="-l" --ldflags "$(LD_FLAGS)" -o "$(ARTIFACTS_DIR)/$(OS)/$(ARCH)/cli/core/$(BUILD_VERSION)/tanzu-cli-$(OS)_$(ARCH).exe" ./cmd/tanzu/main.go;\
else \
GOOS=$(OS) GOARCH=$(ARCH) $(GO) build --ldflags "$(LD_FLAGS)" -o "$(ARTIFACTS_DIR)/$(OS)/$(ARCH)/cli/core/$(BUILD_VERSION)/tanzu-cli-$(OS)_$(ARCH)" ./cmd/tanzu/main.go;\
GOOS=$(OS) GOARCH=$(ARCH) $(GO) build -gcflags=all="-l" --ldflags "$(LD_FLAGS)" -o "$(ARTIFACTS_DIR)/$(OS)/$(ARCH)/cli/core/$(BUILD_VERSION)/tanzu-cli-$(OS)_$(ARCH)" ./cmd/tanzu/main.go;\
fi

## --------------------------------------
Expand Down
15 changes: 15 additions & 0 deletions cmd/plugin/builder/command/cli_compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type PluginCompileArgs struct {
PluginScopeAssociationFile string
TargetArch []string
GroupByOSArch bool
DebugSymbols bool
}

const local = "local"
Expand Down Expand Up @@ -103,6 +104,20 @@ func setGlobals(compileArgs *PluginCompileArgs) {

// Append version specific ldflag by default so that user doesn't need to pass this ldflag always.
ldflags = fmt.Sprintf("%s -X 'github.com/vmware-tanzu/tanzu-plugin-runtime/plugin/buildinfo.Version=%s'", ldflags, version)

// Remove debug symbols to reduce binary size
if !compileArgs.DebugSymbols {
ldflags = fmt.Sprintf("%s -w -s", ldflags)
}

// Disable function inlining to reduce binary size
disableInlining := "-gcflags=all=-l"
if len(goflags) > 0 {
// Append the user-defined goflags so they can override the default if needed
goflags = fmt.Sprintf("%s %s", disableInlining, goflags)
} else {
goflags = disableInlining
}
}

func Compile(compileArgs *PluginCompileArgs) error {
Expand Down
3 changes: 3 additions & 0 deletions cmd/plugin/builder/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type pluginBuildFlags struct {
Match string
PluginScopeAssociationFile string
GoFlags string
DebugSymbols bool
}

type pluginBuildPackageFlags struct {
Expand Down Expand Up @@ -82,6 +83,7 @@ func newPluginBuildCmd() *cobra.Command {
PluginScopeAssociationFile: pbFlags.PluginScopeAssociationFile,
GroupByOSArch: true,
GoFlags: pbFlags.GoFlags,
DebugSymbols: pbFlags.DebugSymbols,
}

return command.Compile(compileArgs)
Expand All @@ -96,6 +98,7 @@ func newPluginBuildCmd() *cobra.Command {
pluginBuildCmd.Flags().StringVarP(&pbFlags.Match, "match", "", "*", "match a plugin name to build, supports globbing")
pluginBuildCmd.Flags().StringVarP(&pbFlags.PluginScopeAssociationFile, "plugin-scope-association-file", "", "", "file specifying plugin scope association")
pluginBuildCmd.Flags().StringVarP(&pbFlags.GoFlags, "goflags", "", "", "goflags to set on build")
pluginBuildCmd.Flags().BoolVarP(&pbFlags.DebugSymbols, "debug-symbols", "", false, "include debug symbols in the build")

_ = pluginBuildCmd.MarkFlagRequired("version")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,21 @@ endif
PLUGIN_LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-plugin-runtime/plugin/buildinfo.Date=$(PLUGIN_BUILD_DATE)'
PLUGIN_LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-plugin-runtime/plugin/buildinfo.SHA=$(PLUGIN_BUILD_SHA)'
PLUGIN_LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-plugin-runtime/plugin/buildinfo.Version=$(PLUGIN_BUILD_VERSION)'

# This variable can be used to pass additional go flags to the build command.
# Note that the builder plugin already implicitly sets the go flags "-gcflags=all=-l"
# which disable function inlining to reduce binary size. If you want to prevent the
# use of this flag, you can set PLUGIN_GO_FLAGS to "-gcflags=all=" which will replace
# the default value.
PLUGIN_GO_FLAGS ?=

# To build with debug symbols use PLUGIN_ENABLE_DEBUG=1
ifeq ($(strip $(PLUGIN_ENABLE_DEBUG)),1)
PLUGIN_DEBUG=true
else
PLUGIN_DEBUG=false
endif

# Add supported OS-ARCHITECTURE combinations here
PLUGIN_BUILD_OS_ARCH ?= linux-amd64 windows-amd64 darwin-amd64 darwin-arm64 linux-arm64

Expand Down Expand Up @@ -102,7 +115,8 @@ plugin-build-%:
--goflags "$(PLUGIN_GO_FLAGS)" \
--os-arch $(OS)_$(ARCH) \
--match "$(PLUGIN_NAME)" \
--plugin-scope-association-file $(PLUGIN_SCOPE_ASSOCIATION_FILE)
--plugin-scope-association-file $(PLUGIN_SCOPE_ASSOCIATION_FILE) \
--debug-symbols=$(PLUGIN_DEBUG)

.PHONY: plugin-build-packages
plugin-build-packages: ## Build plugin packages
Expand Down
11 changes: 11 additions & 0 deletions docs/dev/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ To run e2e tests for the repository:
make e2e-cli-core
```

### Debugging

By default the CLI is built without debug symbols to reduce its binary size;
this has the side-effect of preventing the use of a debugger towards the built
binary. However, when using an IDE, a different binary is used, one built by
the IDE, and therefore the debugger can be used directly from the IDE.

If you require using a debugger directly on a CLI binary, you have to build
a binary that includes the debug symbols. You can build such a binary by using
`TANZU_CLI_ENABLE_DEBUG=1` along with your build command.

## Centralized Discovery of Plugins

The Tanzu CLI uses a system of plugins to provide functionality to interact
Expand Down
24 changes: 24 additions & 0 deletions docs/plugindev/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,30 @@ well.

Edit the file as appropriate.

#### Optimizations, debugging and build flags

By default the `builder` plugin `v1.2.0` or later optimizes build flags to
reduce the binary size of the plugin being built. Two optimizations are done:
building without debug symbols (which reduces the binary size by up to 30%),
and deactivating function inlining (which reduces the binary size by about 6%).

Building without debug symbols has the side-effect of preventing the use of a
debugger towards the built binary. However, when using an IDE, a different
binary is used, one built by the IDE, and therefore the debugger can be used
directly from the IDE.

If you require using a debugger directly on a plugin binary, you have to build
a binary that includes the debug symbols. You can build such a binary by using
`PLUGIN_ENABLE_DEBUG=1` along with your `make` command.

The `builder` plugin deactivates function inlining by implicitly injecting
the `-gcflags=all=-l` go flags. Be aware that if you use the `PLUGIN_GO_FLAGS`
variable to inject other `-gcflags`, you should pay attention to also include
the `all=-l` to keep the optimization. If for some reason you need to activate
function inlining (at the cost of a larger binary), you can do so by
using `PLUGIN_GO_FLAGS=-gcflags=all=` (without the `-l` specified), which will
have the effect of replacing the go flags specified by the `builder` plugin.

### Publishing a plugin

To publish one or more built plugins to a target repository, one would need to
Expand Down
16 changes: 15 additions & 1 deletion plugin-tooling.mk
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,21 @@ endif
PLUGIN_LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-plugin-runtime/plugin/buildinfo.Date=$(PLUGIN_BUILD_DATE)'
PLUGIN_LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-plugin-runtime/plugin/buildinfo.SHA=$(PLUGIN_BUILD_SHA)'
PLUGIN_LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-plugin-runtime/plugin/buildinfo.Version=$(PLUGIN_BUILD_VERSION)'

# This variable can be used to pass additional go flags to the build command.
# Note that the builder plugin already implicitly sets the go flags "-gcflags=all=-l"
# which disable function inlining to reduce binary size. If you want to prevent the
# use of this flag, you can set PLUGIN_GO_FLAGS to "-gcflags=all=" which will replace
# the default value.
PLUGIN_GO_FLAGS ?=

# To build with debug symbols use PLUGIN_ENABLE_DEBUG=1
ifeq ($(strip $(PLUGIN_ENABLE_DEBUG)),1)
PLUGIN_DEBUG=true
else
PLUGIN_DEBUG=false
endif

# Add supported OS-ARCHITECTURE combinations here
PLUGIN_BUILD_OS_ARCH ?= linux-amd64 windows-amd64 darwin-amd64 darwin-arm64 linux-arm64

Expand Down Expand Up @@ -106,7 +119,8 @@ plugin-build-%:
--goflags "$(PLUGIN_GO_FLAGS)" \
--os-arch $(OS)_$(ARCH) \
--match "$(PLUGIN_NAME)" \
--plugin-scope-association-file $(PLUGIN_SCOPE_ASSOCIATION_FILE)
--plugin-scope-association-file $(PLUGIN_SCOPE_ASSOCIATION_FILE) \
--debug-symbols=$(PLUGIN_DEBUG)

.PHONY: plugin-build-packages
plugin-build-packages: ## Build plugin packages
Expand Down
4 changes: 3 additions & 1 deletion test/sample-plugin/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ $(GOLANGCI_LINT): $(TOOLS_BIN_DIR) ## Install golangci-lint

.PHONY: install-builder
install-builder: ## Install builder
$(MAKE) -C $(ROOT_DIR_RELATIVE)/../.. prepare-builder
unset BUILDER_PLUGIN ; \
export TANZU_CLI_CEIP_OPT_IN_PROMPT_ANSWER="No" ; \
export TANZU_CLI_EULA_PROMPT_ANSWER="Yes" ; \
tanzu plugin install builder
$(MAKE) -C $(ROOT_DIR_RELATIVE)/../.. plugin-build-install-local PLUGIN_NAME=builder

.PHONEY: e2e-tests-simple-plugin ## Run all e2e tests for simple plugin
e2e-tests-simple-plugin: install-builder plugin-build-local plugin-install-local e2e-tests-sample-plugin-functionality e2e-tests-sample-plugin-e2e-api
Expand Down
19 changes: 17 additions & 2 deletions test/sample-plugin/plugin-tooling.mk
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,21 @@ endif
PLUGIN_LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-plugin-runtime/plugin/buildinfo.Date=$(PLUGIN_BUILD_DATE)'
PLUGIN_LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-plugin-runtime/plugin/buildinfo.SHA=$(PLUGIN_BUILD_SHA)'
PLUGIN_LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-plugin-runtime/plugin/buildinfo.Version=$(PLUGIN_BUILD_VERSION)'

# This variable can be used to pass additional go flags to the build command.
# Note that the builder plugin already implicitly sets the go flags "-gcflags=all=-l"
# which disable function inlining to reduce binary size. If you want to prevent the
# use of this flag, you can set PLUGIN_GO_FLAGS to "-gcflags=all=" which will replace
# the default value.
PLUGIN_GO_FLAGS ?=

# To build with debug symbols use PLUGIN_ENABLE_DEBUG=1
ifeq ($(strip $(PLUGIN_ENABLE_DEBUG)),1)
PLUGIN_DEBUG=true
else
PLUGIN_DEBUG=false
endif

# Add supported OS-ARCHITECTURE combinations here
PLUGIN_BUILD_OS_ARCH ?= linux-amd64 windows-amd64 darwin-amd64 darwin-arm64 linux-arm64

Expand Down Expand Up @@ -88,8 +101,9 @@ plugin-install-local:
.PHONY: plugin-build
plugin-build: $(PLUGIN_BUILD_TARGETS) generate-plugin-bundle ## Build all plugin binaries for all supported os-arch

.PHONY: plugin-build-local
plugin-build-local: plugin-build-$(GOHOSTOS)-$(GOHOSTARCH) ## Build all plugin binaries for local platform

plugin-build-%:
$(eval ARCH = $(word 2,$(subst -, ,$*)))
$(eval OS = $(word 1,$(subst -, ,$*)))
Expand All @@ -101,7 +115,8 @@ plugin-build-%:
--goflags "$(PLUGIN_GO_FLAGS)" \
--os-arch $(OS)_$(ARCH) \
--match "$(PLUGIN_NAME)" \
--plugin-scope-association-file $(PLUGIN_SCOPE_ASSOCIATION_FILE)
--plugin-scope-association-file $(PLUGIN_SCOPE_ASSOCIATION_FILE) \
--debug-symbols=$(PLUGIN_DEBUG)

.PHONY: plugin-build-packages
plugin-build-packages: ## Build plugin packages
Expand Down

0 comments on commit 6f528f5

Please sign in to comment.