Skip to content

Commit

Permalink
cmd/godoc: support automatic vendoring
Browse files Browse the repository at this point in the history
Fixes golang/go#35429

Change-Id: I060ccfbed4c3975d1ddc94fda4fadea527b29841
Reviewed-on: https://go-review.googlesource.com/c/tools/+/232958
Run-TryBot: Agniva De Sarker <agniva.quicksilver@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
  • Loading branch information
agnivade committed Jun 9, 2020
1 parent 1b747fd commit 6eec81c
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 110 deletions.
72 changes: 47 additions & 25 deletions cmd/godoc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package main
import (
"archive/zip"
"bytes"
"context"
"encoding/json"
_ "expvar" // to serve /debug/vars
"flag"
Expand All @@ -44,6 +45,7 @@ import (
"golang.org/x/tools/godoc/vfs/gatefs"
"golang.org/x/tools/godoc/vfs/mapfs"
"golang.org/x/tools/godoc/vfs/zipfs"
"golang.org/x/tools/internal/gocommand"
"golang.org/x/xerrors"
)

Expand Down Expand Up @@ -210,28 +212,48 @@ func main() {
usage()
}

// Try to download dependencies that are not in the module cache in order to
// to show their documentation.
// This may fail if module downloading is disallowed (GOPROXY=off) or due to
// limited connectivity, in which case we print errors to stderr and show
// documentation only for packages that are available.
fillModuleCache(os.Stderr, goModFile)

// Determine modules in the build list.
mods, err := buildList(goModFile)
// Detect whether to use vendor mode or not.
mainMod, vendorEnabled, err := gocommand.VendorEnabled(context.Background(), gocommand.Invocation{}, &gocommand.Runner{})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to determine the build list of the main module: %v", err)
fmt.Fprintf(os.Stderr, "failed to determine if vendoring is enabled: %v", err)
os.Exit(1)
}
if vendorEnabled {
// Bind the root directory of the main module.
fs.Bind(path.Join("/src", mainMod.Path), gatefs.New(vfs.OS(mainMod.Dir), fsGate), "/", vfs.BindAfter)

// Bind the vendor directory.
//
// Note that in module mode, vendor directories in locations
// other than the main module's root directory are ignored.
// See https://golang.org/ref/mod#vendoring.
vendorDir := filepath.Join(mainMod.Dir, "vendor")
fs.Bind("/src", gatefs.New(vfs.OS(vendorDir), fsGate), "/", vfs.BindAfter)

} else {
// Try to download dependencies that are not in the module cache in order to
// to show their documentation.
// This may fail if module downloading is disallowed (GOPROXY=off) or due to
// limited connectivity, in which case we print errors to stderr and show
// documentation only for packages that are available.
fillModuleCache(os.Stderr, goModFile)

// Determine modules in the build list.
mods, err := buildList(goModFile)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to determine the build list of the main module: %v", err)
os.Exit(1)
}

