Skip to content

Commit 6eec81c

Browse files
committed
cmd/godoc: support automatic vendoring
Fixes golang/go#35429 Change-Id: I060ccfbed4c3975d1ddc94fda4fadea527b29841 Reviewed-on: https://go-review.googlesource.com/c/tools/+/232958 Run-TryBot: Agniva De Sarker <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent 1b747fd commit 6eec81c

File tree

3 files changed

+171
-110
lines changed

3 files changed

+171
-110
lines changed

cmd/godoc/main.go

+47-25
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package main
1919
import (
2020
"archive/zip"
2121
"bytes"
22+
"context"
2223
"encoding/json"
2324
_ "expvar" // to serve /debug/vars
2425
"flag"
@@ -44,6 +45,7 @@ import (
4445
"golang.org/x/tools/godoc/vfs/gatefs"
4546
"golang.org/x/tools/godoc/vfs/mapfs"
4647
"golang.org/x/tools/godoc/vfs/zipfs"
48+
"golang.org/x/tools/internal/gocommand"
4749
"golang.org/x/xerrors"
4850
)
4951

@@ -210,28 +212,48 @@ func main() {
210212
usage()
211213
}
212214

213-
// Try to download dependencies that are not in the module cache in order to
214-
// to show their documentation.
215-
// This may fail if module downloading is disallowed (GOPROXY=off) or due to
216-
// limited connectivity, in which case we print errors to stderr and show
217-
// documentation only for packages that are available.
218-
fillModuleCache(os.Stderr, goModFile)
219-
220-
// Determine modules in the build list.
221-
mods, err := buildList(goModFile)
215+
// Detect whether to use vendor mode or not.
216+
mainMod, vendorEnabled, err := gocommand.VendorEnabled(context.Background(), gocommand.Invocation{}, &gocommand.Runner{})
222217
if err != nil {
223-
fmt.Fprintf(os.Stderr, "failed to determine the build list of the main module: %v", err)
218+
fmt.Fprintf(os.Stderr, "failed to determine if vendoring is enabled: %v", err)
224219
os.Exit(1)
225220
}
221+
if vendorEnabled {
222+
// Bind the root directory of the main module.
223+
fs.Bind(path.Join("/src", mainMod.Path), gatefs.New(vfs.OS(mainMod.Dir), fsGate), "/", vfs.BindAfter)
224+
225+
// Bind the vendor directory.
226+
//
227+
// Note that in module mode, vendor directories in locations
228+
// other than the main module's root directory are ignored.
229+
// See https://golang.org/ref/mod#vendoring.
230+
vendorDir := filepath.Join(mainMod.Dir, "vendor")
231+
fs.Bind("/src", gatefs.New(vfs.OS(vendorDir), fsGate), "/", vfs.BindAfter)
232+
233+
} else {
234+
// Try to download dependencies that are not in the module cache in order to
235+
// to show their documentation.
236+
// This may fail if module downloading is disallowed (GOPROXY=off) or due to
237+
// limited connectivity, in which case we print errors to stderr and show
238+
// documentation only for packages that are available.
239+
fillModuleCache(os.Stderr, goModFile)
240+
241+
// Determine modules in the build list.
242+
mods, err := buildList(goModFile)
243+
if err != nil {
244+
fmt.Fprintf(os.Stderr, "failed to determine the build list of the main module: %v", err)
245+
os.Exit(1)
246+
}
226247

227-
// Bind module trees into Go root.
228-
for _, m := range mods {
229-
if m.Dir == "" {
230-
// Module is not available in the module cache, skip it.
231-
continue
248+
// Bind module trees into Go root.
249+
for _, m := range mods {
250+
if m.Dir == "" {
251+
// Module is not available in the module cache, skip it.
252+
continue
253+
}
254+
dst := path.Join("/src", m.Path)
255+
fs.Bind(dst, gatefs.New(vfs.OS(m.Dir), fsGate), "/", vfs.BindAfter)
232256
}
233-
dst := path.Join("/src", m.Path)
234-
fs.Bind(dst, gatefs.New(vfs.OS(m.Dir), fsGate), "/", vfs.BindAfter)
235257
}
236258
} else {
237259
fmt.Println("using GOPATH mode")
@@ -395,7 +417,7 @@ func goMod() (string, error) {
395417
// with all dependencies of the main module in the current directory
396418
// by invoking the go command. Module download logs are streamed to w.
397419
// If there are any problems encountered, they are also written to w.
398-
// It should only be used when operating in module mode.
420+
// It should only be used in module mode, when vendor mode isn't on.
399421
//
400422
// See https://golang.org/cmd/go/#hdr-Download_modules_to_local_cache.
401423
func fillModuleCache(w io.Writer, goMod string) {
@@ -436,9 +458,14 @@ func fillModuleCache(w io.Writer, goMod string) {
436458
}
437459
}
438460

461+
type mod struct {
462+
Path string // Module path.
463+
Dir string // Directory holding files for this module, if any.
464+
}
465+
439466
// buildList determines the build list in the current directory
440-
// by invoking the go command. It should only be used when operating
441-
// in module mode.
467+
// by invoking the go command. It should only be used in module mode,
468+
// when vendor mode isn't on.
442469
//
443470
// See https://golang.org/cmd/go/#hdr-The_main_module_and_the_build_list.
444471
func buildList(goMod string) ([]mod, error) {
@@ -467,11 +494,6 @@ func buildList(goMod string) ([]mod, error) {
467494
return mods, nil
468495
}
469496

470-
type mod struct {
471-
Path string // Module path.
472-
Dir string // Directory holding files for this module, if any.
473-
}
474-
475497
// moduleFS is a vfs.FileSystem wrapper used when godoc is running
476498
// in module mode. It's needed so that packages inside modules are
477499
// considered to be third party.

internal/gocommand/vendor.go

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package gocommand
6+
7+
import (
8+
"bytes"
9+
"context"
10+
"fmt"
11+
"os"
12+
"path/filepath"
13+
"regexp"
14+
"strings"
15+
16+
"golang.org/x/mod/semver"
17+
)
18+
19+
// ModuleJSON holds information about a module.
20+
type ModuleJSON struct {
21+
Path string // module path
22+
Replace *ModuleJSON // replaced by this module
23+
Main bool // is this the main module?
24+
Indirect bool // is this module only an indirect dependency of main module?
25+
Dir string // directory holding files for this module, if any
26+
GoMod string // path to go.mod file for this module, if any
27+
GoVersion string // go version used in module
28+
}
29+
30+
var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`)
31+
32+
// VendorEnabled reports whether vendoring is enabled. It takes a *Runner to execute Go commands
33+
// with the supplied context.Context and Invocation. The Invocation can contain pre-defined fields,
34+
// of which only Verb and Args are modified to run the appropriate Go command.
35+
// Inspired by setDefaultBuildMod in modload/init.go
36+
func VendorEnabled(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) {
37+
mainMod, go114, err := getMainModuleAnd114(ctx, inv, r)
38+
if err != nil {
39+
return nil, false, err
40+
}
41+
42+
// We check the GOFLAGS to see if there is anything overridden or not.
43+
inv.Verb = "env"
44+
inv.Args = []string{"GOFLAGS"}
45+
stdout, err := r.Run(ctx, inv)
46+
if err != nil {
47+
return nil, false, err
48+
}
49+
goflags := string(bytes.TrimSpace(stdout.Bytes()))
50+
matches := modFlagRegexp.FindStringSubmatch(goflags)
51+
var modFlag string
52+
if len(matches) != 0 {
53+
modFlag = matches[1]
54+
}
55+
if modFlag != "" {
56+
// Don't override an explicit '-mod=' argument.
57+
return mainMod, modFlag == "vendor", nil
58+
}
59+
if mainMod == nil || !go114 {
60+
return mainMod, false, nil
61+
}
62+
// Check 1.14's automatic vendor mode.
63+
if fi, err := os.Stat(filepath.Join(mainMod.Dir, "vendor")); err == nil && fi.IsDir() {
64+
if mainMod.GoVersion != "" && semver.Compare("v"+mainMod.GoVersion, "v1.14") >= 0 {
65+
// The Go version is at least 1.14, and a vendor directory exists.
66+
// Set -mod=vendor by default.
67+
return mainMod, true, nil
68+
}
69+
}
70+
return mainMod, false, nil
71+
}
72+
73+
// getMainModuleAnd114 gets the main module's information and whether the
74+
// go command in use is 1.14+. This is the information needed to figure out
75+
// if vendoring should be enabled.
76+
func getMainModuleAnd114(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) {
77+
const format = `{{.Path}}
78+
{{.Dir}}
79+
{{.GoMod}}
80+
{{.GoVersion}}
81+
{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}
82+
`
83+
inv.Verb = "list"
84+
inv.Args = []string{"-m", "-f", format}
85+
stdout, err := r.Run(ctx, inv)
86+
if err != nil {
87+
return nil, false, err
88+
}
89+
90+
lines := strings.Split(stdout.String(), "\n")
91+
if len(lines) < 5 {
92+
return nil, false, fmt.Errorf("unexpected stdout: %q", stdout.String())
93+
}
94+
mod := &ModuleJSON{
95+
Path: lines[0],
96+
Dir: lines[1],
97+
GoMod: lines[2],
98+
GoVersion: lines[3],
99+
Main: true,
100+
}
101+
return mod, lines[4] == "go1.14", nil
102+
}

0 commit comments

Comments
 (0)