1// Copyright 2021 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// go work sync
6
7package workcmd
8
9import (
10	"cmd/go/internal/base"
11	"cmd/go/internal/gover"
12	"cmd/go/internal/imports"
13	"cmd/go/internal/modload"
14	"cmd/go/internal/toolchain"
15	"context"
16
17	"golang.org/x/mod/module"
18)
19
20var cmdSync = &base.Command{
21	UsageLine: "go work sync",
22	Short:     "sync workspace build list to modules",
23	Long: `Sync syncs the workspace's build list back to the
24workspace's modules
25
26The workspace's build list is the set of versions of all the
27(transitive) dependency modules used to do builds in the workspace. go
28work sync generates that build list using the Minimal Version Selection
29algorithm, and then syncs those versions back to each of modules
30specified in the workspace (with use directives).
31
32The syncing is done by sequentially upgrading each of the dependency
33modules specified in a workspace module to the version in the build list
34if the dependency module's version is not already the same as the build
35list's version. Note that Minimal Version Selection guarantees that the
36build list's version of each module is always the same or higher than
37that in each workspace module.
38
39See the workspaces reference at https://go.dev/ref/mod#workspaces
40for more information.
41`,
42	Run: runSync,
43}
44
45func init() {
46	base.AddChdirFlag(&cmdSync.Flag)
47	base.AddModCommonFlags(&cmdSync.Flag)
48}
49
50func runSync(ctx context.Context, cmd *base.Command, args []string) {
51	modload.ForceUseModules = true
52	modload.InitWorkfile()
53	if modload.WorkFilePath() == "" {
54		base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
55	}
56
57	_, err := modload.LoadModGraph(ctx, "")
58	if err != nil {
59		toolchain.SwitchOrFatal(ctx, err)
60	}
61	mustSelectFor := map[module.Version][]module.Version{}
62
63	mms := modload.MainModules
64
65	opts := modload.PackageOpts{
66		Tags:                     imports.AnyTags(),
67		VendorModulesInGOROOTSrc: true,
68		ResolveMissingImports:    false,
69		LoadTests:                true,
70		AllowErrors:              true,
71		SilencePackageErrors:     true,
72		SilenceUnmatchedWarnings: true,
73	}
74	for _, m := range mms.Versions() {
75		opts.MainModule = m
76		_, pkgs := modload.LoadPackages(ctx, opts, "all")
77		opts.MainModule = module.Version{} // reset
78
79		var (
80			mustSelect   []module.Version
81			inMustSelect = map[module.Version]bool{}
82		)
83		for _, pkg := range pkgs {
84			if r := modload.PackageModule(pkg); r.Version != "" && !inMustSelect[r] {
85				// r has a known version, so force that version.
86				mustSelect = append(mustSelect, r)
87				inMustSelect[r] = true
88			}
89		}
90		gover.ModSort(mustSelect) // ensure determinism
91		mustSelectFor[m] = mustSelect
92	}
93
94	workFilePath := modload.WorkFilePath() // save go.work path because EnterModule clobbers it.
95
96	var goV string
97	for _, m := range mms.Versions() {
98		if mms.ModRoot(m) == "" && m.Path == "command-line-arguments" {
99			// This is not a real module.
100			// TODO(#49228): Remove this special case once the special
101			// command-line-arguments module is gone.
102			continue
103		}
104
105		// Use EnterModule to reset the global state in modload to be in
106		// single-module mode using the modroot of m.
107		modload.EnterModule(ctx, mms.ModRoot(m))
108
109		// Edit the build list in the same way that 'go get' would if we
110		// requested the relevant module versions explicitly.
111		// TODO(#57001): Do we need a toolchain.SwitchOrFatal here,
112		// and do we need to pass a toolchain.Switcher in LoadPackages?
113		// If so, think about saving the WriteGoMods for after the loop,
114		// so we don't write some go.mods with the "before" toolchain
115		// and others with the "after" toolchain. If nothing else, that
116		// discrepancy could show up in auto-recorded toolchain lines.
117		changed, err := modload.EditBuildList(ctx, nil, mustSelectFor[m])
118		if err != nil {
119			continue
120		}
121		if changed {
122			modload.LoadPackages(ctx, modload.PackageOpts{
123				Tags:                     imports.AnyTags(),
124				Tidy:                     true,
125				VendorModulesInGOROOTSrc: true,
126				ResolveMissingImports:    false,
127				LoadTests:                true,
128				AllowErrors:              true,
129				SilenceMissingStdImports: true,
130				SilencePackageErrors:     true,
131			}, "all")
132			modload.WriteGoMod(ctx, modload.WriteOpts{})
133		}
134		goV = gover.Max(goV, modload.MainModules.GoVersion())
135	}
136
137	wf, err := modload.ReadWorkFile(workFilePath)
138	if err != nil {
139		base.Fatal(err)
140	}
141	modload.UpdateWorkGoVersion(wf, goV)
142	modload.UpdateWorkFile(wf)
143	if err := modload.WriteWorkFile(workFilePath, wf); err != nil {
144		base.Fatal(err)
145	}
146}
147