Commit c357b363 authored by Jay Conrod's avatar Jay Conrod

cmd/go: add -modfile flag that sets go.mod file to read/write

This change adds the -modfile flag to module aware build commands and
to 'go mod' subcommands. -modfile may be set to a path to an alternate
go.mod file to be read and written. A real go.mod file must still
exist and is used to set the module root directory. However, it is not
opened.

When -modfile is set, the effective location of the go.sum file is
also changed to the -modfile with the ".mod" suffix trimmed (if
present) and ".sum" added.

Updates #34506

Change-Id: I2d1e044e18af55505a4f24bbff09b73bb9c908b4
Reviewed-on: https://go-review.googlesource.com/c/go/+/202564
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBryan C. Mills <bcmills@google.com>
parent c4c37547
...@@ -113,9 +113,9 @@ TODO ...@@ -113,9 +113,9 @@ TODO
</p> </p>
<p><!-- golang.org/issue/31481 --> <p><!-- golang.org/issue/31481 -->
The <code>go</code> command now accepts a new flag, <code>-modcacherw</code>, <code>-modcacherw</code> is a new flag that instructs the <code>go</code>
which leaves newly-created directories in the module cache at their default command to leave newly-created directories in the module cache at their
permissions rather than making them read-only. default permissions rather than making them read-only.
The use of this flag makes it more likely that tests or other tools will The use of this flag makes it more likely that tests or other tools will
accidentally add files not included in the module's verified checksum. accidentally add files not included in the module's verified checksum.
However, it allows the use of <code>rm</code> <code>-rf</code> However, it allows the use of <code>rm</code> <code>-rf</code>
...@@ -123,6 +123,16 @@ TODO ...@@ -123,6 +123,16 @@ TODO
to remove the module cache. to remove the module cache.
</p> </p>
<p><!-- golang.org/issue/34506 -->
<code>-modfile=file</code> is a new flag that instructs the <code>go</code>
command to read (and possibly write) an alternate go.mod file instead of the
one in the module root directory. A file named "go.mod" must still be present
in order to determine the module root directory, but it is not
accessed. When <code>-modfile</code> is specified, an alternate go.sum file
is also used: its path is derived from the <code>-modfile</code> flag by
trimming the ".mod" extension and appending ".sum".
</p>
<h2 id="runtime">Runtime</h2> <h2 id="runtime">Runtime</h2>
<p> <p>
......
...@@ -153,6 +153,13 @@ ...@@ -153,6 +153,13 @@
// -modcacherw // -modcacherw
// leave newly-created directories in the module cache read-write // leave newly-created directories in the module cache read-write
// instead of making them read-only. // instead of making them read-only.
// -modfile file
// in module aware mode, read (and possibly write) an alternate go.mod
// file instead of the one in the module root directory. A file named
// "go.mod" must still be present in order to determine the module root
// directory, but it is not accessed. When -modfile is specified, an
// alternate go.sum file is also used: its path is derived from the
// -modfile flag by trimming the ".mod" extension and appending ".sum".
// -pkgdir dir // -pkgdir dir
// install and load all packages from dir instead of the usual locations. // install and load all packages from dir instead of the usual locations.
// For example, when building with a non-standard configuration, // For example, when building with a non-standard configuration,
......
...@@ -44,7 +44,8 @@ var ( ...@@ -44,7 +44,8 @@ var (
BuildWork bool // -work flag BuildWork bool // -work flag
BuildX bool // -x flag BuildX bool // -x flag
ModCacheRW bool // -modcacherw flag ModCacheRW bool // -modcacherw flag
ModFile string // -modfile flag
CmdName string // "build", "install", "list", "mod tidy", etc. CmdName string // "build", "install", "list", "mod tidy", etc.
......
...@@ -12,7 +12,6 @@ import ( ...@@ -12,7 +12,6 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"strings" "strings"
"cmd/go/internal/base" "cmd/go/internal/base"
...@@ -159,7 +158,7 @@ func runEdit(cmd *base.Command, args []string) { ...@@ -159,7 +158,7 @@ func runEdit(cmd *base.Command, args []string) {
if len(args) == 1 { if len(args) == 1 {
gomod = args[0] gomod = args[0]
} else { } else {
gomod = filepath.Join(modload.ModRoot(), "go.mod") gomod = modload.ModFilePath()
} }
if *editModule != "" { if *editModule != "" {
......
...@@ -43,7 +43,8 @@ func runInit(cmd *base.Command, args []string) { ...@@ -43,7 +43,8 @@ func runInit(cmd *base.Command, args []string) {
if os.Getenv("GO111MODULE") == "off" { if os.Getenv("GO111MODULE") == "off" {
base.Fatalf("go mod init: modules disabled by GO111MODULE=off; see 'go help modules'") base.Fatalf("go mod init: modules disabled by GO111MODULE=off; see 'go help modules'")
} }
if _, err := os.Stat("go.mod"); err == nil { modFilePath := modload.ModFilePath()
if _, err := os.Stat(modFilePath); err == nil {
base.Fatalf("go mod init: go.mod already exists") base.Fatalf("go mod init: go.mod already exists")
} }
if strings.Contains(modload.CmdModModule, "@") { if strings.Contains(modload.CmdModModule, "@") {
......
...@@ -5,7 +5,10 @@ ...@@ -5,7 +5,10 @@
// Package modcmd implements the ``go mod'' command. // Package modcmd implements the ``go mod'' command.
package modcmd package modcmd
import "cmd/go/internal/base" import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
)
var CmdMod = &base.Command{ var CmdMod = &base.Command{
UsageLine: "go mod", UsageLine: "go mod",
...@@ -29,3 +32,7 @@ See 'go help modules' for an overview of module functionality. ...@@ -29,3 +32,7 @@ See 'go help modules' for an overview of module functionality.
cmdWhy, cmdWhy,
}, },
} }
func addModFlags(cmd *base.Command) {
cmd.Flag.StringVar(&cfg.ModFile, "modfile", "", "")
}
...@@ -91,6 +91,9 @@ func Init() { ...@@ -91,6 +91,9 @@ func Init() {
} }
initialized = true initialized = true
// Keep in sync with WillBeEnabled. We perform extra validation here, and
// there are lots of diagnostics and side effects, so we can't use
// WillBeEnabled directly.
env := cfg.Getenv("GO111MODULE") env := cfg.Getenv("GO111MODULE")
switch env { switch env {
default: default:
...@@ -137,6 +140,9 @@ func Init() { ...@@ -137,6 +140,9 @@ func Init() {
} else { } else {
modRoot = findModuleRoot(base.Cwd) modRoot = findModuleRoot(base.Cwd)
if modRoot == "" { if modRoot == "" {
if cfg.ModFile != "" {
base.Fatalf("go: cannot find main module, but -modfile was set.\n\t-modfile cannot be used to set the module root directory.")
}
if !mustUseModules { if !mustUseModules {
// GO111MODULE is 'auto', and we can't find a module root. // GO111MODULE is 'auto', and we can't find a module root.
// Stay in GOPATH mode. // Stay in GOPATH mode.
...@@ -152,6 +158,9 @@ func Init() { ...@@ -152,6 +158,9 @@ func Init() {
fmt.Fprintf(os.Stderr, "go: warning: ignoring go.mod in system temp root %v\n", os.TempDir()) fmt.Fprintf(os.Stderr, "go: warning: ignoring go.mod in system temp root %v\n", os.TempDir())
} }
} }
if cfg.ModFile != "" && !strings.HasSuffix(cfg.ModFile, ".mod") {
base.Fatalf("go: -modfile=%s: file does not have .mod extension", cfg.ModFile)
}
// We're in module mode. Install the hooks to make it work. // We're in module mode. Install the hooks to make it work.
...@@ -210,7 +219,7 @@ func Init() { ...@@ -210,7 +219,7 @@ func Init() {
// //
// See golang.org/issue/32027. // See golang.org/issue/32027.
} else { } else {
modfetch.GoSumFile = filepath.Join(modRoot, "go.sum") modfetch.GoSumFile = strings.TrimSuffix(ModFilePath(), ".mod") + ".sum"
search.SetModRoot(modRoot) search.SetModRoot(modRoot)
} }
} }
...@@ -226,6 +235,54 @@ func init() { ...@@ -226,6 +235,54 @@ func init() {
} }
} }
// WillBeEnabled checks whether modules should be enabled but does not
// initialize modules by installing hooks. If Init has already been called,
// WillBeEnabled returns the same result as Enabled.
//
// This function is needed to break a cycle. The main package needs to know
// whether modules are enabled in order to install the module or GOPATH version
// of 'go get', but Init reads the -modfile flag in 'go get', so it shouldn't
// be called until the command is installed and flags are parsed. Instead of
// calling Init and Enabled, the main package can call this function.
func WillBeEnabled() bool {
if modRoot != "" || mustUseModules {
return true
}
if initialized {
return false
}
// Keep in sync with Init. Init does extra validation and prints warnings or
// exits, so it can't call this function directly.
env := cfg.Getenv("GO111MODULE")
switch env {
case "on":
return true
case "auto", "":
break
default:
return false
}
if CmdModInit {
// Running 'go mod init': go.mod will be created in current directory.
return true
}
if modRoot := findModuleRoot(base.Cwd); modRoot == "" {
// GO111MODULE is 'auto', and we can't find a module root.
// Stay in GOPATH mode.
return false
} else if search.InDir(modRoot, os.TempDir()) == "." {
// If you create /tmp/go.mod for experimenting,
// then any tests that create work directories under /tmp
// will find it and get modules when they're not expecting them.
// It's a bit of a peculiar thing to disallow but quite mysterious
// when it happens. See golang.org/issue/26708.
return false
}
return true
}
// Enabled reports whether modules are (or must be) enabled. // Enabled reports whether modules are (or must be) enabled.
// If modules are enabled but there is no main module, Enabled returns true // If modules are enabled but there is no main module, Enabled returns true
// and then the first use of module information will call die // and then the first use of module information will call die
...@@ -252,6 +309,20 @@ func HasModRoot() bool { ...@@ -252,6 +309,20 @@ func HasModRoot() bool {
return modRoot != "" return modRoot != ""
} }
// ModFilePath returns the effective path of the go.mod file. Normally, this
// "go.mod" in the directory returned by ModRoot, but the -modfile flag may
// change its location. ModFilePath calls base.Fatalf if there is no main
// module, even if -modfile is set.
func ModFilePath() string {
if !HasModRoot() {
die()
}
if cfg.ModFile != "" {
return cfg.ModFile
}
return filepath.Join(modRoot, "go.mod")
}
// printStackInDie causes die to print a stack trace. // printStackInDie causes die to print a stack trace.
// //
// It is enabled by the testgo tag, and helps to diagnose paths that // It is enabled by the testgo tag, and helps to diagnose paths that
...@@ -305,7 +376,7 @@ func InitMod() { ...@@ -305,7 +376,7 @@ func InitMod() {
return return
} }
gomod := filepath.Join(modRoot, "go.mod") gomod := ModFilePath()
data, err := renameio.ReadFile(gomod) data, err := renameio.ReadFile(gomod)
if err != nil { if err != nil {
base.Fatalf("go: %v", err) base.Fatalf("go: %v", err)
...@@ -801,7 +872,7 @@ func WriteGoMod() { ...@@ -801,7 +872,7 @@ func WriteGoMod() {
unlock := modfetch.SideLock() unlock := modfetch.SideLock()
defer unlock() defer unlock()
file := filepath.Join(modRoot, "go.mod") file := ModFilePath()
old, err := renameio.ReadFile(file) old, err := renameio.ReadFile(file)
if !bytes.Equal(old, modFileData) { if !bytes.Equal(old, modFileData) {
if bytes.Equal(old, new) { if bytes.Equal(old, new) {
...@@ -819,7 +890,6 @@ func WriteGoMod() { ...@@ -819,7 +890,6 @@ func WriteGoMod() {
// want to run concurrent commands, they need to start with a complete, // want to run concurrent commands, they need to start with a complete,
// consistent module definition. // consistent module definition.
base.Fatalf("go: updates to go.mod needed, but contents have changed") base.Fatalf("go: updates to go.mod needed, but contents have changed")
} }
if err := renameio.WriteFile(file, new, 0666); err != nil { if err := renameio.WriteFile(file, new, 0666); err != nil {
......
...@@ -105,6 +105,13 @@ and test commands: ...@@ -105,6 +105,13 @@ and test commands:
-modcacherw -modcacherw
leave newly-created directories in the module cache read-write leave newly-created directories in the module cache read-write
instead of making them read-only. instead of making them read-only.
-modfile file
in module aware mode, read (and possibly write) an alternate go.mod
file instead of the one in the module root directory. A file named
"go.mod" must still be present in order to determine the module root
directory, but it is not accessed. When -modfile is specified, an
alternate go.sum file is also used: its path is derived from the
-modfile flag by trimming the ".mod" extension and appending ".sum".
-pkgdir dir -pkgdir dir
install and load all packages from dir instead of the usual locations. install and load all packages from dir instead of the usual locations.
For example, when building with a non-standard configuration, For example, when building with a non-standard configuration,
...@@ -266,6 +273,7 @@ func AddBuildFlags(cmd *base.Command, mask BuildFlagMask) { ...@@ -266,6 +273,7 @@ func AddBuildFlags(cmd *base.Command, mask BuildFlagMask) {
// and 'go mod' subcommands. // and 'go mod' subcommands.
func AddModCommonFlags(cmd *base.Command) { func AddModCommonFlags(cmd *base.Command) {
cmd.Flag.BoolVar(&cfg.ModCacheRW, "modcacherw", false, "") cmd.Flag.BoolVar(&cfg.ModCacheRW, "modcacherw", false, "")
cmd.Flag.StringVar(&cfg.ModFile, "modfile", "", "")
} }
// tagsFlag is the implementation of the -tags flag. // tagsFlag is the implementation of the -tags flag.
......
...@@ -258,6 +258,9 @@ func buildModeInit() { ...@@ -258,6 +258,9 @@ func buildModeInit() {
if cfg.ModCacheRW && !inGOFLAGS("-modcacherw") { if cfg.ModCacheRW && !inGOFLAGS("-modcacherw") {
base.Fatalf("build flag -modcacherw only valid when using modules") base.Fatalf("build flag -modcacherw only valid when using modules")
} }
if cfg.ModFile != "" && !inGOFLAGS("-mod") {
base.Fatalf("build flag -modfile only valid when using modules")
}
} }
} }
......
...@@ -91,7 +91,7 @@ func main() { ...@@ -91,7 +91,7 @@ func main() {
} }
if args[0] == "get" || args[0] == "help" { if args[0] == "get" || args[0] == "help" {
if modload.Init(); !modload.Enabled() { if !modload.WillBeEnabled() {
// Replace module-aware get with GOPATH get if appropriate. // Replace module-aware get with GOPATH get if appropriate.
*modget.CmdGet = *get.CmdGet *modget.CmdGet = *get.CmdGet
} }
......
# Tests the behavior of the -modfile flag in commands that support it.
# The go.mod file exists but should not be read or written.
# Same with go.sum.
env GOFLAGS=-modfile=go.alt.mod
cp go.mod go.mod.orig
cp go.sum go.sum.orig
# go mod init should create a new file, even though go.mod already exists.
go mod init example.com/m
grep example.com/m go.alt.mod
# go mod edit should operate on the alternate file
go mod edit -require rsc.io/quote@v1.5.2
grep rsc.io/quote go.alt.mod
# other 'go mod' commands should work. 'go mod vendor' is tested later.
go mod download rsc.io/quote
go mod graph
stdout rsc.io/quote
go mod tidy
grep rsc.io/quote go.alt.sum
go mod verify
go mod why rsc.io/quote
# 'go list' and other commands with build flags should work.
# They should update the alternate go.mod when a dependency is missing.
go mod edit -droprequire rsc.io/quote
go list .
grep rsc.io/quote go.alt.mod
go build -n .
go test -n .
go get -d rsc.io/quote
# 'go mod vendor' should work.
go mod vendor
exists vendor
# Automatic vendoring should be broken by editing an explicit requirement
# in the alternate go.mod file.
go mod edit -require rsc.io/quote@v1.5.1
! go list .
go list -mod=mod
# The original files should not have been modified.
cmp go.mod go.mod.orig
cmp go.sum go.sum.orig
# If the altnernate mod file does not have a ".mod" suffix, an error
# should be reported.
cp go.alt.mod goaltmod
! go mod tidy -modfile=goaltmod
stderr '-modfile=goaltmod: file does not have .mod extension'
-- go.mod --
ʕ◔ϖ◔ʔ
-- go.sum --
ʕ◔ϖ◔ʔ
-- use.go --
package use
import _ "rsc.io/quote"
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment