Skip to content

Commit

Permalink
Merge pull request #184 from WillAbides/wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
WillAbides authored Nov 20, 2023
2 parents 3cd477b + a51f0da commit e39b976
Show file tree
Hide file tree
Showing 10 changed files with 369 additions and 117 deletions.
135 changes: 66 additions & 69 deletions cmd/bindown/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,19 @@ var kongVars = kong.Vars{
"config_install_completions_help": `install shell completions`,
"config_extract_path_help": `output path to directory where the downloaded archive is extracted`,
"install_force_help": `force install even if it already exists`,
"install_target_file_help": `where to write the file`,
"install_dependency_help": `dependency to install`,
"install_target_file_help": `where to write the file. when multiple dependencies are selected, this is the directory to write to.`,
"download_force_help": `force download even if the file already exists`,
"download_target_file_help": `filename and path for the downloaded file. Default downloads to cache.`,
"download_dependency_help": `name of the dependency to download`,
"allow_missing_checksum": `allow missing checksums`,
"download_help": `download a dependency but don't extract or install it`,
"extract_dependency_help": `name of the dependency to extract`,
"extract_help": `download and extract a dependency but don't install it`,
"extract_target_dir_help": `path to extract to. Default extracts to cache.`,
"checksums_dep_help": `name of the dependency to update`,
"trust_cache_help": `trust the cache contents and do not recheck existing downloads and extracts in the cache`,
"all_deps_help": `select all dependencies`,
"dependency_help": `name of dependency`,
"install_to_cache_help": `install to cache instead of install dir`,
"install_wrapper_help": `install a wrapper script instead of the binary`,
"install_bindown_help": `path to bindown executable to use in wrapper`,
}

