Skip to content

Commit

Permalink
go/build: implement default GOPATH
Browse files Browse the repository at this point in the history
Whenever GOPATH is not defined in the environment, use $HOME/go
as its default value. For Windows systems use %USERPROFILE%/go
and $home/go for plan9.

The choice of these environment variables is based on what Docker
currently does. The os/user package is not used to avoid having
a cgo dependency.

Updates #17262. Documentation changes forthcoming.

Change-Id: I6368fbfbc5afda99d6e64c35c1980076fcf45344
Reviewed-on: https://go-review.googlesource.com/32019
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
  • Loading branch information
campoy committed Nov 11, 2016
1 parent ebc0b62 commit dc4a815
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 8 deletions.
2 changes: 1 addition & 1 deletion src/cmd/go/env.go
Expand Up @@ -40,7 +40,7 @@ func mkEnv() []envVar {
{"GOHOSTARCH", runtime.GOARCH},
{"GOHOSTOS", runtime.GOOS},
{"GOOS", goos},
{"GOPATH", os.Getenv("GOPATH")},
{"GOPATH", buildContext.GOPATH},
{"GORACE", os.Getenv("GORACE")},
{"GOROOT", goroot},
{"GOTOOLDIR", toolDir},
Expand Down
12 changes: 12 additions & 0 deletions src/cmd/go/get.go
Expand Up @@ -417,6 +417,10 @@ func downloadPackage(p *Package) error {
if list[0] == goroot {
return fmt.Errorf("cannot download, $GOPATH must not be set to $GOROOT. For more details see: 'go help gopath'")
}
if _, err := os.Stat(filepath.Join(list[0], "src/cmd/go/alldocs.go")); err == nil {
return fmt.Errorf("cannot download, %s is a GOROOT, not a GOPATH. For more details see: 'go help gopath'", list[0])
}
p.build.Root = list[0]
p.build.SrcRoot = filepath.Join(list[0], "src")
p.build.PkgRoot = filepath.Join(list[0], "pkg")
}
Expand Down Expand Up @@ -445,11 +449,19 @@ func downloadPackage(p *Package) error {
if _, err := os.Stat(root); err == nil {
return fmt.Errorf("%s exists but %s does not - stale checkout?", root, meta)
}

_, err := os.Stat(p.build.Root)
gopathExisted := err == nil

// Some version control tools require the parent of the target to exist.
parent, _ := filepath.Split(root)
if err = os.MkdirAll(parent, 0777); err != nil {
return err
}
if buildV && !gopathExisted && p.build.Root == buildContext.GOPATH {
fmt.Fprintf(os.Stderr, "created GOPATH=%s; see 'go help gopath'\n", p.build.Root)
}

if err = vcs.create(root, repo); err != nil {
return err
}
Expand Down
145 changes: 140 additions & 5 deletions src/cmd/go/go_test.go
Expand Up @@ -1664,15 +1664,150 @@ func TestMentionGOPATHNotOnSecondEntry(t *testing.T) {
}
}

// Test missing GOPATH is reported.
func TestMissingGOPATHIsReported(t *testing.T) {
func homeEnvName() string {
switch runtime.GOOS {
case "windows":
return "USERPROFILE"
case "plan9":
return "home"
default:
return "HOME"
}
}

// Test go env missing GOPATH shows default.
func TestMissingGOPATHEnvShowsDefault(t *testing.T) {
tg := testgo(t)
defer tg.cleanup()
tg.parallel()
tg.setenv("GOPATH", "")
tg.runFail("install", "foo/quxx")
if tg.grepCountBoth(`\(\$GOPATH not set\. For more details see: 'go help gopath'\)$`) != 1 {
t.Error(`go install foo/quxx expected error: ($GOPATH not set. For more details see: 'go help gopath')`)
tg.run("env", "GOPATH")

want := filepath.Join(os.Getenv(homeEnvName()), "go")
got := strings.TrimSpace(tg.getStdout())
if got != want {
t.Errorf("got %q; want %q", got, want)
}
}

// Test go get missing GOPATH causes go get to warn if directory doesn't exist.
func TestMissingGOPATHGetWarnsIfNotExists(t *testing.T) {
if _, err := exec.LookPath("git"); err != nil {
t.Skip("skipping because git binary not found")
}

tg := testgo(t)
defer tg.cleanup()

// setenv variables for test and defer deleting temporary home directory.
tg.setenv("GOPATH", "")
tmp, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("could not create tmp home: %v", err)
}
defer os.RemoveAll(tmp)
tg.setenv(homeEnvName(), tmp)

tg.run("get", "-v", "github.com/golang/example/hello")

want := fmt.Sprintf("created GOPATH=%s; see 'go help gopath'", filepath.Join(tmp, "go"))
got := strings.TrimSpace(tg.getStderr())
if !strings.Contains(got, want) {
t.Errorf("got %q; want %q", got, want)
}
}

// Test go get missing GOPATH causes no warning if directory exists.
func TestMissingGOPATHGetDoesntWarnIfExists(t *testing.T) {
if _, err := exec.LookPath("git"); err != nil {
t.Skip("skipping because git binary not found")
}

tg := testgo(t)
defer tg.cleanup()

// setenv variables for test and defer resetting them.
tg.setenv("GOPATH", "")
tmp, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("could not create tmp home: %v", err)
}
defer os.RemoveAll(tmp)
if err := os.Mkdir(filepath.Join(tmp, "go"), 0777); err != nil {
t.Fatalf("could not create $HOME/go: %v", err)
}

tg.setenv(homeEnvName(), tmp)

tg.run("get", "github.com/golang/example/hello")

got := strings.TrimSpace(tg.getStderr())
if got != "" {
t.Errorf("got %q; wants empty", got)
}
}

// Test go get missing GOPATH fails if pointed file is not a directory.
func TestMissingGOPATHGetFailsIfItsNotDirectory(t *testing.T) {
tg := testgo(t)
defer tg.cleanup()

// setenv variables for test and defer resetting them.
tg.setenv("GOPATH", "")
tmp, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("could not create tmp home: %v", err)
}
defer os.RemoveAll(tmp)

path := filepath.Join(tmp, "go")
if err := ioutil.WriteFile(path, nil, 0777); err != nil {
t.Fatalf("could not create GOPATH at %s: %v", path, err)
}
tg.setenv(homeEnvName(), tmp)

const pkg = "github.com/golang/example/hello"
tg.runFail("get", pkg)

msg := "not a directory"
if runtime.GOOS == "windows" {
msg = "The system cannot find the path specified."
}
want := fmt.Sprintf("package %s: mkdir %s: %s", pkg, filepath.Join(tmp, "go"), msg)
got := strings.TrimSpace(tg.getStderr())
if got != want {
t.Errorf("got %q; wants %q", got, want)
}
}

