@@ -6,19 +6,24 @@ package main
66
77import (
88 "bytes"
9+ "fmt"
910 "log"
1011 "os"
1112 "os/exec"
1213 "path/filepath"
14+ "regexp"
1315 "strings"
1416 "sync"
17+
18+ "golang.org/x/mod/semver"
1519)
1620
1721// A Dir describes a directory holding code by specifying
1822// the expected import path and the file system directory.
1923type Dir struct {
2024 importPath string // import path for that dir
2125 dir string // file system directory
26+ inModule bool
2227}
2328
2429// Dirs is a structure for scanning the directory tree.
@@ -113,9 +118,14 @@ func (d *Dirs) bfsWalkRoot(root Dir) {
113118 if name [0 ] == '.' || name [0 ] == '_' || name == "testdata" {
114119 continue
115120 }
116- // Ignore vendor when using modules.
117- if usingModules && name == "vendor" {
118- continue
121+ // When in a module, ignore vendor directories and stop at module boundaries.
122+ if root .inModule {
123+ if name == "vendor" {
124+ continue
125+ }
126+ if fi , err := os .Stat (filepath .Join (dir , name , "go.mod" )); err == nil && ! fi .IsDir () {
127+ continue
128+ }
119129 }
120130 // Remember this (fully qualified) directory for the next pass.
121131 next = append (next , filepath .Join (dir , name ))
@@ -129,7 +139,7 @@ func (d *Dirs) bfsWalkRoot(root Dir) {
129139 }
130140 importPath += filepath .ToSlash (dir [len (root .dir )+ 1 :])
131141 }
132- d .scan <- Dir {importPath , dir }
142+ d .scan <- Dir {importPath , dir , root . inModule }
133143 }
134144 }
135145
@@ -156,14 +166,20 @@ var codeRootsCache struct {
156166var usingModules bool
157167
158168func findCodeRoots () []Dir {
159- list := []Dir {{"" , filepath .Join (buildCtx .GOROOT , "src" )}}
160-
169+ var list []Dir
161170 if ! testGOPATH {
162171 // Check for use of modules by 'go env GOMOD',
163172 // which reports a go.mod file path if modules are enabled.
164173 stdout , _ := exec .Command ("go" , "env" , "GOMOD" ).Output ()
165174 gomod := string (bytes .TrimSpace (stdout ))
175+
166176 usingModules = len (gomod ) > 0
177+ if usingModules {
178+ list = append (list ,
179+ Dir {dir : filepath .Join (buildCtx .GOROOT , "src" ), inModule : true },
180+ Dir {importPath : "cmd" , dir : filepath .Join (buildCtx .GOROOT , "src" , "cmd" ), inModule : true })
181+ }
182+
167183 if gomod == os .DevNull {
168184 // Modules are enabled, but the working directory is outside any module.
169185 // We can still access std, cmd, and packages specified as source files
@@ -174,8 +190,9 @@ func findCodeRoots() []Dir {
174190 }
175191
176192 if ! usingModules {
193+ list = append (list , Dir {dir : filepath .Join (buildCtx .GOROOT , "src" )})
177194 for _ , root := range splitGopath () {
178- list = append (list , Dir {"" , filepath .Join (root , "src" )})
195+ list = append (list , Dir {dir : filepath .Join (root , "src" )})
179196 }
180197 return list
181198 }
@@ -185,6 +202,21 @@ func findCodeRoots() []Dir {
185202 // to handle the entire file system search and become go/packages,
186203 // but for now enumerating the module roots lets us fit modules
187204 // into the current code with as few changes as possible.
205+ mainMod , vendorEnabled , err := vendorEnabled ()
206+ if err != nil {
207+ return list
208+ }
209+ if vendorEnabled {
210+ // Add the vendor directory to the search path ahead of "std".
211+ // That way, if the main module *is* "std", we will identify the path
212+ // without the "vendor/" prefix before the one with that prefix.
213+ list = append ([]Dir {{dir : filepath .Join (mainMod .Dir , "vendor" ), inModule : false }}, list ... )
214+ if mainMod .Path != "std" {
215+ list = append (list , Dir {importPath : mainMod .Path , dir : mainMod .Dir , inModule : true })
216+ }
217+ return list
218+ }
219+
188220 cmd := exec .Command ("go" , "list" , "-m" , "-f={{.Path}}\t {{.Dir}}" , "all" )
189221 cmd .Stderr = os .Stderr
190222 out , _ := cmd .Output ()
@@ -195,9 +227,77 @@ func findCodeRoots() []Dir {
195227 }
196228 path , dir := line [:i ], line [i + 1 :]
197229 if dir != "" {
198- list = append (list , Dir {path , dir })
230+ list = append (list , Dir {importPath : path , dir : dir , inModule : true })
199231 }
200232 }
201233
202234 return list
203235}
236+
237+ // The functions below are derived from x/tools/internal/imports at CL 203017.
238+
239+ type moduleJSON struct {
240+ Path , Dir , GoVersion string
241+ }
242+
243+ var modFlagRegexp = regexp .MustCompile (`-mod[ =](\w+)` )
244+
245+ // vendorEnabled indicates if vendoring is enabled.
246+ // Inspired by setDefaultBuildMod in modload/init.go
247+ func vendorEnabled () (* moduleJSON , bool , error ) {
248+ mainMod , go114 , err := getMainModuleAnd114 ()
249+ if err != nil {
250+ return nil , false , err
251+ }
252+
253+ stdout , _ := exec .Command ("go" , "env" , "GOFLAGS" ).Output ()
254+ goflags := string (bytes .TrimSpace (stdout ))
255+ matches := modFlagRegexp .FindStringSubmatch (goflags )
256+ var modFlag string
257+ if len (matches ) != 0 {
258+ modFlag = matches [1 ]
259+ }
260+ if modFlag != "" {
261+ // Don't override an explicit '-mod=' argument.
262+ return mainMod , modFlag == "vendor" , nil
263+ }
264+ if mainMod == nil || ! go114 {
265+ return mainMod , false , nil
266+ }
267+ // Check 1.14's automatic vendor mode.
268+ if fi , err := os .Stat (filepath .Join (mainMod .Dir , "vendor" )); err == nil && fi .IsDir () {
269+ if mainMod .GoVersion != "" && semver .Compare ("v" + mainMod .GoVersion , "v1.14" ) >= 0 {
270+ // The Go version is at least 1.14, and a vendor directory exists.
271+ // Set -mod=vendor by default.
272+ return mainMod , true , nil
273+ }
274+ }
275+ return mainMod , false , nil
276+ }
277+
278+ // getMainModuleAnd114 gets the main module's information and whether the
279+ // go command in use is 1.14+. This is the information needed to figure out
280+ // if vendoring should be enabled.
281+ func getMainModuleAnd114 () (* moduleJSON , bool , error ) {
282+ const format = `{{.Path}}
283+ {{.Dir}}
284+ {{.GoVersion}}
285+ {{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}
286+ `
287+ cmd := exec .Command ("go" , "list" , "-m" , "-f" , format )
288+ cmd .Stderr = os .Stderr
289+ stdout , err := cmd .Output ()
290+ if err != nil {
291+ return nil , false , nil
292+ }
293+ lines := strings .Split (string (stdout ), "\n " )
294+ if len (lines ) < 5 {
295+ return nil , false , fmt .Errorf ("unexpected stdout: %q" , stdout )
296+ }
297+ mod := & moduleJSON {
298+ Path : lines [0 ],
299+ Dir : lines [1 ],
300+ GoVersion : lines [2 ],
301+ }
302+ return mod , lines [3 ] == "go1.14" , nil
303+ }
0 commit comments