gotool

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 29, 2021 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package gotool provides a runner to install and run compiled executables of Go module-based "main" packages.

Go Executable Installation

As of Go 1.16 `go install` supports the `pkg@version` syntax [1] which allows to install commands without "polluting" a projects `go.mod` file. The resulting executables are placed in the Go executable search path that is defined by the "GOBIN" environment variable [2] (see the "go env" command [3] to show or modify the Go toolchain environment). The problem is that installed executables will overwrite any previously installed executable of the same module/package regardless of the version. Therefore only one version of an executable can be installed at a time which makes it impossible to work on different projects that make use of the same executable but require different versions.

UX Before Go 1.16

The installation concept for "main" package executables was always a somewhat controversial point which unfortunately, partly for historical reasons, did not offer an optimal and user-friendly solution until Go 1.16. The "go" command [4] is a fantastic toolchain that provides many great features one would expect to be provided out-of-the-box from a modern and well designed programming language without the requirement to use a third-party solution: from compiling code, running unit/integration/benchmark tests, quality and error analysis, debugging utilities and many more. This did not apply for the "go install" command [1] of Go versions less than 1.16.

The general problem of tool dependencies was a long-time known issue/weak point of the Go toolchain and was a highly rated change request from the Go community with discussions like https://github.com/golang/go/issues/30515, https://github.com/golang/go/issues/25922 and https://github.com/golang/go/issues/27653 to improve this essential feature. They have been around for quite a long time without a solution that worked without introducing breaking changes and most users and the Go team agree on. Luckily, this topic was finally resolved in the Go release version 1.16 [5] and https://github.com/golang/go/issues/40276 introduced a way to install executables in module mode outside a module.

The Leftover Drawback

Even though the "go install" command works totally fine to globally install executables, the problem that only a single version can be installed at a time is still left. The executable is placed in the path defined by "go env GOBIN" so the previously installed executable will be overridden. It is not possible to install multiple versions of the same package and "go install" still messes up the local user environment.

The Workaround

To work around the leftover drawback, this package provides a runner that uses "go install" under the hood, but allows to place the compiled executable in a custom cache directory instead of `go env GOBIN`. It checks if the executable already exists, installs it if not so, and executes it afterwards.

The concept of storing dependencies locally on a per-project basis is well-known from the "node_modules" directory [6] of the Node [7] package manager npm [8]. Storing executables in a cache directory within the repository (not tracked by Git) allows to use "go install" mechanisms while not affect the global user environment and executables stored in "go env GOBIN". The runner achieves this by temporarily changing the "GOBIN" environment variable to the custom cache directory during the execution of "go install".

The only known disadvantage is the increased usage of storage disk space, but since most Go executables are small in size anyway, this is perfectly acceptable compared to the clearly outweighing advantages.

Note that the runner dynamically runs executables based on the given task so the "Validate" method is a NOOP.

Future Changes

The provided runner is still not a clean solution that uses the Go toolchain without any special logic so as soon as the following changes are made to the Go toolchain (Go 1.17 or later), the runner will be removed again:

References

[1]: https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies
[2]: https://pkg.go.dev/cmd/go/#hdr-Environment_variables
[3]: https://pkg.go.dev/cmd/go/#hdr-Print_Go_environment_information
[4]: https://pkg.go.dev/cmd/go
[5]: https://golang.org/doc/go1.16#modules
[6]: https://docs.npmjs.com/cli/v7/configuring-npm/folders#node-modules
[7]: https://nodejs.org
[8]: https://www.npmjs.com

Index

Constants