// Test go install of missing package when missing GOPATH fails and shows default GOPATH.
func TestMissingGOPATHInstallMissingPackageFailsAndShowsDefault(t *testing.T) {
tg := testgo(t)
defer tg.cleanup()

// setenv variables for test and defer resetting them.
tg.setenv("GOPATH", "")
tmp, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("could not create tmp home: %v", err)
}
defer os.RemoveAll(tmp)
if err := os.Mkdir(filepath.Join(tmp, "go"), 0777); err != nil {
t.Fatalf("could not create $HOME/go: %v", err)
}
tg.setenv(homeEnvName(), tmp)

const pkg = "github.com/golang/example/hello"
tg.runFail("install", pkg)

pkgPath := filepath.Join(strings.Split(pkg, "/")...)
want := fmt.Sprintf("can't load package: package %s: cannot find package \"%s\" in any of:", pkg, pkg) +
fmt.Sprintf("\n\t%s (from $GOROOT)", filepath.Join(runtime.GOROOT(), "src", pkgPath)) +
fmt.Sprintf("\n\t%s (from $GOPATH)", filepath.Join(tmp, "go", "src", pkgPath))

got := strings.TrimSpace(tg.getStderr())
if got != want {
t.Errorf("got %q; wants %q", got, want)
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/cmd/go/main.go
Expand Up @@ -135,7 +135,7 @@ func main() {
// Diagnose common mistake: GOPATH==GOROOT.
// This setting is equivalent to not setting GOPATH at all,
// which is not what most people want when they do it.
if gopath := os.Getenv("GOPATH"); gopath == runtime.GOROOT() {
if gopath := buildContext.GOPATH; gopath == runtime.GOROOT() {
fmt.Fprintf(os.Stderr, "warning: GOPATH set to GOROOT (%s) has no effect\n", gopath)
} else {
for _, p := range filepath.SplitList(gopath) {
Expand Down
15 changes: 14 additions & 1 deletion src/go/build/build.go
Expand Up @@ -256,13 +256,26 @@ func (ctxt *Context) SrcDirs() []string {
// if set, or else the compiled code's GOARCH, GOOS, and GOROOT.
var Default Context = defaultContext()

func defaultGOPATH() string {
env := "HOME"
if runtime.GOOS == "windows" {
env = "USERPROFILE"
} else if runtime.GOOS == "plan9" {
env = "home"
}
if home := os.Getenv(env); home != "" {
return filepath.Join(home, "go")
}
return ""
}

func defaultContext() Context {
var c Context

c.GOARCH = envOr("GOARCH", runtime.GOARCH)
c.GOOS = envOr("GOOS", runtime.GOOS)
c.GOROOT = pathpkg.Clean(runtime.GOROOT())
c.GOPATH = envOr("GOPATH", "")
c.GOPATH = envOr("GOPATH", defaultGOPATH())
c.Compiler = runtime.Compiler

// Each major Go release in the Go 1.x series should add a tag here.
Expand Down

0 comments on commit dc4a815

Please sign in to comment.