type rootCmd struct {
Expand Down Expand Up @@ -252,80 +252,68 @@ func (c fmtCmd) Run(ctx *runContext, cli *rootCmd) error {
}

type installCmd struct {
dependencySelector
Force bool `kong:"help=${install_force_help}"`
Dependency []string `kong:"arg,name=dependency,help=${download_dependency_help},predictor=bin"`
All bool `kong:"help=${all_deps_help}"`
TargetFile string `kong:"type=path,name=output,type=file,help=${install_target_file_help}"`
System bindown.System `kong:"name=system,default=${system_default},help=${system_help},predictor=allSystems"`
AllowMissingChecksum bool `kong:"name=allow-missing-checksum,help=${allow_missing_checksum}"`
}

func (d *installCmd) BeforeApply(k *kong.Context) error {
optionalDependency(k)
return nil
ToCache bool `kong:"name=to-cache,help=${install_to_cache_help}"`
Wrapper bool `kong:"name=wrapper,help=${install_wrapper_help}"`
BindownExec string `kong:"name=bindown,help=${install_bindown_help}"`
}

func (d *installCmd) Run(ctx *runContext) error {
config, err := loadConfigFile(ctx, false)
if err != nil {
return err
}
if d.All {
if len(d.Dependency) > 0 {
return fmt.Errorf("cannot specify dependencies when using --all")
}
d.Dependency = allDependencies(config)
err = d.setDependencies(config)
if err != nil {
return err
}
switch len(d.Dependency) {
case 0:
return fmt.Errorf("must specify at least one dependency")
case 1:
default:
if d.TargetFile != "" {
return fmt.Errorf("cannot specify --output when installing multiple dependencies")
}
if d.ToCache && d.Wrapper {
return fmt.Errorf("cannot use --to-cache and --wrapper together")
}
for _, dep := range d.Dependency {
var pth string
pth, err = config.InstallDependency(dep, d.System, &bindown.ConfigInstallDependencyOpts{
TargetPath: d.TargetFile,
Force: d.Force,
AllowMissingChecksum: d.AllowMissingChecksum,
})
if err != nil {
return err
if d.BindownExec != "" && !d.Wrapper {
return fmt.Errorf("--bindown can only be used with --wrapper")
}
if d.Force && d.Wrapper {
return fmt.Errorf("cannot use --force and --wrapper together")
}
opts := bindown.ConfigInstallDependenciesOpts{
TargetFile: d.TargetFile,
Force: d.Force,
AllowMissingChecksum: d.AllowMissingChecksum,
ToCache: d.ToCache,
Wrapper: d.Wrapper,
BindownPath: d.BindownExec,
Stdout: ctx.stdout,
}
if d.All || len(d.Dependency) > 1 {
opts.TargetFile = ""
opts.TargetDir = d.TargetFile
if opts.TargetDir == "" {
opts.TargetDir = config.InstallDir
}
fmt.Fprintf(ctx.stdout, "installed %s to %s\n", dep, pth)
}
return nil
return config.InstallDependencies(d.Dependency, d.System, &opts)
}

type downloadCmd struct {
dependencySelector
Force bool `kong:"help=${download_force_help}"`
System bindown.System `kong:"name=system,default=${system_default},help=${system_help},predictor=allSystems"`
Dependency []string `kong:"arg,help=${download_dependency_help},predictor=bin"`
All bool `kong:"help=${all_deps_help}"`
AllowMissingChecksum bool `kong:"name=allow-missing-checksum,help=${allow_missing_checksum}"`
}

func (d *downloadCmd) BeforeApply(k *kong.Context) error {
optionalDependency(k)
return nil
}

func (d *downloadCmd) Run(ctx *runContext) error {
config, err := loadConfigFile(ctx, false)
if err != nil {
return err
}
if d.All {
if len(d.Dependency) > 0 {
return fmt.Errorf("cannot specify dependencies when using --all")
}
d.Dependency = allDependencies(config)
}
if len(d.Dependency) == 0 {
return fmt.Errorf("must specify at least one dependency")
err = d.setDependencies(config)
if err != nil {
return err
}
for _, dep := range d.Dependency {
var pth string
Expand All @@ -342,30 +330,19 @@ func (d *downloadCmd) Run(ctx *runContext) error {
}

type extractCmd struct {
dependencySelector
System bindown.System `kong:"name=system,default=${system_default},help=${system_help},predictor=allSystems"`
Dependency []string `kong:"arg,help=${extract_dependency_help},predictor=bin"`
All bool `kong:"help=${all_deps_help}"`
AllowMissingChecksum bool `kong:"name=allow-missing-checksum,help=${allow_missing_checksum}"`
}

func (d *extractCmd) BeforeApply(k *kong.Context) error {
optionalDependency(k)
return nil
}

func (d *extractCmd) Run(ctx *runContext) error {
config, err := loadConfigFile(ctx, false)
if err != nil {
return err
}
if d.All {
if len(d.Dependency) > 0 {
return fmt.Errorf("cannot specify dependencies when using --all")
}
d.Dependency = allDependencies(config)
}
if len(d.Dependency) == 0 {
return fmt.Errorf("must specify at least one dependency")
err = d.setDependencies(config)
if err != nil {
return err
}
for _, dep := range d.Dependency {
var pth string
Expand All @@ -380,12 +357,32 @@ func (d *extractCmd) Run(ctx *runContext) error {
return nil
}

// optionalDependency sets dependency positional to optional. We do this because we want to allow --all to be
// equivalent to specifying all dependencies but want the help output to indicate that a dependency is required.
func optionalDependency(k *kong.Context) {
type dependencySelector struct {
Dependency []string `kong:"arg,name=dependency,help=${dependency_help},predictor=bin"`
All bool `kong:"help=${all_deps_help}"`
}

func (d *dependencySelector) BeforeApply(k *kong.Context) error {
// sets dependency positional to optional. We do this because we want to allow --all to be
// equivalent to specifying all dependencies but want the help output to indicate that a
// dependency is required.
for _, pos := range k.Selected().Positional {
if pos.Name == "dependency" {
pos.Required = false
}
}
return nil
}

func (d *dependencySelector) setDependencies(config *bindown.Config) error {
if d.All {
if len(d.Dependency) > 0 {
return fmt.Errorf("cannot specify dependencies when using --all")
}
d.Dependency = allDependencies(config)
}
if len(d.Dependency) == 0 {
return fmt.Errorf("must specify at least one dependency")
}
return nil
}
97 changes: 74 additions & 23 deletions internal/bindown/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ func (c *Config) Validate(depName string, systems []System) (errOut error) {
}
}
for _, system := range depSystems {
_, err = c.InstallDependency(depName, system, &ConfigInstallDependencyOpts{
err = c.InstallDependencies([]string{depName}, system, &ConfigInstallDependenciesOpts{
Force: true,
})
if err != nil {
Expand Down Expand Up @@ -413,38 +413,89 @@ type ConfigInstallDependencyOpts struct {
Force bool
// AllowMissingChecksum - whether to allow missing checksum
AllowMissingChecksum bool
// ToCache - if set, the dependency will be installed to a cache directory. ToCache overrides TargetPath.
ToCache bool
}

// InstallDependency downloads, extracts and installs a dependency
func (c *Config) InstallDependency(dependencyName string, system System, opts *ConfigInstallDependencyOpts) (_ string, errOut error) {
// ConfigInstallDependenciesOpts provides options for Config.InstallDependencies
type ConfigInstallDependenciesOpts struct {
TargetFile string
TargetDir string
BindownPath string
Stdout io.Writer
Force bool
AllowMissingChecksum bool
ToCache bool
Wrapper bool
}

func (c *Config) InstallDependencies(deps []string, system System, opts *ConfigInstallDependenciesOpts) (errOut error) {
if opts == nil {
opts = &ConfigInstallDependencyOpts{}
opts = &ConfigInstallDependenciesOpts{}
}
dep, err := c.BuildDependency(dependencyName, system)
if err != nil {
return "", err
targetDir := opts.TargetDir
if targetDir == "" {
targetDir = c.InstallDir
}
dlFile, key, dlUnlock, err := downloadDependency(dep, c.downloadsCache(), opts.AllowMissingChecksum, opts.Force)
if err != nil {
return "", err
if opts.Wrapper {
cacheDir := c.Cache
configFile := c.Filename
err := createWrappers(deps, opts.TargetFile, targetDir, opts.BindownPath, cacheDir, configFile, opts.AllowMissingChecksum, opts.Stdout)
if err != nil {
return err
}
return nil
}
defer deferErr(&errOut, dlUnlock)

extractDir, exUnlock, err := extractDependencyToCache(dlFile, c.Cache, key, c.extractsCache(), opts.Force)
if err != nil {
return "", err
for _, name := range deps {
dep, err := c.BuildDependency(name, system)
if err != nil {
return err
}
target := opts.TargetFile
if target == "" {
target = filepath.Join(targetDir, name)
}
out, err := install(dep, target, c.Cache, opts.Force, opts.ToCache, opts.AllowMissingChecksum)
if err != nil {
return err
}
if opts.Stdout == nil {
continue
}
if !opts.ToCache {
out = fmt.Sprintf("installed %s to %s", dep.name, out)
}
_, err = fmt.Fprintln(opts.Stdout, out)
if err != nil {
return err
}
}
defer deferErr(&errOut, exUnlock)
targetPath := opts.TargetPath
if targetPath == "" {
var binName string
binName, err = c.BinName(dependencyName, system)
return nil
}

func createWrappers(
deps []string, targetFile, targetDir, bindownExec, cacheDir, configFile string,
missingSums bool,
stdout io.Writer,
) error {
for _, name := range deps {
target := targetFile
if target == "" {
target = filepath.Join(targetDir, name)
}
out, err := createWrapper(name, target, bindownExec, cacheDir, configFile, missingSums)
if err != nil {
return err
}
if stdout == nil {
continue
}
_, err = fmt.Fprintln(stdout, out)
if err != nil {
return "", err
return err
}
targetPath = filepath.Join(c.InstallDir, binName)
}
return install(dep, targetPath, extractDir)
return nil
}

// AddDependencyFromTemplateOpts options for AddDependencyFromTemplate
Expand Down
27 changes: 18 additions & 9 deletions internal/bindown/config_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bindown

import (
"bytes"
"context"
"fmt"
"os"
Expand Down Expand Up @@ -192,7 +193,7 @@ func TestConfig_addTemplateFromSource(t *testing.T) {
})
}

func TestConfig_InstallDependency(t *testing.T) {
func TestConfig_InstallDependencies(t *testing.T) {
t.Run("raw file", func(t *testing.T) {
dir := t.TempDir()
servePath := filepath.Join("testdata", "downloadables", "rawfile", "foo")
Expand All @@ -212,10 +213,14 @@ dependencies:
`, binDir, cacheDir, depURL, depURL))
t.Cleanup(func() { require.NoError(t, config.ClearCache()) })
wantBin := filepath.Join(binDir, "foo")
gotPath, err := config.InstallDependency("foo", "darwin/amd64", &ConfigInstallDependencyOpts{})
wantStdout := fmt.Sprintf("installed foo to %s\n", wantBin)
var stdout bytes.Buffer
err := config.InstallDependencies([]string{"foo"}, "darwin/amd64", &ConfigInstallDependenciesOpts{
Stdout: &stdout,
})
require.NoError(t, err)
require.Equal(t, wantBin, gotPath)
require.True(t, fileExists(wantBin))
require.Equal(t, wantStdout, stdout.String())
require.True(t, FileExists(wantBin))
stat, err := os.Stat(wantBin)
require.NoError(t, err)
require.False(t, stat.IsDir())
Expand All @@ -241,10 +246,14 @@ dependencies:
`, binDir, cacheDir, depURL, depURL))
t.Cleanup(func() { require.NoError(t, config.ClearCache()) })
wantBin := filepath.Join(binDir, "foo")
gotPath, err := config.InstallDependency("foo", "darwin/amd64", &ConfigInstallDependencyOpts{})
var stdout bytes.Buffer
wantStdout := fmt.Sprintf("installed foo to %s\n", wantBin)
err := config.InstallDependencies([]string{"foo"}, "darwin/amd64", &ConfigInstallDependenciesOpts{
Stdout: &stdout,
})
require.NoError(t, err)
require.Equal(t, wantBin, gotPath)
require.True(t, fileExists(wantBin))
require.Equal(t, wantStdout, stdout.String())
require.True(t, FileExists(wantBin))
stat, err := os.Stat(wantBin)
require.NoError(t, err)
require.False(t, stat.IsDir())
Expand Down Expand Up @@ -274,9 +283,9 @@ dependencies:
`, binDir, cacheDir, depURL, depURL))
t.Cleanup(func() { require.NoError(t, config.ClearCache()) })
wantBin := filepath.Join(binDir, "foo")
_, err := config.InstallDependency("foo", "darwin/amd64", &ConfigInstallDependencyOpts{})
err := config.InstallDependencies([]string{"foo"}, "darwin/amd64", &ConfigInstallDependenciesOpts{})
require.Error(t, err)
require.False(t, fileExists(wantBin))
require.False(t, FileExists(wantBin))
})
}

Expand Down
Loading

0 comments on commit e39b976

Please sign in to comment.