internal/genv: find directory to write output with module mode in mind

The goal of this change is to prevent genv from potentially writing
its output in the wrong location.

genv has been relying on os.Getenv("GOPATH") to figure out where to
write its output. This stopped working reliably after Go 1.8 started
having a default GOPATH value.¹ Back then, it was possible to start
using 'go env GOPATH' instead. However, with the introduction of
module mode in Go 1.11, there isn't a way to reliably pinpoint the
canonical location of a development copy of a module, because that
module can be placed in anywhere on the system.

So, start requiring that genv is run inside the golang.org/dl module.
At that point, it is possible to find the module root reliably.

Add a Go 1.13+ build constraint in order to use errors.As without
adding a requirement to golang.org/x/xerrors module. genv is a tool
used only by contributors to this module, so it doesn't need to
support Go 1.12.

Also add a missing copyright header to a test file.

¹ https://golang.org/doc/go1.8#gopath

Change-Id: Ia65e6cbb07d69481214708afaee15adb104f782c
Reviewed-on: https://go-review.googlesource.com/c/dl/+/214386
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Alexander Rakoczy <alex@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
diff --git a/internal/genv/main.go b/internal/genv/main.go
index 01e5c87..37e035d 100644
--- a/internal/genv/main.go
+++ b/internal/genv/main.go
@@ -2,11 +2,15 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+// +build go1.13
+
 // The genv command generates version-specific go command source files.
 package main
 
 import (
 	"bytes"
+	"encoding/json"
+	"errors"
 	"fmt"
 	"html/template"
 	"io/ioutil"
@@ -27,6 +31,10 @@
 	if len(os.Args) == 1 {
 		usage()
 	}
+	dlRoot, err := golangOrgDlRoot()
+	if err != nil {
+		failf("golangOrgDlRoot: %v", err)
+	}
 	for _, version := range os.Args[1:] {
 		if !strings.HasPrefix(version, "go") {
 			failf("version names should have the 'go' prefix")
@@ -47,7 +55,7 @@
 		}); err != nil {
 			failf("mainTmpl.execute: %v", err)
 		}
-		path := filepath.Join(os.Getenv("GOPATH"), "src/golang.org/dl", version, "main.go")
+		path := filepath.Join(dlRoot, version, "main.go")
 		if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
 			failf("%v", err)
 		}
@@ -113,3 +121,30 @@
 	version.Run("{{.Version}}")
 }
 `))
+
+// golangOrgDlRoot determines the directory corresponding to the root
+// of module golang.org/dl by invoking 'go list -m' in module mode.
+// It must be called with a working directory that is contained
+// by the golang.org/dl module, otherwise it returns an error.
+func golangOrgDlRoot() (string, error) {
+	cmd := exec.Command("go", "list", "-m", "-json")
+	cmd.Env = append(os.Environ(), "GO111MODULE=on")
+	out, err := cmd.Output()
+	if ee := (*exec.ExitError)(nil); errors.As(err, &ee) {
+		return "", fmt.Errorf("go command exited unsuccessfully: %v\n%s", ee.ProcessState.String(), ee.Stderr)
+	} else if err != nil {
+		return "", err
+	}
+	var mod struct {
+		Path string // Module path.
+		Dir  string // Directory holding files for this module.
+	}
+	err = json.Unmarshal(out, &mod)
+	if err != nil {
+		return "", err
+	}
+	if mod.Path != "golang.org/dl" {
+		return "", fmt.Errorf("working directory must be in module golang.org/dl, but 'go list -m' reports it's currently in module %s", mod.Path)
+	}
+	return mod.Dir, nil
+}
diff --git a/internal/genv/main_test.go b/internal/genv/main_test.go
index b2ad3cf..ecffed2 100644
--- a/internal/genv/main_test.go
+++ b/internal/genv/main_test.go
@@ -1,3 +1,9 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13
+
 package main
 
 import "testing"