github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/workcmd/sync.go (about)

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