From 54e4637f525e08de9ce84c0a2e990e107bec3b82 Mon Sep 17 00:00:00 2001 From: Carolyn Van Slyck Date: Sat, 16 Jul 2022 20:49:18 -0500 Subject: [PATCH] Add EnsurePackageWith/InstallPackageWith EnsurePackageWith replaces EnsurePackage, which is now marked as deprecated. Same for InstallPackage. The With variation accepts an options struct so that we can add functionality without modifying the function signature. You can now specify a destination directory instead of installing into GOPATH/bin. This is useful for installing build/dev tools into ./bin and not messing with the system installation on dev machines. Signed-off-by: Carolyn Van Slyck --- README.md | 11 +++- pkg/install.go | 122 ++++++++++++++++++++++++++++++------ pkg/install_example_test.go | 88 ++++++++++++++++++++++++-- pkg/install_test.go | 49 ++++++++++++++- 4 files changed, 241 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 922f5fa..ba3318b 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,16 @@ import ( "github.com/carolynvs/magex/shx" ) -// Install packr2 v2.8.0 if it's not available, and ensure it's in PATH. +// Check if packr2 is in the bin/ directory and is at least v2. +// If not, install packr@v2.8.0 into bin/ func EnsurePackr2() error { - return pkg.EnsurePackage("github.com/gobuffalo/packr/v2/packr2", "v2.8.0", "version") + opts := pkg.EnsurePackageOptions{ + Name: "github.com/gobuffalo/packr/v2/packr2", + DefaultVersion: "v2.8.0", + VersionCommand: "version", + Destination: "bin", + } + return pkg.EnsurePackageWith(opts) } // Install mage if it's not available, and ensure it's in PATH. We don't care which version diff --git a/pkg/install.go b/pkg/install.go index daabd51..daeb9bb 100644 --- a/pkg/install.go +++ b/pkg/install.go @@ -10,9 +10,10 @@ import ( "regexp" "strings" + "github.com/carolynvs/magex/pkg/gopath" + "github.com/Masterminds/semver/v3" "github.com/carolynvs/magex/pkg/downloads" - "github.com/carolynvs/magex/pkg/gopath" "github.com/carolynvs/magex/shx" "github.com/carolynvs/magex/xplat" ) @@ -45,31 +46,77 @@ func EnsureMage(defaultVersion string) error { // is used as the minimum version and sets the allowed major version. For example, // a defaultVersion of 1.2.3 would result in a constraint of ^1.2.3. // When no defaultVersion is specified, the latest version is installed. +// +// Deprecated: Use EnsurePackageWith. func EnsurePackage(pkg string, defaultVersion string, versionArgs ...string) error { - cmd := getCommandName(pkg) + var versionCmd, allowedVersion string - // Apply optional arguments: versionCmd, versionConstraint - versionCmd := "" - versionConstraint := "" if len(versionArgs) > 0 { versionCmd = versionArgs[0] if len(versionArgs) > 1 { - versionConstraint = versionArgs[1] + allowedVersion = versionArgs[1] } } + return EnsurePackageWith(EnsurePackageOptions{ + Name: pkg, + DefaultVersion: defaultVersion, + AllowedVersion: allowedVersion, + VersionCommand: versionCmd, + }) +} + +// EnsurePackageOptions are the set of options that can be passed to EnsurePackageWith. +type EnsurePackageOptions struct { + // Name of the Go package + // Provide the name of the package that should be compiled into a cli, + // such as github.com/gobuffalo/packr/v2/packr2 + Name string + + // DefaultVersion is the version to install, if not found. When specified, and + // AllowedVersion is not, DefaultVersion is used as the minimum version and sets + // the allowed major version. For example, a DefaultVersion of 1.2.3 would result + // in an AllowedVersion of ^1.2.3. When no DefaultVersion is specified, the + // latest version is installed. + DefaultVersion string + + // AllowedVersion is a semver range that specifies which versions are acceptable + // if found. For example, ^1.2.3 or 2.x. When unspecified, any installed version + // is acceptable. See https://github.com/Masterminds/semver for further + // documentation. + AllowedVersion string + + // Destination is the location where the CLI should be installed + // Defaults to GOPATH/bin. Using ./bin is recommended to require build tools + // without modifying the host environment. + Destination string + + // VersionCommand is the arguments to pass to the CLI to determine the installed version. + // For example, "version" or "--version". When unspecified the CLI is called without any arguments. + VersionCommand string +} + +// EnsurePackageWith checks if the package is installed and installs it if needed. +func EnsurePackageWith(opts EnsurePackageOptions) error { + cmd := getCommandName(opts.Name) + // Default the constraint to [defaultVersion - next major) - if versionConstraint == "" { - versionConstraint = makeDefaultVersionConstraint(defaultVersion) + if opts.AllowedVersion == "" { + opts.AllowedVersion = makeDefaultVersionConstraint(opts.DefaultVersion) } - found, err := IsCommandAvailable(cmd, versionCmd, versionConstraint) + found, err := IsCommandAvailable(cmd, opts.VersionCommand, opts.AllowedVersion) if err != nil { return err } if !found { - return InstallPackage(pkg, defaultVersion) + installOpts := InstallPackageOptions{ + Name: opts.Name, + Destination: opts.Destination, + Version: opts.DefaultVersion, + } + return InstallPackageWith(installOpts) } return nil } @@ -91,26 +138,61 @@ func getCommandName(pkg string) string { return name } +// InstallPackageOptions are the set of options that can be passed to InstallPackageWith. +type InstallPackageOptions struct { + // Name of the Go package + // Provide the name of the package that should be compiled into a cli, + // such as github.com/gobuffalo/packr/v2/packr2 + Name string + + // Destination is the location where the CLI should be installed + // Defaults to GOPATH/bin. Using ./bin is recommended to require build tools + // without modifying the host environment. + Destination string + + // Version of the package to install. + Version string +} + // InstallPackage installs the latest version of a package. // -// When version is specified, install that version. Otherwise install the most +// When version is specified, install that version. Otherwise, install the most // recent version. +// Deprecated: Use InstallPackageWith instead. func InstallPackage(pkg string, version string) error { - gopath.EnsureGopathBin() + opts := InstallPackageOptions{ + Name: pkg, + Version: version, + } + return InstallPackageWith(opts) +} - cmd := getCommandName(pkg) +// InstallPackageWith unconditionally installs a package +func InstallPackageWith(opts InstallPackageOptions) error { + cmd := getCommandName(opts.Name) - if version == "" { - version = "latest" + if opts.Version == "" { + opts.Version = "latest" } else { - if version != "latest" && !strings.HasPrefix(version, "v") { - version = "v" + version + if opts.Version != "latest" && !strings.HasPrefix(opts.Version, "v") { + opts.Version = "v" + opts.Version } } - fmt.Printf("Installing %s@%s\n", cmd, version) - return shx.Command("go", "install", pkg+"@"+version). - Env("GO111MODULE=on").In(os.TempDir()).RunE() + installCmd := shx.Command("go", "install", opts.Name+"@"+opts.Version). + Env("GO111MODULE=on").In(os.TempDir()) + if opts.Destination == "" { + gopath.EnsureGopathBin() + fmt.Printf("Installing %s@%s into GOPATH/bin\n", cmd, opts.Version) + } else { + dest, err := filepath.Abs(opts.Destination) + if err != nil { + return fmt.Errorf("error converting %s to an absolute path", opts.Destination) + } + installCmd.Env("GOBIN=" + dest) + fmt.Printf("Installing %s@%s into %s\n", cmd, opts.Version, dest) + } + return installCmd.RunE() } // InstallMage mage into GOPATH and add GOPATH/bin to PATH if necessary. diff --git a/pkg/install_example_test.go b/pkg/install_example_test.go index e6bbb4b..0dcd6fa 100644 --- a/pkg/install_example_test.go +++ b/pkg/install_example_test.go @@ -2,8 +2,11 @@ package pkg_test import ( "log" + "os" "testing" + "github.com/stretchr/testify/require" + "github.com/carolynvs/magex/pkg" "github.com/carolynvs/magex/pkg/gopath" ) @@ -26,7 +29,7 @@ func TestExampleEnsurePackage(t *testing.T) { } func ExampleEnsurePackage() { - // Install packr2@v2.8.0 using the command `packr2 version` to detect if the + // Install packr2@v2.8.3 using the command `packr2 version` to detect if the // correct version is installed. err := pkg.EnsurePackage("github.com/gobuffalo/packr/v2/packr2", "v2.8.3", "version") if err != nil { @@ -34,12 +37,47 @@ func ExampleEnsurePackage() { } } -func TestExampleEnsurePackage_WithVersionConstraint(t *testing.T) { - ExampleEnsurePackage_WithVersionConstraint() +func TestExampleEnsurePackageWith_LatestVersion(t *testing.T) { + ExampleEnsurePackageWith_LatestVersion() +} + +func ExampleEnsurePackageWith_LatestVersion() { + // Install packr2@latest into bin/ using the command `packr2 version` to detect if the + // correct version is installed. + err := pkg.EnsurePackageWith(pkg.EnsurePackageOptions{ + Name: "github.com/gobuffalo/packr/v2/packr2", + VersionCommand: "version", + Destination: "bin", + }) + if err != nil { + log.Fatal("could not install packr2") + } +} + +func TestExampleEnsurePackageWith_DefaultVersion(t *testing.T) { + ExampleEnsurePackageWith_DefaultVersion() +} + +func ExampleEnsurePackageWith_DefaultVersion() { + // Install packr2@v2.8.3 into bin/ using the command `packr2 version` to detect if the + // correct version is installed. + err := pkg.EnsurePackageWith(pkg.EnsurePackageOptions{ + Name: "github.com/gobuffalo/packr/v2/packr2", + DefaultVersion: "v2.8.3", + VersionCommand: "version", + Destination: "bin", + }) + if err != nil { + log.Fatal("could not install packr2") + } +} + +func TestExampleEnsurePackage_VersionConstraint(t *testing.T) { + ExampleEnsurePackage_VersionConstraint() } -func ExampleEnsurePackage_WithVersionConstraint() { - // Install packr2@v2.8.0 using the command `packr2 version` to detect if +func ExampleEnsurePackage_VersionConstraint() { + // Install packr2@v2.8.3 using the command `packr2 version` to detect if // any v2 version is installed err := pkg.EnsurePackage("github.com/gobuffalo/packr/v2/packr2", "v2.8.3", "version", "2.x") if err != nil { @@ -47,6 +85,25 @@ func ExampleEnsurePackage_WithVersionConstraint() { } } +func TestExampleEnsurePackageWith_VersionConstraint(t *testing.T) { + ExampleEnsurePackageWith_VersionConstraint() +} + +func ExampleEnsurePackageWith_VersionConstraint() { + // Install packr2@v2.8.3 into bin/ using the command `packr2 version` to detect if + // any v2 version is installed + err := pkg.EnsurePackageWith(pkg.EnsurePackageOptions{ + Name: "github.com/gobuffalo/packr/v2/packr2", + DefaultVersion: "v2.8.3", + VersionCommand: "version", + Destination: "bin", + AllowedVersion: "2.x", + }) + if err != nil { + log.Fatal("could not install packr2") + } +} + func TestExampleInstallPackage(t *testing.T) { ExampleInstallPackage() } @@ -59,6 +116,27 @@ func ExampleInstallPackage() { } } +func TestExampleInstallPackageWith(t *testing.T) { + tmpBin, err := os.MkdirTemp("", "magex") + require.NoError(t, err) + defer os.RemoveAll(tmpBin) + + ExampleInstallPackageWith() +} + +func ExampleInstallPackageWith() { + // Install packr2@v2.8.3 into the bin/ directory + opts := pkg.InstallPackageOptions{ + Name: "github.com/gobuffalo/packr/v2/packr2", + Destination: "bin", + Version: "v2.8.3", + } + err := pkg.InstallPackageWith(opts) + if err != nil { + log.Fatal("could not install packr2@v2.8.3 into bin") + } +} + func TestExampleDownloadToGopathBin(t *testing.T) { ExampleDownloadToGopathBin() } diff --git a/pkg/install_test.go b/pkg/install_test.go index 6423b89..ec9a116 100644 --- a/pkg/install_test.go +++ b/pkg/install_test.go @@ -3,6 +3,7 @@ package pkg import ( "os" "os/exec" + "path/filepath" "testing" "github.com/carolynvs/magex/pkg/gopath" @@ -68,15 +69,52 @@ func TestEnsurePackage_FreshInstall(t *testing.T) { hasCmd, err := IsCommandAvailable("testpkg", "", "") require.False(t, hasCmd) - err = EnsurePackage("github.com/carolynvs/testpkg/v2", tc.defaultVersion, "--version", tc.versionConstraint) + opts := EnsurePackageOptions{ + Name: "github.com/carolynvs/testpkg/v2", + DefaultVersion: tc.defaultVersion, + AllowedVersion: tc.versionConstraint, + VersionCommand: "--version", + } + err = EnsurePackageWith(opts) require.NoError(t, err) installedVersion, err := GetCommandVersion("testpkg", "") + require.NoError(t, err, "GetCommandVersion failed") require.Equal(t, tc.wantVersion, installedVersion, "incorrect version was resolved") }) } } +func TestEnsurePackage_IntoDirectory(t *testing.T) { + os.Setenv(mg.VerboseEnv, "true") + defer os.Unsetenv(mg.VerboseEnv) + + // Make a temp GOPATH to avoid accidentally messing with the system GOPATH/bin if the test fails + err, cleanup := gopath.UseTempGopath() + require.NoError(t, err, "Failed to set up a temporary GOPATH") + defer cleanup() + + tmpBin, err := os.MkdirTemp("", "magex") + require.NoError(t, err, "Failed to create temporary bin directory") + defer os.RemoveAll(tmpBin) + + opts := EnsurePackageOptions{ + Name: "github.com/carolynvs/testpkg/v2", + DefaultVersion: "v2.0.2", + Destination: tmpBin, + } + err = EnsurePackageWith(opts) + require.NoError(t, err) + + cmdPath := filepath.Join(tmpBin, "testpkg"+xplat.FileExt()) + require.FileExists(t, cmdPath, "The command was not installed into the bin directory") + + installedVersion, err := GetCommandVersion(cmdPath, "") + require.NoError(t, err, "GetCommandVersion failed") + require.Equal(t, opts.DefaultVersion, installedVersion, "incorrect version was installed") + +} + func TestEnsurePackage_Upgrade(t *testing.T) { os.Setenv(mg.VerboseEnv, "true") defer os.Unsetenv(mg.VerboseEnv) @@ -104,10 +142,17 @@ func TestEnsurePackage_Upgrade(t *testing.T) { require.NoError(t, err) // Ensure it's installed with a higher default version - err = EnsurePackage("github.com/carolynvs/testpkg/v2", tc.defaultVersion, "--version", tc.versionConstraint) + opts := EnsurePackageOptions{ + Name: "github.com/carolynvs/testpkg/v2", + DefaultVersion: tc.defaultVersion, + AllowedVersion: tc.versionConstraint, + VersionCommand: "--version", + } + err = EnsurePackageWith(opts) require.NoError(t, err) installedVersion, err := GetCommandVersion("testpkg", "") + require.NoError(t, err, "GetCommandVersion failed") require.Equal(t, tc.wantVersion, installedVersion, "incorrect version was resolved") }) }