View Source
const (
	// RunnerName is the name of the runner.
	RunnerName = "gotool"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Runner

type Runner struct {
	// contains filtered or unexported fields
}

Runner is runner to install and run compiled executables of Go module-based "main" packages.

Go Executable Installation

As of Go 1.16 `go install` supports the `pkg@version` syntax [1] which allows to install commands without "polluting" a projects `go.mod` file. The resulting executables are placed in the Go executable search path that is defined by the "GOBIN" environment variable [2] (see the "go env" command [3] to show or modify the Go toolchain environment). The problem is that installed executables will overwrite any previously installed executable of the same module/package regardless of the version. Therefore only one version of an executable can be installed at a time which makes it impossible to work on different projects that make use of the same executable but require different versions.

UX Before Go 1.16

The installation concept for "main" package executables was always a somewhat controversial point which unfortunately, partly for historical reasons, did not offer an optimal and user-friendly solution until Go 1.16. The "go" command [4] is a fantastic toolchain that provides many great features one would expect to be provided out-of-the-box from a modern and well designed programming language without the requirement to use a third-party solution: from compiling code, running unit/integration/benchmark tests, quality and error analysis, debugging utilities and many more. This did not apply for the "go install" command [1] of Go versions less than 1.16.

The general problem of tool dependencies was a long-time known issue/weak point of the Go toolchain and was a highly rated change request from the Go community with discussions like https://github.com/golang/go/issues/30515, https://github.com/golang/go/issues/25922 and https://github.com/golang/go/issues/27653 to improve this essential feature. They have been around for quite a long time without a solution that worked without introducing breaking changes and most users and the Go team agree on. Luckily, this topic was finally resolved in the Go release version 1.16 [5] and https://github.com/golang/go/issues/40276 introduced a way to install executables in module mode outside a module.

The Leftover Drawback

Even though the "go install" command works totally fine to globally install executables, the problem that only a single version can be installed at a time is still left. The executable is placed in the path defined by "go env GOBIN" so the previously installed executable will be overridden. It is not possible to install multiple versions of the same package and "go install" still messes up the local user environment.

The Workaround

To work around the leftover drawback, this runner uses "go install" under the hood, but allows to place the compiled executable in a custom cache directory instead of `go env GOBIN`. It checks if the executable already exists, installs it if not so, and executes it afterwards.

The concept of storing dependencies locally on a per-project basis is well-known from the "node_modules" directory [6] of the Node [7] package manager npm [8]. Storing executables in a cache directory within the repository (not tracked by Git) allows to use "go install" mechanisms while not affect the global user environment and executables stored in "go env GOBIN". The runner achieves this by temporarily changing the "GOBIN" environment variable to the custom cache directory during the execution of "go install".

The only known disadvantage is the increased usage of storage disk space, but since most Go executables are small in size anyway, this is perfectly acceptable compared to the clearly outweighing advantages.

Note that the runner dynamically runs executables based on the given task so the "Validate" method is a NOOP.

Future Changes

This runner is still not a clean solution that uses the Go toolchain without any special logic so as soon as the following changes are made to the Go toolchain (Go 1.17 or later), the runner will be removed again:

References

[1]: https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies
[2]: https://pkg.go.dev/cmd/go/#hdr-Environment_variables
[3]: https://pkg.go.dev/cmd/go/#hdr-Print_Go_environment_information
[4]: https://pkg.go.dev/cmd/go
[5]: https://golang.org/doc/go1.16#modules
[6]: https://docs.npmjs.com/cli/v7/configuring-npm/folders#node-modules
[7]: https://nodejs.org
[8]: https://www.npmjs.com

func NewRunner

func NewRunner(goRunner *taskGo.Runner, opts ...RunnerOption) (*Runner, error)

NewRunner creates a new command runner for Go module-based tools. It returns an error of type *task.ErrRunner when any error occurs during the creation.

func (*Runner) Handles

func (r *Runner) Handles() task.Kind

Handles returns the supported task kind.

func (*Runner) Install

func (r *Runner) Install(goModule *project.GoModuleID) error

Install installs the executable of the given Go module. It returns an error of type *task.ErrRunner when any error occurs during the command execution.

// See https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies for more details.

func (*Runner) Run

func (r *Runner) Run(t task.Task) error

Run runs the command. It returns an error of type *task.ErrRunner when any error occurs during the command execution.

func (*Runner) RunOut

func (r *Runner) RunOut(t task.Task) (string, error)

RunOut runs the command and returns its output. It returns an error of type *task.ErrRunner when any error occurs during the command execution.

func (*Runner) Validate

func (r *Runner) Validate() error

Validate validates the runner. This runner uses dynamic executables based on the given task so this method is a NOOP.

type RunnerOption

type RunnerOption func(*RunnerOptions)

RunnerOption is a runner option.

func WithEnv

func WithEnv(env map[string]string) RunnerOption

WithEnv sets the runner specific environment.

func WithQuiet

func WithQuiet(quiet bool) RunnerOption

WithQuiet indicates whether the runner output should be minimal.

func WithToolsBinDir

func WithToolsBinDir(toolsBinDir string) RunnerOption

WithToolsBinDir sets the path to the directory where compiled binaries of Go module-based tools are placed. Defaults to DefaultToolsBinDir.

type RunnerOptions

type RunnerOptions struct {
	// Env is the runner specific environment.
	Env map[string]string

	// Quiet indicates whether the runner output should be minimal.
	Quiet bool
	// contains filtered or unexported fields
}

RunnerOptions are runner options.

func NewRunnerOptions

func NewRunnerOptions(opts ...RunnerOption) (*RunnerOptions, error)

NewRunnerOptions creates new runner options.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL