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}
0 commit comments