Skip to content

Commit

Permalink
goanywhere command
Browse files Browse the repository at this point in the history
Signed-off-by: Chris Koch <[email protected]>
  • Loading branch information
hugelgupf committed Feb 26, 2024
1 parent af2844f commit be176df
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/cmd/goanywhere/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
goanywhere
122 changes: 122 additions & 0 deletions src/cmd/goanywhere/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package main

import (
"bytes"
"flag"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"text/template"

"github.com/u-root/gobusybox/src/pkg/bb/findpkg"
"github.com/u-root/gobusybox/src/pkg/golang"
"github.com/u-root/uio/ulog"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)

var (
debug = flag.Bool("d", false, "Show debug string")
tmp = flag.Bool("tmp", true, "Create workspace in temp dir")
)

func run(dir string, args []string, paths []string) {
var relPaths []string
absDir, err := filepath.Abs(dir)
if err != nil {
relPaths = paths
} else {
for _, p := range paths {
rp, err := filepath.Rel(absDir, p)
if err != nil || len(rp) >= len(p) {
relPaths = append(relPaths, p)
} else {
relPaths = append(relPaths, "./"+rp)

Check warning on line 37 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L26-L37

Added lines #L26 - L37 were not covered by tests
}
}
}
if *debug {
if dir != "" {
fmt.Printf("+ cd %s\n", dir)

Check warning on line 43 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L41-L43

Added lines #L41 - L43 were not covered by tests
}
fmt.Printf("+ %s %s\n", strings.Join(args, " "), strings.Join(relPaths, " "))

Check warning on line 45 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L45

Added line #L45 was not covered by tests
}
c := exec.Command(args[0], append(args[1:], relPaths...)...)
c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr
c.Dir = dir
if err := c.Run(); err != nil {
log.Fatalf("Exited with %v", err)
os.Exit(1)

Check warning on line 52 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L47-L52

Added lines #L47 - L52 were not covered by tests
}
os.Exit(0)

Check warning on line 54 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L54

Added line #L54 was not covered by tests
}

func main() {
flag.Parse()
log.SetPrefix("[goanywhere] ")

Check warning on line 59 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L57-L59

Added lines #L57 - L59 were not covered by tests

args := flag.Args()
if len(args) == 0 {
log.Fatalf("Usage: goanywhere $pkg... -- <command> ...")

Check warning on line 63 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L61-L63

Added lines #L61 - L63 were not covered by tests
}
dashIndex := slices.Index(args, "--")
if dashIndex == -1 {
log.Fatalf("Must use -- to distinguish Go packages from command. Usage: goanywhere $pkg... -- <command> ...")

Check warning on line 67 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L65-L67

Added lines #L65 - L67 were not covered by tests
}
if len(args) == dashIndex+1 {
log.Fatalf("Must provide command to run. Usage: goanywhere $pkg... -- <command> ...")

Check warning on line 70 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L69-L70

Added lines #L69 - L70 were not covered by tests
}
pkgs := args[:dashIndex]
args = args[dashIndex+1:]

Check warning on line 73 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L72-L73

Added lines #L72 - L73 were not covered by tests

env := golang.Default()
paths := findpkg.GlobPaths(ulog.Log, findpkg.DefaultEnv(), pkgs...)
mods, noModulePaths := findpkg.Modules(paths)

Check warning on line 77 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L75-L77

Added lines #L75 - L77 were not covered by tests

if env.GO111MODULE == "off" {
run("", args, paths)

Check warning on line 80 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L79-L80

Added lines #L79 - L80 were not covered by tests
}

if len(noModulePaths) > 0 {
log.Fatalf("No modules found for %v", noModulePaths)

Check warning on line 84 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L83-L84

Added lines #L83 - L84 were not covered by tests
}

var dir string
if *tmp {
var err error
dir, err = os.MkdirTemp("", "goanywhere-")
if err != nil {
log.Fatalf("Could not create temp dir: %v", err)

Check warning on line 92 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L87-L92

Added lines #L87 - L92 were not covered by tests
}
defer os.RemoveAll(dir)

Check warning on line 94 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L94

Added line #L94 was not covered by tests
}

tpl := `go {{.Version}}

Check warning on line 97 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L97

Added line #L97 was not covered by tests
use ({{range .Modules}}
{{.}}{{end}}
)
`

Check warning on line 102 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L99-L102

Added lines #L99 - L102 were not covered by tests

vars := struct {
Version string
Modules []string
}{
Version: "1.22",
Modules: maps.Keys(mods),

Check warning on line 109 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L104-L109

Added lines #L104 - L109 were not covered by tests
}
t := template.Must(template.New("tpl").Parse(tpl))
var b bytes.Buffer
if err := t.Execute(&b, vars); err != nil {
log.Fatal(err)

Check warning on line 114 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L111-L114

Added lines #L111 - L114 were not covered by tests
}
if err := os.WriteFile(filepath.Join(dir, "go.work"), b.Bytes(), 0o644); err != nil {
log.Fatal(err)

Check warning on line 117 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L116-L117

Added lines #L116 - L117 were not covered by tests
}
defer os.Remove(filepath.Join(dir, "go.work"))

Check warning on line 119 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L119

Added line #L119 was not covered by tests

run(dir, args, paths)

Check warning on line 121 in src/cmd/goanywhere/main.go

View check run for this annotation

Codecov / codecov/patch

src/cmd/goanywhere/main.go#L121

Added line #L121 was not covered by tests
}
48 changes: 48 additions & 0 deletions src/pkg/bb/findpkg/bb.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,3 +445,51 @@ func ResolveGlobs(l ulog.Logger, genv *golang.Environ, env Env, patterns []strin
sort.Strings(paths)
return paths, nil
}

