Skip to content

Commit 7a96da1

Browse files
authored
main: fix definition and implementation of relative GOHACK (#49)
rogpeppe has a far sharper brain than me and pointed out a major issue with the previous implementation: that we hadn't actually implemented a relative value of GOHACK relative to the main module.
1 parent bfe901f commit 7a96da1

12 files changed

+275
-62
lines changed

Diff for: cmdget.go

+18-15
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,6 @@ func runGet1(args []string) error {
6161
// Perhaps we should be more resilient in that case?
6262
return errors.Notef(err, nil, "cannot get module info")
6363
}
64-
modf, err := goModInfo()
65-
if err != nil {
66-
return errors.Notef(err, nil, "cannot get local module info")
67-
}
6864
for _, mpath := range args {
6965
m := mods[mpath]
7066
if m == nil {
@@ -74,7 +70,7 @@ func runGet1(args []string) error {
7470
// Early check that we can replace the module, so we don't
7571
// do all the work to check it out only to find we can't
7672
// add the replace directive.
77-
if err := checkCanReplace(modf, mpath); err != nil {
73+
if err := checkCanReplace(mainModFile, mpath); err != nil {
7874
errorf("%v", err)
7975
continue
8076
}
@@ -99,9 +95,8 @@ func runGet1(args []string) error {
9995
}
10096
repl = repl1
10197
}
102-
// Automatically generate a go.mod file if one doesn't
103-
// already exist, because otherwise the directory cannot be
104-
// used as a module.
98+
// Automatically generate a go.mod file if one doesn't already exist,
99+
// because otherwise the directory cannot be used as a module.
105100
if err := ensureGoModFile(repl.modulePath, repl.dir); err != nil {
106101
errorf("%v", err)
107102
}
@@ -110,14 +105,14 @@ func runGet1(args []string) error {
110105
if len(repls) == 0 {
111106
return errors.New("all modules failed; not replacing anything")
112107
}
113-
if err := replace(modf, repls); err != nil {
108+
if err := replace(mainModFile, repls); err != nil {
114109
return errors.Notef(err, nil, "cannot replace")
115110
}
116-
if err := writeModFile(modf); err != nil {
111+
if err := writeModFile(mainModFile); err != nil {
117112
return errors.Wrap(err)
118113
}
119114
for _, info := range repls {
120-
fmt.Printf("%s => %s\n", info.modulePath, info.dir)
115+
fmt.Printf("%s => %s\n", info.modulePath, info.replDir)
121116
}
122117
return nil
123118
}
@@ -130,14 +125,18 @@ func updateFromLocalDir(m *listModule) (*modReplace, error) {
130125
if err != nil {
131126
return nil, errors.Notef(err, nil, "cannot hash %q", m.Dir)
132127
}
133-
destDir := moduleDir(m.Path)
128+
destDir, replDir, err := moduleDir(m.Path)
129+
if err != nil {
130+
return nil, errors.Notef(err, nil, "failed to determine target directory for %v", m.Path)
131+
}
134132
_, err = os.Stat(destDir)
135133
if err != nil && !os.IsNotExist(err) {
136134
return nil, errors.Wrap(err)
137135
}
138136
repl := &modReplace{
139137
modulePath: m.Path,
140138
dir: destDir,
139+
replDir: replDir,
141140
}
142141
if err != nil {
143142
// Destination doesn't exist. Copy the entire directory.
@@ -234,18 +233,22 @@ func updateVCSDir(m *listModule) (*modReplace, error) {
234233
return &modReplace{
235234
modulePath: m.Path,
236235
dir: info.dir,
236+
replDir: info.replDir,
237237
}, nil
238238
}
239239

240240
type modReplace struct {
241+
// modulePath is the module path
241242
modulePath string
242-
dir string
243+
// dir holds the absolute path to the replacement directory.
244+
dir string
245+
// replDir holds the path to use for the module in the go.mod replace directive.
246+
replDir string
243247
}
244248

245249
func replace(f *modfile.File, repls []*modReplace) error {
246250
for _, repl := range repls {
247-
// TODO should we use relative path here?
248-
if err := replaceModule(f, repl.modulePath, repl.dir); err != nil {
251+
if err := replaceModule(f, repl.modulePath, repl.replDir); err != nil {
249252
return errors.Wrap(err)
250253
}
251254
}

Diff for: cmdstatus.go

+1-7
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package main
22

33
import (
44
"fmt"
5-
6-
"gopkg.in/errgo.v2/fmt/errors"
75
)
86

97
var statusCommand = &Command{
@@ -30,11 +28,7 @@ func cmdStatus(_ *Command, args []string) int {
3028
}
3129

3230
func printReplacementInfo() error {
33-
m, err := goModInfo()
34-
if err != nil {
35-
return errors.Wrap(err)
36-
}
37-
for _, r := range m.Replace {
31+
for _, r := range mainModFile.Replace {
3832
if r.Old.Version == "" && r.New.Version == "" {
3933
fmt.Printf("%s => %s\n", r.Old.Path, r.New.Path)
4034
}

Diff for: cmdundo.go

+4-8
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ func cmdUndo(_ *Command, args []string) int {
3939
}
4040

4141
func cmdUndo1(modules []string) error {
42-
modFile, err := goModInfo()
43-
if err != nil {
44-
return errors.Wrap(err)
45-
}
4642
modMap := make(map[string]bool)
4743
if len(modules) > 0 {
4844
for _, m := range modules {
@@ -51,15 +47,15 @@ func cmdUndo1(modules []string) error {
5147
} else {
5248
// With no modules specified, we un-gohack all modules
5349
// we can find with local directory info in the go.mod file.
54-
for _, r := range modFile.Replace {
50+
for _, r := range mainModFile.Replace {
5551
if r.Old.Version == "" && r.New.Version == "" {
5652
modMap[r.Old.Path] = true
5753
modules = append(modules, r.Old.Path)
5854
}
5955
}
6056
}
6157
drop := make(map[string]bool)
62-
for _, r := range modFile.Replace {
58+
for _, r := range mainModFile.Replace {
6359
if !modMap[r.Old.Path] || r.Old.Version != "" || r.New.Version != "" {
6460
continue
6561
}
@@ -93,7 +89,7 @@ func cmdUndo1(modules []string) error {
9389
delete(modMap, r.Old.Path)
9490
}
9591
for m := range drop {
96-
if err := modFile.DropReplace(m, ""); err != nil {
92+
if err := mainModFile.DropReplace(m, ""); err != nil {
9793
return errors.Notef(err, nil, "cannot drop replacement for %v", m)
9894
}
9995
}
@@ -105,7 +101,7 @@ func cmdUndo1(modules []string) error {
105101
for _, m := range failed {
106102
errorf("%s not currently replaced; cannot drop", m)
107103
}
108-
if err := writeModFile(modFile); err != nil {
104+
if err := writeModFile(mainModFile); err != nil {
109105
return errors.Wrap(err)
110106
}
111107
for _, m := range modules {

Diff for: main.go

+33-11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"fmt"
1313
"os"
1414

15+
"github.com/rogpeppe/go-internal/modfile"
1516
"gopkg.in/errgo.v2/fmt/errors"
1617
)
1718

@@ -52,6 +53,8 @@ var (
5253
var (
5354
exitCode = 0
5455
cwd = "."
56+
57+
mainModFile *modfile.File
5558
)
5659

5760
var commands = []*Command{
@@ -68,7 +71,7 @@ func main1() int {
6871
if dir, err := os.Getwd(); err == nil {
6972
cwd = dir
7073
} else {
71-
errorf("cannot get current working directory: %v", err)
74+
return errorf("cannot get current working directory: %v", err)
7275
}
7376
flag.Usage = func() {
7477
mainUsage(os.Stderr)
@@ -83,22 +86,40 @@ func main1() int {
8386
if cmdName == "help" {
8487
return runHelp(args)
8588
}
86-
for _, cmd := range commands {
87-
if cmd.Name() != cmdName {
88-
continue
89+
var cmd *Command
90+
for _, c := range commands {
91+
if c.Name() == cmdName {
92+
cmd = c
93+
break
94+
}
95+
}
96+
if cmd == nil {
97+
errorf("gohack %s: unknown command\nRun 'gohack help' for usage\n", cmdName)
98+
return 2
99+
}
100+
101+
cmd.Flag.Usage = func() { cmd.Usage() }
102+
103+
if err := cmd.Flag.Parse(args); err != nil {
104+
if err != flag.ErrHelp {
105+
errorf(err.Error())
89106
}
90-
cmd.Flag.Usage = func() { cmd.Usage() }
91-
cmd.Flag.Parse(args)
92-
rcode := cmd.Run(cmd, cmd.Flag.Args())
93-
return max(exitCode, rcode)
107+
return 2
108+
}
109+
110+
if mf, err := goModInfo(); err == nil {
111+
mainModFile = mf
112+
} else {
113+
return errorf("cannot determine main module: %v", err)
94114
}
95-
errorf("gohack %s: unknown command\nRun 'gohack help' for usage\n", cmdName)
96-
return 2
115+
116+
rcode := cmd.Run(cmd, cmd.Flag.Args())
117+
return max(exitCode, rcode)
97118
}
98119

99120
const debug = false
100121

101-
func errorf(f string, a ...interface{}) {
122+
func errorf(f string, a ...interface{}) int {
102123
fmt.Fprintln(os.Stderr, fmt.Sprintf(f, a...))
103124
if debug {
104125
for _, arg := range a {
@@ -108,6 +129,7 @@ func errorf(f string, a ...interface{}) {
108129
}
109130
}
110131
exitCode = 1
132+
return exitCode
111133
}
112134

113135
func max(a, b int) int {

Diff for: mod.go

+38-15
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"io"
55
"os"
66
"path/filepath"
7+
"strings"
78

89
"github.com/rogpeppe/go-internal/dirhash"
910
"golang.org/x/tools/go/vcs"
@@ -45,8 +46,10 @@ type moduleVCSInfo struct {
4546
module *listModule
4647
// alreadyExists holds whether the replacement directory already exists.
4748
alreadyExists bool
48-
// dir holds the path to the replacement directory.
49+
// dir holds the absolute path to the replacement directory.
4950
dir string
51+
// replDir holds the path to use for the module in the go.mod replace directive.
52+
replDir string
5053
// root holds information on the VCS root of the module.
5154
root *vcs.RepoRoot
5255
// vcs holds the implementation of the VCS used by the module.
@@ -71,7 +74,10 @@ func getVCSInfoForModule(m *listModule) (*moduleVCSInfo, error) {
7174
if !ok {
7275
return nil, errors.Newf("unknown VCS kind %q", root.VCS.Cmd)
7376
}
74-
dir := moduleDir(m.Path)
77+
dir, replDir, err := moduleDir(m.Path)
78+
if err != nil {
79+
return nil, errors.Notef(err, nil, "failed to determine target directory for %v", m.Path)
80+
}
7581
dirInfo, err := os.Stat(dir)
7682
if err != nil && !os.IsNotExist(err) {
7783
return nil, errors.Wrap(err)
@@ -84,6 +90,7 @@ func getVCSInfoForModule(m *listModule) (*moduleVCSInfo, error) {
8490
root: root,
8591
alreadyExists: err == nil,
8692
dir: dir,
93+
replDir: replDir,
8794
vcs: v,
8895
}
8996
if !info.alreadyExists {
@@ -108,22 +115,38 @@ func getVCSInfoForModule(m *listModule) (*moduleVCSInfo, error) {
108115
return info, nil
109116
}
110117

111-
// moduleDir returns the path to the directory to be
112-
// used for storing the module with the given path.
113-
func moduleDir(module string) string {
114-
// TODO decide what color this bikeshed should be.
115-
d := filepath.FromSlash(os.Getenv("GOHACK"))
118+
// moduleDir returns the path to the directory to be used for storing the
119+
// module with the given path, as well as the filepath to be used in a replace
120+
// directive. If $GOHACK is set then it will be used. A relative $GOHACK will
121+
// be interpreted relative to main module directory.
122+
func moduleDir(module string) (path string, replPath string, err error) {
123+
modfp := filepath.FromSlash(module)
124+
d := os.Getenv("GOHACK")
116125
if d == "" {
117-
d = filepath.Join(os.Getenv("HOME"), "gohack")
126+
uhd, err := UserHomeDir()
127+
if err != nil {
128+
return "", "", errors.Notef(err, nil, "failed to determine user home dir")
129+
}
130+
path = filepath.Join(uhd, "gohack", modfp)
131+
return path, path, nil
118132
}
119133

120-
return join(d, filepath.FromSlash(module))
121-
}
134+
if filepath.IsAbs(d) {
135+
path = filepath.Join(d, modfp)
136+
return path, path, nil
137+
}
122138

123-
func join(ps ...string) string {
124-
res := filepath.Join(ps...)
125-
if !filepath.IsAbs(res) && res[0] != '.' {
126-
res = "." + string(os.PathSeparator) + res
139+
replPath = filepath.Join(d, modfp)
140+
if !strings.HasPrefix(replPath, ".."+string(os.PathSeparator)) {
141+
// We know replPath is relative, but filepath.Join strips any leading
142+
// "./" prefix, and we need that in the replace directive because
143+
// otherwise the path will be treated as a module path rather than a
144+
// relative file path, so add it back.
145+
replPath = "." + string(os.PathSeparator) + replPath
127146
}
128-
return res
147+
148+
mainModDir := filepath.Dir(mainModFile.Syntax.Name)
149+
path = filepath.Join(mainModDir, replPath)
150+
151+
return path, replPath, err
129152
}

Diff for: os_1.11.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// +build !go1.12
2+
3+
package main
4+
5+
import (
6+
"errors"
7+
"os"
8+
"runtime"
9+
)
10+
11+
// UserHomeDir was introduced in Go 1.12. When we drop support for Go 1.11, we can
12+
// lose this file.
13+
14+
// UserHomeDir returns the current user's home directory.
15+
//
16+
// On Unix, including macOS, it returns the $HOME environment variable.
17+
// On Windows, it returns %USERPROFILE%.
18+
// On Plan 9, it returns the $home environment variable.
19+
func UserHomeDir() (string, error) {
20+
env, enverr := "HOME", "$HOME"
21+
switch runtime.GOOS {
22+
case "windows":
23+
env, enverr = "USERPROFILE", "%userprofile%"
24+
case "plan9":
25+
env, enverr = "home", "$home"
26+
case "nacl", "android":
27+
return "/", nil
28+
case "darwin":
29+
if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
30+
return "/", nil
31+
}
32+
}
33+
if v := os.Getenv(env); v != "" {
34+
return v, nil
35+
}
36+
return "", errors.New(enverr + " is not defined")
37+
}

Diff for: os_1.12.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// +build go1.12
2+
3+
package main
4+
5+
import "os"
6+
7+
// UserHomeDir was introduced in Go 1.12. When we drop support for Go 1.11, we can
8+
// lose this file.
9+
10+
func UserHomeDir() (string, error) {
11+
return os.UserHomeDir()
12+
}

0 commit comments

Comments
 (0)