1
0
mirror of https://github.com/golang/go synced 2024-11-17 09:54:46 -07:00

cmd/go: add go work sync command

Change-Id: I09b22f05035700e1ed90bd066ee8f77c3913286a
Reviewed-on: https://go-review.googlesource.com/c/go/+/358540
Trust: Michael Matloob <matloob@golang.org>
Run-TryBot: Michael Matloob <matloob@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
This commit is contained in:
Michael Matloob 2021-10-25 16:19:11 -04:00
parent 1c86beeadf
commit 795cb333d9
9 changed files with 490 additions and 12 deletions

View File

@ -1382,6 +1382,7 @@
//
// edit edit go.work from tools or scripts
// init initialize workspace file
// sync sync workspace build list to modules
//
// Use "go help work <command>" for more information about a command.
//
@ -1473,6 +1474,15 @@
// more information.
//
//
// Sync workspace build list to modules
//
// Usage:
//
// go work sync [moddirs]
//
// go work sync
//
//
// Compile and run Go program
//
// Usage:

View File

@ -614,12 +614,13 @@ func updateRoots(ctx context.Context, direct map[string]bool, rs *Requirements,
func updateWorkspaceRoots(ctx context.Context, rs *Requirements, add []module.Version) (*Requirements, error) {
if len(add) != 0 {
// add should be empty in workspace mode because a non-empty add slice means
// that there are missing roots in the current pruning mode or that the
// pruning mode is being changed. But the pruning mode should always be
// 'workspace' in workspace mode and the set of roots in workspace mode is
// always complete because it's the set of workspace modules, which can't
// be edited by loading.
// add should be empty in workspace mode because workspace mode implies
// -mod=readonly, which in turn implies no new requirements. The code path
// that would result in add being non-empty returns an error before it
// reaches this point: The set of modules to add comes from
// resolveMissingImports, which in turn resolves each package by calling
// queryImport. But queryImport explicitly checks for -mod=readonly, and
// return an error.
panic("add is not empty")
}
return rs, nil

View File

@ -73,6 +73,16 @@ var (
gopath string
)
// EnterModule resets MainModules and requirements to refer to just this one module.
func EnterModule(ctx context.Context, enterModroot string) {
MainModules = nil // reset MainModules
requirements = nil
workFilePath = "" // Force module mode
modRoots = []string{enterModroot}
LoadModFile(ctx)
}
// Variable set in InitWorkfile
var (
// Set to the path to the go.work file, or "" if workspace mode is disabled.
@ -1040,7 +1050,7 @@ func setDefaultBuildMod() {
// to modload functions instead of relying on an implicit setting
// based on command name.
switch cfg.CmdName {
case "get", "mod download", "mod init", "mod tidy":
case "get", "mod download", "mod init", "mod tidy", "work sync":
// These commands are intended to update go.mod and go.sum.
cfg.BuildMod = "mod"
return

View File

@ -231,6 +231,9 @@ type PackageOpts struct {
// SilenceUnmatchedWarnings suppresses the warnings normally emitted for
// patterns that did not match any packages.
SilenceUnmatchedWarnings bool
// Resolve the query against this module.
MainModule module.Version
}
// LoadPackages identifies the set of packages matching the given patterns and
@ -256,7 +259,11 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
case m.IsLocal():
// Evaluate list of file system directories on first iteration.
if m.Dirs == nil {
matchLocalDirs(ctx, m, rs)
matchModRoots := modRoots
if opts.MainModule != (module.Version{}) {
matchModRoots = []string{MainModules.ModRoot(opts.MainModule)}
}
matchLocalDirs(ctx, matchModRoots, m, rs)
}
// Make a copy of the directory list and translate to import paths.
@ -309,7 +316,11 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
// The initial roots are the packages in the main module.
// loadFromRoots will expand that to "all".
m.Errs = m.Errs[:0]
matchPackages(ctx, m, opts.Tags, omitStd, MainModules.Versions())
matchModules := MainModules.Versions()
if opts.MainModule != (module.Version{}) {
matchModules = []module.Version{opts.MainModule}
}
matchPackages(ctx, m, opts.Tags, omitStd, matchModules)
} else {
// Starting with the packages in the main module,
// enumerate the full list of "all".
@ -441,7 +452,7 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
// matchLocalDirs is like m.MatchDirs, but tries to avoid scanning directories
// outside of the standard library and active modules.
func matchLocalDirs(ctx context.Context, m *search.Match, rs *Requirements) {
func matchLocalDirs(ctx context.Context, modRoots []string, m *search.Match, rs *Requirements) {
if !m.IsLocal() {
panic(fmt.Sprintf("internal error: resolveLocalDirs on non-local pattern %s", m.Pattern()))
}
@ -460,8 +471,8 @@ func matchLocalDirs(ctx context.Context, m *search.Match, rs *Requirements) {
modRoot := findModuleRoot(absDir)
found := false
for _, mod := range MainModules.Versions() {
if MainModules.ModRoot(mod) == modRoot {
for _, mainModuleRoot := range modRoots {
if mainModuleRoot == modRoot {
found = true
break
}

View File

@ -0,0 +1,101 @@
// Copyright 2021 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.
// go work sync
package workcmd
import (
"cmd/go/internal/base"
"cmd/go/internal/imports"
"cmd/go/internal/modload"
"context"
"golang.org/x/mod/module"
)
var _ = modload.TODOWorkspaces("Add more documentation below. Though this is" +
"enough for those trying workspaces out, there should be more through" +
"documentation if the proposal is accepted and released.")
var cmdSync = &base.Command{
UsageLine: "go work sync [moddirs]",
Short: "sync workspace build list to modules",
Long: `go work sync`,
Run: runSync,
}
func init() {
base.AddModCommonFlags(&cmdSync.Flag)
base.AddWorkfileFlag(&cmdSync.Flag)
}
func runSync(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
modload.ForceUseModules = true
workGraph := modload.LoadModGraph(ctx, "")
_ = workGraph
mustSelectFor := map[module.Version][]module.Version{}
mms := modload.MainModules
opts := modload.PackageOpts{
Tags: imports.AnyTags(),
VendorModulesInGOROOTSrc: true,
ResolveMissingImports: false,
LoadTests: true,
AllowErrors: true,
SilencePackageErrors: true,
SilenceUnmatchedWarnings: true,
}
for _, m := range mms.Versions() {
opts.MainModule = m
_, pkgs := modload.LoadPackages(ctx, opts, "all")
opts.MainModule = module.Version{} // reset
var (
mustSelect []module.Version
inMustSelect = map[module.Version]bool{}
)
for _, pkg := range pkgs {
if r := modload.PackageModule(pkg); r.Version != "" && !inMustSelect[r] {
// r has a known version, so force that version.
mustSelect = append(mustSelect, r)
inMustSelect[r] = true
}
}
module.Sort(mustSelect) // ensure determinism
mustSelectFor[m] = mustSelect
}
for _, m := range mms.Versions() {
// Use EnterModule to reset the global state in modload to be in
// single-module mode using the modroot of m.
modload.EnterModule(ctx, mms.ModRoot(m))
// Edit the build list in the same way that 'go get' would if we
// requested the relevant module versions explicitly.
changed, err := modload.EditBuildList(ctx, nil, mustSelectFor[m])
if err != nil {
base.Errorf("go: %v", err)
}
if !changed {
continue
}
modload.LoadPackages(ctx, modload.PackageOpts{
Tags: imports.AnyTags(),
VendorModulesInGOROOTSrc: true,
ResolveMissingImports: false,
LoadTests: true,
AllowErrors: true,
SilencePackageErrors: true,
Tidy: true,
SilenceUnmatchedWarnings: true,
}, "all")
modload.WriteGoMod(ctx)
}
}

View File

@ -24,5 +24,6 @@ which workspaces are a part.
Commands: []*base.Command{
cmdEdit,
cmdInit,
cmdSync,
},
}

119
src/cmd/go/testdata/script/work_sync.txt vendored Normal file
View File

@ -0,0 +1,119 @@
go work sync
cmp a/go.mod a/want_go.mod
cmp b/go.mod b/want_go.mod
-- go.work --
go 1.18
directory (
./a
./b
)
-- a/go.mod --
go 1.18
module example.com/a
require (
example.com/p v1.0.0
example.com/q v1.1.0
example.com/r v1.0.0
)
replace (
example.com/p => ../p
example.com/q => ../q
example.com/r => ../r
)
-- a/want_go.mod --
go 1.18
module example.com/a
require (
example.com/p v1.1.0
example.com/q v1.1.0
)
replace (
example.com/p => ../p
example.com/q => ../q
example.com/r => ../r
)
-- a/a.go --
package a
import (
"example.com/p"
"example.com/q"
)
func Foo() {
p.P()
q.Q()
}
-- b/go.mod --
go 1.18
module example.com/b
require (
example.com/p v1.1.0
example.com/q v1.0.0
)
replace (
example.com/p => ../p
example.com/q => ../q
)
-- b/want_go.mod --
go 1.18
module example.com/b
require (
example.com/p v1.1.0
example.com/q v1.1.0
)
replace (
example.com/p => ../p
example.com/q => ../q
)
-- b/b.go --
package b
import (
"example.com/p"
"example.com/q"
)
func Foo() {
p.P()
q.Q()
}
-- p/go.mod --
go 1.18
module example.com/p
-- p/p.go --
package p
func P() {}
-- q/go.mod --
go 1.18
module example.com/q
-- q/q.go --
package q
func Q() {}
-- r/go.mod --
go 1.18
module example.com/r
-- r/q.go --
package r
func R() {}

View File

@ -0,0 +1,119 @@
# Test of go work sync in a workspace in which some dependency needed by `a`
# appears at a lower version in the build list of `b`, but is not needed at all
# by `b` (so it should not be upgraded within b).
#
# a -> p 1.1
# b -> q 1.0 -(through a test dependency)-> p 1.0
go work sync
cmp a/go.mod a/want_go.mod
cmp b/go.mod b/want_go.mod
-- go.work --
go 1.18
directory (
./a
./b
)
-- a/go.mod --
go 1.18
module example.com/a
require (
example.com/p v1.1.0
)
replace (
example.com/p => ../p
)
-- a/want_go.mod --
go 1.18
module example.com/a
require (
example.com/p v1.1.0
)
replace (
example.com/p => ../p
)
-- a/a.go --
package a
import (
"example.com/p"
)
func Foo() {
p.P()
}
-- b/go.mod --
go 1.18
module example.com/b
require (
example.com/q v1.0.0
)
replace (
example.com/q => ../q
)
-- b/want_go.mod --
go 1.18
module example.com/b
require (
example.com/q v1.0.0
)
replace (
example.com/q => ../q
)
-- b/b.go --
package b
import (
"example.com/q"
)
func Foo() {
q.Q()
}
-- p/go.mod --
go 1.18
module example.com/p
-- p/p.go --
package p
func P() {}
-- q/go.mod --
go 1.18
module example.com/q
require (
example.com/p v1.0.0
)
replace (
example.com/p => ../p
)
-- q/q.go --
package q
func Q() {
}
-- q/q_test.go --
package q
import example.com/p
func TestQ(t *testing.T) {
p.P()
}

View File

@ -0,0 +1,106 @@
# Test of go work sync in a workspace in which some dependency in the build
# list of 'b' (but not otherwise needed by `b`, so not seen when lazy loading
# occurs) actually is relevant to `a`.
#
# a -> p 1.0
# b -> q 1.1 -> p 1.1
go work sync
cmp a/go.mod a/want_go.mod
cmp b/go.mod b/want_go.mod
-- go.work --
go 1.18
directory (
./a
./b
)
-- a/go.mod --
go 1.18
module example.com/a
require (
example.com/p v1.0.0
)
replace (
example.com/p => ../p
)
-- a/want_go.mod --
go 1.18
module example.com/a
require example.com/p v1.1.0
replace example.com/p => ../p
-- a/a.go --
package a
import (
"example.com/p"
)
func Foo() {
p.P()
}
-- b/go.mod --
go 1.18
module example.com/b
require (
example.com/q v1.1.0
)
replace (
example.com/q => ../q
)
-- b/want_go.mod --
go 1.18
module example.com/b
require (
example.com/q v1.1.0
)
replace (
example.com/q => ../q
)
-- b/b.go --
package b
import (
"example.com/q"
)
func Foo() {
q.Q()
}
-- p/go.mod --
go 1.18
module example.com/p
-- p/p.go --
package p
func P() {}
-- q/go.mod --
go 1.18
module example.com/q
require example.com/p v1.1.0
replace example.com/p => ../p
-- q/q.go --
package q
import example.com/p
func Q() {
p.P()
}