// Modules returns a list of module directories => directories of packages
// inside that module as well as packages that have no discernible module.
//
// The module for a package is determined by the **first** parent directory
// that contains a go.mod.
func Modules(paths []string) (map[string][]string, []string) {
// list of module directory => directories of packages it likely contains
moduledPackages := make(map[string][]string)
var noModulePkgs []string
for _, fullPath := range paths {
components := strings.Split(fullPath, "/")

inModule := false
for i := len(components); i >= 1; i-- {
prefixPath := "/" + filepath.Join(components[:i]...)
if _, err := os.Stat(filepath.Join(prefixPath, "go.mod")); err == nil {
moduledPackages[prefixPath] = append(moduledPackages[prefixPath], fullPath)
inModule = true
break
}
}
if !inModule {
noModulePkgs = append(noModulePkgs, fullPath)
}
}
return moduledPackages, noModulePkgs
}

func globPaths(l ulog.Logger, env Env, patterns []string) []string {
var ret []string
for _, pattern := range patterns {
if match, directories := env.Glob(l, pattern); len(directories) > 0 {
ret = append(ret, directories...)
} else if !match {
l.Printf("No match found for %v", pattern)

Check warning on line 483 in src/pkg/bb/findpkg/bb.go

View check run for this annotation

Codecov / codecov/patch

src/pkg/bb/findpkg/bb.go#L477-L483

Added lines #L477 - L483 were not covered by tests
}
}
return ret

Check warning on line 486 in src/pkg/bb/findpkg/bb.go

View check run for this annotation

Codecov / codecov/patch

src/pkg/bb/findpkg/bb.go#L486

Added line #L486 was not covered by tests
}

