Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions gta.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,12 +604,22 @@ func toplevel() ([]string, error) {
return gopaths()
}

root, err := moduleroot()
// In workspace mode, go list -m -f '{{.Dir}}' outputs one line per workspace
// module, producing a corrupt multi-line string when TrimSpace'd. Use
// go env GOWORK instead, which always returns a single path.
root, ok, err := workspaceroot()
if err != nil {
return nil, err
}
return []string{root}, nil
if ok {
return []string{root}, nil
}

root, err = moduleroot()
if err != nil {
return nil, err
}
return []string{root}, nil
}

func gopaths() ([]string, error) {
Expand All @@ -635,3 +645,25 @@ func moduleroot() (string, error) {

return strings.TrimSpace(string(b)), nil
}

// workspaceroot returns the directory containing the active go.work file and
// true when Go workspace mode is active.
func workspaceroot() (string, bool, error) {
cmd := exec.Command("go", "env", "GOWORK")
b, err := cmd.CombinedOutput()
if err != nil {
return "", false, fmt.Errorf("go env GOWORK: %w", err)
}
dir, ok := parseGOWORK(string(b))
return dir, ok, nil
}

// parseGOWORK interprets the raw output of `go env GOWORK`.
// "off" indicates workspace mode was explicitly disabled via GOWORK=off.
func parseGOWORK(output string) (string, bool) {
gowork := strings.TrimSpace(output)
if gowork == "" || gowork == "off" {
return "", false
}
return filepath.Dir(gowork), true
}
99 changes: 99 additions & 0 deletions gta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1082,3 +1082,102 @@ func TestDeepestUnignoredDir(t *testing.T) {
}
}
}

func TestParseGOWORK(t *testing.T) {
t.Parallel()

cases := map[string]struct {
input string
wantDir string
wantOK bool
}{
"active workspace": {
input: "/home/user/project/go.work\n",
wantDir: "/home/user/project",
wantOK: true,
},
"explicitly disabled": {
input: "off\n",
wantDir: "",
wantOK: false,
},
"empty output": {
input: "",
wantDir: "",
wantOK: false,
},
"whitespace only": {
input: " \n",
wantDir: "",
wantOK: false,
},
"workspace at filesystem root": {
input: "/go.work\n",
wantDir: "/",
wantOK: true,
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
t.Parallel()
gotDir, gotOK := parseGOWORK(tc.input)
if gotDir != tc.wantDir || gotOK != tc.wantOK {
t.Errorf("parseGOWORK(%q) = (%q, %v), want (%q, %v)",
tc.input, gotDir, gotOK, tc.wantDir, tc.wantOK)
}
})
}
}

func TestWorkspaceroot(t *testing.T) {
tmpDir := t.TempDir()

cases := map[string]struct {
gowork string
wantDir string
wantOK bool
}{
"active workspace": {
gowork: filepath.Join(tmpDir, "go.work"),
wantDir: tmpDir,
wantOK: true,
},
"disabled": {gowork: "off"},
"no workspace": {gowork: ""},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
t.Setenv("GOWORK", tc.gowork)
gotDir, gotOK, err := workspaceroot()
if err != nil {
t.Fatalf("workspaceroot() error: %v", err)
}
if gotDir != tc.wantDir || gotOK != tc.wantOK {
t.Errorf("workspaceroot() = (%q, %v), want (%q, %v)",
gotDir, gotOK, tc.wantDir, tc.wantOK)
}
})
}
}

func TestSetRootsOption(t *testing.T) {
t.Parallel()

root := "/fake/workspace/root"
g, err := New(
SetRoots(root),
SetDiffer(&testDiffer{}),
SetPackager(&testPackager{
dirs2Imports: map[string]string{},
graph: &Graph{graph: map[string]map[string]bool{}},
}),
)
if err != nil {
t.Fatalf("New() error: %v", err)
}
if len(g.roots) != 1 || g.roots[0] != root {
t.Errorf("roots = %v, want [%q]", g.roots, root)
}
}
9 changes: 9 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,12 @@ func SetIncludeTransitiveTestDeps(include bool) Option {
return nil
}
}

// SetRoots sets the root directories for the GTA. When provided, toplevel() is
// not called and the supplied roots are used directly.
func SetRoots(roots ...string) Option {
return func(g *GTA) error {
g.roots = roots
return nil
}
}
Loading