Source file src/cmd/go/internal/workcmd/sync.go

     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  
     7  package workcmd
     8  
     9  import (
    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  
    20  var 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
    24  workspace's modules
    25  
    26  The workspace's build list is the set of versions of all the
    27  (transitive) dependency modules used to do builds in the workspace. go
    28  work sync generates that build list using the Minimal Version Selection
    29  algorithm, and then syncs those versions back to each of modules
    30  specified in the workspace (with use directives).
    31  
    32  The syncing is done by sequentially upgrading each of the dependency
    33  modules specified in a workspace module to the version in the build list
    34  if the dependency module's version is not already the same as the build
    35  list's version. Note that Minimal Version Selection guarantees that the
    36  build list's version of each module is always the same or higher than
    37  that in each workspace module.
    38  
    39  See the workspaces reference at https://go.dev/ref/mod#workspaces
    40  for more information.
    41  `,
    42  	Run: runSync,
    43  }
    44  
    45  func init() {
    46  	base.AddChdirFlag(&cmdSync.Flag)
    47  	base.AddModCommonFlags(&cmdSync.Flag)
    48  }
    49  
    50  func 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  

View as plain text