// Bind module trees into Go root.
for _, m := range mods {
if m.Dir == "" {
// Module is not available in the module cache, skip it.
continue
// Bind module trees into Go root.
for _, m := range mods {
if m.Dir == "" {
// Module is not available in the module cache, skip it.
continue
}
dst := path.Join("/src", m.Path)
fs.Bind(dst, gatefs.New(vfs.OS(m.Dir), fsGate), "/", vfs.BindAfter)
}
dst := path.Join("/src", m.Path)
fs.Bind(dst, gatefs.New(vfs.OS(m.Dir), fsGate), "/", vfs.BindAfter)
}
} else {
fmt.Println("using GOPATH mode")
Expand Down Expand Up @@ -395,7 +417,7 @@ func goMod() (string, error) {
// with all dependencies of the main module in the current directory
// by invoking the go command. Module download logs are streamed to w.
// If there are any problems encountered, they are also written to w.
// It should only be used when operating in module mode.
// It should only be used in module mode, when vendor mode isn't on.
//
// See https://golang.org/cmd/go/#hdr-Download_modules_to_local_cache.
func fillModuleCache(w io.Writer, goMod string) {
Expand Down Expand Up @@ -436,9 +458,14 @@ func fillModuleCache(w io.Writer, goMod string) {
}
}

type mod struct {
Path string // Module path.
Dir string // Directory holding files for this module, if any.
}

// buildList determines the build list in the current directory
// by invoking the go command. It should only be used when operating
// in module mode.
// by invoking the go command. It should only be used in module mode,
// when vendor mode isn't on.
//
// See https://golang.org/cmd/go/#hdr-The_main_module_and_the_build_list.
func buildList(goMod string) ([]mod, error) {
Expand Down Expand Up @@ -467,11 +494,6 @@ func buildList(goMod string) ([]mod, error) {
return mods, nil
}

type mod struct {
Path string // Module path.
Dir string // Directory holding files for this module, if any.
}

// moduleFS is a vfs.FileSystem wrapper used when godoc is running
// in module mode. It's needed so that packages inside modules are
// considered to be third party.
Expand Down
102 changes: 102 additions & 0 deletions internal/gocommand/vendor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2020 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.

package gocommand

import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"

"golang.org/x/mod/semver"
)

// ModuleJSON holds information about a module.
type ModuleJSON struct {
Path string // module path
Replace *ModuleJSON // replaced by this module
Main bool // is this the main module?
Indirect bool // is this module only an indirect dependency of main module?
Dir string // directory holding files for this module, if any
GoMod string // path to go.mod file for this module, if any
GoVersion string // go version used in module
}

var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`)

// VendorEnabled reports whether vendoring is enabled. It takes a *Runner to execute Go commands
// with the supplied context.Context and Invocation. The Invocation can contain pre-defined fields,
// of which only Verb and Args are modified to run the appropriate Go command.
// Inspired by setDefaultBuildMod in modload/init.go
func VendorEnabled(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) {
mainMod, go114, err := getMainModuleAnd114(ctx, inv, r)
if err != nil {
return nil, false, err
}

// We check the GOFLAGS to see if there is anything overridden or not.
inv.Verb = "env"
inv.Args = []string{"GOFLAGS"}
stdout, err := r.Run(ctx, inv)
if err != nil {
return nil, false, err
}
goflags := string(bytes.TrimSpace(stdout.Bytes()))
matches := modFlagRegexp.FindStringSubmatch(goflags)
var modFlag string
if len(matches) != 0 {
modFlag = matches[1]
}
if modFlag != "" {
// Don't override an explicit '-mod=' argument.
return mainMod, modFlag == "vendor", nil
}
if mainMod == nil || !go114 {
return mainMod, false, nil
}
// Check 1.14's automatic vendor mode.
if fi, err := os.Stat(filepath.Join(mainMod.Dir, "vendor")); err == nil && fi.IsDir() {
if mainMod.GoVersion != "" && semver.Compare("v"+mainMod.GoVersion, "v1.14") >= 0 {
// The Go version is at least 1.14, and a vendor directory exists.
// Set -mod=vendor by default.
return mainMod, true, nil
}
}
return mainMod, false, nil
}

// getMainModuleAnd114 gets the main module's information and whether the
// go command in use is 1.14+. This is the information needed to figure out
// if vendoring should be enabled.
func getMainModuleAnd114(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) {
const format = `{{.Path}}
{{.Dir}}
{{.GoMod}}
{{.GoVersion}}
{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}
`
inv.Verb = "list"
inv.Args = []string{"-m", "-f", format}
stdout, err := r.Run(ctx, inv)
if err != nil {
return nil, false, err
}

lines := strings.Split(stdout.String(), "\n")
if len(lines) < 5 {
return nil, false, fmt.Errorf("unexpected stdout: %q", stdout.String())
}
mod := &ModuleJSON{
Path: lines[0],
Dir: lines[1],
GoMod: lines[2],
GoVersion: lines[3],
Main: true,
}
return mod, lines[4] == "go1.14", nil
}
Loading

0 comments on commit 6eec81c

Please sign in to comment.