func GlobPaths(l ulog.Logger, env Env, patterns ...string) []string {
includes, excludes := splitExclusions(patterns)
includes = globPaths(l, env, expand(includes))
excludes = globPaths(l, env, expand(excludes))
paths := excludePaths(includes, excludes)
return paths

Check warning on line 494 in src/pkg/bb/findpkg/bb.go

View check run for this annotation

Codecov / codecov/patch

src/pkg/bb/findpkg/bb.go#L489-L494

Added lines #L489 - L494 were not covered by tests
}
58 changes: 55 additions & 3 deletions src/pkg/bb/findpkg/bb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package findpkg
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
Expand Down Expand Up @@ -58,15 +57,15 @@ func TestResolve(t *testing.T) {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.RemoveAll("./test/resolvebroken") })
if err := ioutil.WriteFile("./test/resolvebroken/main.go", []byte("broken"), 0777); err != nil {
if err := os.WriteFile("./test/resolvebroken/main.go", []byte("broken"), 0777); err != nil {
t.Fatal(err)
}

if err := os.Mkdir("./test/parsebroken", 0777); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.RemoveAll("./test/parsebroken") })
if err := ioutil.WriteFile("./test/parsebroken/main.go", []byte("package main\n\nimport \"fmt\""), 0777); err != nil {
if err := os.WriteFile("./test/parsebroken/main.go", []byte("package main\n\nimport \"fmt\""), 0777); err != nil {
t.Fatal(err)
}

Expand Down Expand Up @@ -440,3 +439,56 @@ func TestDefaultEnv(t *testing.T) {
})
}
}

func TestModules(t *testing.T) {
dir := t.TempDir()

_ = os.MkdirAll(filepath.Join(dir, "mod1/cmd/cmd1"), 0755)
_ = os.MkdirAll(filepath.Join(dir, "mod1/cmd/cmd2"), 0755)
_ = os.MkdirAll(filepath.Join(dir, "mod1/nestedmod1/cmd/cmd5"), 0755)
_ = os.MkdirAll(filepath.Join(dir, "mod1/nestedmod2/cmd/cmd6"), 0755)
_ = os.MkdirAll(filepath.Join(dir, "mod2/cmd/cmd3"), 0755)
_ = os.MkdirAll(filepath.Join(dir, "mod2/cmd/cmd4"), 0755)
_ = os.MkdirAll(filepath.Join(dir, "nomod/cmd/cmd7"), 0755)
_ = os.WriteFile(filepath.Join(dir, "mod1/go.mod"), nil, 0644)
_ = os.WriteFile(filepath.Join(dir, "mod1/nestedmod1/go.mod"), nil, 0644)
_ = os.WriteFile(filepath.Join(dir, "mod1/nestedmod2/go.mod"), nil, 0644)
_ = os.WriteFile(filepath.Join(dir, "mod2/go.mod"), nil, 0644)

paths := []string{
filepath.Join(dir, "mod1/cmd/cmd1"),
filepath.Join(dir, "mod1/cmd/cmd2"),
filepath.Join(dir, "mod1/nestedmod1/cmd/cmd5"),
filepath.Join(dir, "mod1/nestedmod2/cmd/cmd6"),
filepath.Join(dir, "mod2/cmd/cmd3"),
filepath.Join(dir, "mod2/cmd/cmd4"),
filepath.Join(dir, "nomod/cmd/cmd7"),
}
mods, noModulePkgs := Modules(paths)

want := map[string][]string{
filepath.Join(dir, "mod1"): {
filepath.Join(dir, "mod1/cmd/cmd1"),
filepath.Join(dir, "mod1/cmd/cmd2"),
},
filepath.Join(dir, "mod1/nestedmod1"): {
filepath.Join(dir, "mod1/nestedmod1/cmd/cmd5"),
},
filepath.Join(dir, "mod1/nestedmod2"): {
filepath.Join(dir, "mod1/nestedmod2/cmd/cmd6"),
},
filepath.Join(dir, "mod2"): {
filepath.Join(dir, "mod2/cmd/cmd3"),
filepath.Join(dir, "mod2/cmd/cmd4"),
},
}
if !reflect.DeepEqual(mods, want) {
t.Errorf("modules() = %v, want %v", mods, want)
}
wantNoModule := []string{
filepath.Join(dir, "nomod/cmd/cmd7"),
}
if !reflect.DeepEqual(noModulePkgs, wantNoModule) {
t.Errorf("modules() no module pkgs = %v, want %v", noModulePkgs, wantNoModule)
}
}

0 comments on commit be176df

Please sign in to comment.