From ccd992355df7192993c666236047820244914598 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Tue, 16 Apr 2024 21:19:13 +0200 Subject: Adding upstream version 1.21.8. Signed-off-by: Daniel Baumann --- src/cmd/go/internal/workcmd/edit.go | 340 ++++++++++++++++++++++++++++++++++++ src/cmd/go/internal/workcmd/init.go | 66 +++++++ src/cmd/go/internal/workcmd/sync.go | 146 ++++++++++++++++ src/cmd/go/internal/workcmd/use.go | 254 +++++++++++++++++++++++++++ src/cmd/go/internal/workcmd/work.go | 78 +++++++++ 5 files changed, 884 insertions(+) create mode 100644 src/cmd/go/internal/workcmd/edit.go create mode 100644 src/cmd/go/internal/workcmd/init.go create mode 100644 src/cmd/go/internal/workcmd/sync.go create mode 100644 src/cmd/go/internal/workcmd/use.go create mode 100644 src/cmd/go/internal/workcmd/work.go (limited to 'src/cmd/go/internal/workcmd') diff --git a/src/cmd/go/internal/workcmd/edit.go b/src/cmd/go/internal/workcmd/edit.go new file mode 100644 index 0000000..8d975b0 --- /dev/null +++ b/src/cmd/go/internal/workcmd/edit.go @@ -0,0 +1,340 @@ +// 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 edit + +package workcmd + +import ( + "cmd/go/internal/base" + "cmd/go/internal/gover" + "cmd/go/internal/modload" + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "golang.org/x/mod/module" + + "golang.org/x/mod/modfile" +) + +var cmdEdit = &base.Command{ + UsageLine: "go work edit [editing flags] [go.work]", + Short: "edit go.work from tools or scripts", + Long: `Edit provides a command-line interface for editing go.work, +for use primarily by tools or scripts. It only reads go.work; +it does not look up information about the modules involved. +If no file is specified, Edit looks for a go.work file in the current +directory and its parent directories + +The editing flags specify a sequence of editing operations. + +The -fmt flag reformats the go.work file without making other changes. +This reformatting is also implied by any other modifications that use or +rewrite the go.mod file. The only time this flag is needed is if no other +flags are specified, as in 'go work edit -fmt'. + +The -use=path and -dropuse=path flags +add and drop a use directive from the go.work file's set of module directories. + +The -replace=old[@v]=new[@v] flag adds a replacement of the given +module path and version pair. If the @v in old@v is omitted, a +replacement without a version on the left side is added, which applies +to all versions of the old module path. If the @v in new@v is omitted, +the new path should be a local module root directory, not a module +path. Note that -replace overrides any redundant replacements for old[@v], +so omitting @v will drop existing replacements for specific versions. + +The -dropreplace=old[@v] flag drops a replacement of the given +module path and version pair. If the @v is omitted, a replacement without +a version on the left side is dropped. + +The -use, -dropuse, -replace, and -dropreplace, +editing flags may be repeated, and the changes are applied in the order given. + +The -go=version flag sets the expected Go language version. + +The -toolchain=name flag sets the Go toolchain to use. + +The -print flag prints the final go.work in its text format instead of +writing it back to go.mod. + +The -json flag prints the final go.work file in JSON format instead of +writing it back to go.mod. The JSON output corresponds to these Go types: + + type GoWork struct { + Go string + Toolchain string + Use []Use + Replace []Replace + } + + type Use struct { + DiskPath string + ModulePath string + } + + type Replace struct { + Old Module + New Module + } + + type Module struct { + Path string + Version string + } + +See the workspaces reference at https://go.dev/ref/mod#workspaces +for more information. +`, +} + +var ( + editFmt = cmdEdit.Flag.Bool("fmt", false, "") + editGo = cmdEdit.Flag.String("go", "", "") + editToolchain = cmdEdit.Flag.String("toolchain", "", "") + editJSON = cmdEdit.Flag.Bool("json", false, "") + editPrint = cmdEdit.Flag.Bool("print", false, "") + workedits []func(file *modfile.WorkFile) // edits specified in flags +) + +type flagFunc func(string) + +func (f flagFunc) String() string { return "" } +func (f flagFunc) Set(s string) error { f(s); return nil } + +func init() { + cmdEdit.Run = runEditwork // break init cycle + + cmdEdit.Flag.Var(flagFunc(flagEditworkUse), "use", "") + cmdEdit.Flag.Var(flagFunc(flagEditworkDropUse), "dropuse", "") + cmdEdit.Flag.Var(flagFunc(flagEditworkReplace), "replace", "") + cmdEdit.Flag.Var(flagFunc(flagEditworkDropReplace), "dropreplace", "") + base.AddChdirFlag(&cmdEdit.Flag) +} + +func runEditwork(ctx context.Context, cmd *base.Command, args []string) { + if *editJSON && *editPrint { + base.Fatalf("go: cannot use both -json and -print") + } + + if len(args) > 1 { + base.Fatalf("go: 'go help work edit' accepts at most one argument") + } + var gowork string + if len(args) == 1 { + gowork = args[0] + } else { + modload.InitWorkfile() + gowork = modload.WorkFilePath() + } + if gowork == "" { + base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)") + } + + if *editGo != "" && *editGo != "none" { + if !modfile.GoVersionRE.MatchString(*editGo) { + base.Fatalf(`go work: invalid -go option; expecting something like "-go %s"`, gover.Local()) + } + } + if *editToolchain != "" && *editToolchain != "none" { + if !modfile.ToolchainRE.MatchString(*editToolchain) { + base.Fatalf(`go work: invalid -toolchain option; expecting something like "-toolchain go%s"`, gover.Local()) + } + } + + anyFlags := *editGo != "" || + *editToolchain != "" || + *editJSON || + *editPrint || + *editFmt || + len(workedits) > 0 + + if !anyFlags { + base.Fatalf("go: no flags specified (see 'go help work edit').") + } + + workFile, err := modload.ReadWorkFile(gowork) + if err != nil { + base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gowork), err) + } + + if *editGo == "none" { + workFile.DropGoStmt() + } else if *editGo != "" { + if err := workFile.AddGoStmt(*editGo); err != nil { + base.Fatalf("go: internal error: %v", err) + } + } + if *editToolchain == "none" { + workFile.DropToolchainStmt() + } else if *editToolchain != "" { + if err := workFile.AddToolchainStmt(*editToolchain); err != nil { + base.Fatalf("go: internal error: %v", err) + } + } + + if len(workedits) > 0 { + for _, edit := range workedits { + edit(workFile) + } + } + + workFile.SortBlocks() + workFile.Cleanup() // clean file after edits + + // Note: No call to modload.UpdateWorkFile here. + // Edit's job is only to make the edits on the command line, + // not to apply the kinds of semantic changes that + // UpdateWorkFile does (or would eventually do, if we + // decide to add the module comments in go.work). + + if *editJSON { + editPrintJSON(workFile) + return + } + + if *editPrint { + os.Stdout.Write(modfile.Format(workFile.Syntax)) + return + } + + modload.WriteWorkFile(gowork, workFile) +} + +// flagEditworkUse implements the -use flag. +func flagEditworkUse(arg string) { + workedits = append(workedits, func(f *modfile.WorkFile) { + _, mf, err := modload.ReadModFile(filepath.Join(arg, "go.mod"), nil) + modulePath := "" + if err == nil { + modulePath = mf.Module.Mod.Path + } + f.AddUse(modload.ToDirectoryPath(arg), modulePath) + if err := f.AddUse(modload.ToDirectoryPath(arg), ""); err != nil { + base.Fatalf("go: -use=%s: %v", arg, err) + } + }) +} + +// flagEditworkDropUse implements the -dropuse flag. +func flagEditworkDropUse(arg string) { + workedits = append(workedits, func(f *modfile.WorkFile) { + if err := f.DropUse(modload.ToDirectoryPath(arg)); err != nil { + base.Fatalf("go: -dropdirectory=%s: %v", arg, err) + } + }) +} + +// allowedVersionArg returns whether a token may be used as a version in go.mod. +// We don't call modfile.CheckPathVersion, because that insists on versions +// being in semver form, but here we want to allow versions like "master" or +// "1234abcdef", which the go command will resolve the next time it runs (or +// during -fix). Even so, we need to make sure the version is a valid token. +func allowedVersionArg(arg string) bool { + return !modfile.MustQuote(arg) +} + +// parsePathVersionOptional parses path[@version], using adj to +// describe any errors. +func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) { + before, after, found := strings.Cut(arg, "@") + if !found { + path = arg + } else { + path, version = strings.TrimSpace(before), strings.TrimSpace(after) + } + if err := module.CheckImportPath(path); err != nil { + if !allowDirPath || !modfile.IsDirectoryPath(path) { + return path, version, fmt.Errorf("invalid %s path: %v", adj, err) + } + } + if path != arg && !allowedVersionArg(version) { + return path, version, fmt.Errorf("invalid %s version: %q", adj, version) + } + return path, version, nil +} + +// flagEditworkReplace implements the -replace flag. +func flagEditworkReplace(arg string) { + before, after, found := strings.Cut(arg, "=") + if !found { + base.Fatalf("go: -replace=%s: need old[@v]=new[@w] (missing =)", arg) + } + old, new := strings.TrimSpace(before), strings.TrimSpace(after) + if strings.HasPrefix(new, ">") { + base.Fatalf("go: -replace=%s: separator between old and new is =, not =>", arg) + } + oldPath, oldVersion, err := parsePathVersionOptional("old", old, false) + if err != nil { + base.Fatalf("go: -replace=%s: %v", arg, err) + } + newPath, newVersion, err := parsePathVersionOptional("new", new, true) + if err != nil { + base.Fatalf("go: -replace=%s: %v", arg, err) + } + if newPath == new && !modfile.IsDirectoryPath(new) { + base.Fatalf("go: -replace=%s: unversioned new path must be local directory", arg) + } + + workedits = append(workedits, func(f *modfile.WorkFile) { + if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil { + base.Fatalf("go: -replace=%s: %v", arg, err) + } + }) +} + +// flagEditworkDropReplace implements the -dropreplace flag. +func flagEditworkDropReplace(arg string) { + path, version, err := parsePathVersionOptional("old", arg, true) + if err != nil { + base.Fatalf("go: -dropreplace=%s: %v", arg, err) + } + workedits = append(workedits, func(f *modfile.WorkFile) { + if err := f.DropReplace(path, version); err != nil { + base.Fatalf("go: -dropreplace=%s: %v", arg, err) + } + }) +} + +type replaceJSON struct { + Old module.Version + New module.Version +} + +// editPrintJSON prints the -json output. +func editPrintJSON(workFile *modfile.WorkFile) { + var f workfileJSON + if workFile.Go != nil { + f.Go = workFile.Go.Version + } + for _, d := range workFile.Use { + f.Use = append(f.Use, useJSON{DiskPath: d.Path, ModPath: d.ModulePath}) + } + + for _, r := range workFile.Replace { + f.Replace = append(f.Replace, replaceJSON{r.Old, r.New}) + } + data, err := json.MarshalIndent(&f, "", "\t") + if err != nil { + base.Fatalf("go: internal error: %v", err) + } + data = append(data, '\n') + os.Stdout.Write(data) +} + +// workfileJSON is the -json output data structure. +type workfileJSON struct { + Go string `json:",omitempty"` + Use []useJSON + Replace []replaceJSON +} + +type useJSON struct { + DiskPath string + ModPath string `json:",omitempty"` +} diff --git a/src/cmd/go/internal/workcmd/init.go b/src/cmd/go/internal/workcmd/init.go new file mode 100644 index 0000000..02240b8 --- /dev/null +++ b/src/cmd/go/internal/workcmd/init.go @@ -0,0 +1,66 @@ +// 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 init + +package workcmd + +import ( + "context" + "path/filepath" + + "cmd/go/internal/base" + "cmd/go/internal/fsys" + "cmd/go/internal/gover" + "cmd/go/internal/modload" + + "golang.org/x/mod/modfile" +) + +var cmdInit = &base.Command{ + UsageLine: "go work init [moddirs]", + Short: "initialize workspace file", + Long: `Init initializes and writes a new go.work file in the +current directory, in effect creating a new workspace at the current +directory. + +go work init optionally accepts paths to the workspace modules as +arguments. If the argument is omitted, an empty workspace with no +modules will be created. + +Each argument path is added to a use directive in the go.work file. The +current go version will also be listed in the go.work file. + +See the workspaces reference at https://go.dev/ref/mod#workspaces +for more information. +`, + Run: runInit, +} + +func init() { + base.AddChdirFlag(&cmdInit.Flag) + base.AddModCommonFlags(&cmdInit.Flag) +} + +func runInit(ctx context.Context, cmd *base.Command, args []string) { + modload.InitWorkfile() + + modload.ForceUseModules = true + + gowork := modload.WorkFilePath() + if gowork == "" { + gowork = filepath.Join(base.Cwd(), "go.work") + } + + if _, err := fsys.Stat(gowork); err == nil { + base.Fatalf("go: %s already exists", gowork) + } + + goV := gover.Local() // Use current Go version by default + wf := new(modfile.WorkFile) + wf.Syntax = new(modfile.FileSyntax) + wf.AddGoStmt(goV) + workUse(ctx, gowork, wf, args) + modload.WriteWorkFile(gowork, wf) +} diff --git a/src/cmd/go/internal/workcmd/sync.go b/src/cmd/go/internal/workcmd/sync.go new file mode 100644 index 0000000..719cf76 --- /dev/null +++ b/src/cmd/go/internal/workcmd/sync.go @@ -0,0 +1,146 @@ +// 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/gover" + "cmd/go/internal/imports" + "cmd/go/internal/modload" + "cmd/go/internal/toolchain" + "context" + + "golang.org/x/mod/module" +) + +var cmdSync = &base.Command{ + UsageLine: "go work sync", + Short: "sync workspace build list to modules", + Long: `Sync syncs the workspace's build list back to the +workspace's modules + +The workspace's build list is the set of versions of all the +(transitive) dependency modules used to do builds in the workspace. go +work sync generates that build list using the Minimal Version Selection +algorithm, and then syncs those versions back to each of modules +specified in the workspace (with use directives). + +The syncing is done by sequentially upgrading each of the dependency +modules specified in a workspace module to the version in the build list +if the dependency module's version is not already the same as the build +list's version. Note that Minimal Version Selection guarantees that the +build list's version of each module is always the same or higher than +that in each workspace module. + +See the workspaces reference at https://go.dev/ref/mod#workspaces +for more information. +`, + Run: runSync, +} + +func init() { + base.AddChdirFlag(&cmdSync.Flag) + base.AddModCommonFlags(&cmdSync.Flag) +} + +func runSync(ctx context.Context, cmd *base.Command, args []string) { + modload.ForceUseModules = true + modload.InitWorkfile() + if modload.WorkFilePath() == "" { + base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)") + } + + _, err := modload.LoadModGraph(ctx, "") + if err != nil { + toolchain.SwitchOrFatal(ctx, err) + } + 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 + } + } + gover.ModSort(mustSelect) // ensure determinism + mustSelectFor[m] = mustSelect + } + + workFilePath := modload.WorkFilePath() // save go.work path because EnterModule clobbers it. + + var goV string + for _, m := range mms.Versions() { + if mms.ModRoot(m) == "" && m.Path == "command-line-arguments" { + // This is not a real module. + // TODO(#49228): Remove this special case once the special + // command-line-arguments module is gone. + continue + } + + // 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. + // TODO(#57001): Do we need a toolchain.SwitchOrFatal here, + // and do we need to pass a toolchain.Switcher in LoadPackages? + // If so, think about saving the WriteGoMods for after the loop, + // so we don't write some go.mods with the "before" toolchain + // and others with the "after" toolchain. If nothing else, that + // discrepancy could show up in auto-recorded toolchain lines. + changed, err := modload.EditBuildList(ctx, nil, mustSelectFor[m]) + if err != nil { + continue + } + if changed { + modload.LoadPackages(ctx, modload.PackageOpts{ + Tags: imports.AnyTags(), + Tidy: true, + VendorModulesInGOROOTSrc: true, + ResolveMissingImports: false, + LoadTests: true, + AllowErrors: true, + SilenceMissingStdImports: true, + SilencePackageErrors: true, + }, "all") + modload.WriteGoMod(ctx, modload.WriteOpts{}) + } + goV = gover.Max(goV, modload.MainModules.GoVersion()) + } + + wf, err := modload.ReadWorkFile(workFilePath) + if err != nil { + base.Fatal(err) + } + modload.UpdateWorkGoVersion(wf, goV) + modload.UpdateWorkFile(wf) + if err := modload.WriteWorkFile(workFilePath, wf); err != nil { + base.Fatal(err) + } +} diff --git a/src/cmd/go/internal/workcmd/use.go b/src/cmd/go/internal/workcmd/use.go new file mode 100644 index 0000000..5547711 --- /dev/null +++ b/src/cmd/go/internal/workcmd/use.go @@ -0,0 +1,254 @@ +// 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 use + +package workcmd + +import ( + "context" + "fmt" + "io/fs" + "os" + "path/filepath" + + "cmd/go/internal/base" + "cmd/go/internal/fsys" + "cmd/go/internal/gover" + "cmd/go/internal/modload" + "cmd/go/internal/str" + "cmd/go/internal/toolchain" + + "golang.org/x/mod/modfile" +) + +var cmdUse = &base.Command{ + UsageLine: "go work use [-r] [moddirs]", + Short: "add modules to workspace file", + Long: `Use provides a command-line interface for adding +directories, optionally recursively, to a go.work file. + +A use directive will be added to the go.work file for each argument +directory listed on the command line go.work file, if it exists, +or removed from the go.work file if it does not exist. +Use fails if any remaining use directives refer to modules that +do not exist. + +Use updates the go line in go.work to specify a version at least as +new as all the go lines in the used modules, both preexisting ones +and newly added ones. With no arguments, this update is the only +thing that go work use does. + +The -r flag searches recursively for modules in the argument +directories, and the use command operates as if each of the directories +were specified as arguments: namely, use directives will be added for +directories that exist, and removed for directories that do not exist. + + + +See the workspaces reference at https://go.dev/ref/mod#workspaces +for more information. +`, +} + +var useR = cmdUse.Flag.Bool("r", false, "") + +func init() { + cmdUse.Run = runUse // break init cycle + + base.AddChdirFlag(&cmdUse.Flag) + base.AddModCommonFlags(&cmdUse.Flag) +} + +func runUse(ctx context.Context, cmd *base.Command, args []string) { + modload.ForceUseModules = true + modload.InitWorkfile() + gowork := modload.WorkFilePath() + if gowork == "" { + base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)") + } + wf, err := modload.ReadWorkFile(gowork) + if err != nil { + base.Fatal(err) + } + workUse(ctx, gowork, wf, args) + modload.WriteWorkFile(gowork, wf) +} + +func workUse(ctx context.Context, gowork string, wf *modfile.WorkFile, args []string) { + workDir := filepath.Dir(gowork) // absolute, since gowork itself is absolute + + haveDirs := make(map[string][]string) // absolute → original(s) + for _, use := range wf.Use { + var abs string + if filepath.IsAbs(use.Path) { + abs = filepath.Clean(use.Path) + } else { + abs = filepath.Join(workDir, use.Path) + } + haveDirs[abs] = append(haveDirs[abs], use.Path) + } + + // keepDirs maps each absolute path to keep to the literal string to use for + // that path (either an absolute or a relative path), or the empty string if + // all entries for the absolute path should be removed. + keepDirs := make(map[string]string) + + var sw toolchain.Switcher + + // lookDir updates the entry in keepDirs for the directory dir, + // which is either absolute or relative to the current working directory + // (not necessarily the directory containing the workfile). + lookDir := func(dir string) { + absDir, dir := pathRel(workDir, dir) + + file := base.ShortPath(filepath.Join(absDir, "go.mod")) + fi, err := fsys.Stat(file) + if err != nil { + if os.IsNotExist(err) { + keepDirs[absDir] = "" + } else { + sw.Error(err) + } + return + } + + if !fi.Mode().IsRegular() { + sw.Error(fmt.Errorf("%v is not a regular file", file)) + return + } + + if dup := keepDirs[absDir]; dup != "" && dup != dir { + base.Errorf(`go: already added "%s" as "%s"`, dir, dup) + } + keepDirs[absDir] = dir + } + + for _, useDir := range args { + absArg, _ := pathRel(workDir, useDir) + + info, err := fsys.Stat(base.ShortPath(absArg)) + if err != nil { + // Errors raised from os.Stat are formatted to be more user-friendly. + if os.IsNotExist(err) { + err = fmt.Errorf("directory %v does not exist", base.ShortPath(absArg)) + } + sw.Error(err) + continue + } else if !info.IsDir() { + sw.Error(fmt.Errorf("%s is not a directory", base.ShortPath(absArg))) + continue + } + + if !*useR { + lookDir(useDir) + continue + } + + // Add or remove entries for any subdirectories that still exist. + // If the root itself is a symlink to a directory, + // we want to follow it (see https://go.dev/issue/50807). + // Add a trailing separator to force that to happen. + fsys.Walk(str.WithFilePathSeparator(useDir), func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + + if !info.IsDir() { + if info.Mode()&fs.ModeSymlink != 0 { + if target, err := fsys.Stat(path); err == nil && target.IsDir() { + fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", base.ShortPath(path)) + } + } + return nil + } + lookDir(path) + return nil + }) + + // Remove entries for subdirectories that no longer exist. + // Because they don't exist, they will be skipped by Walk. + for absDir := range haveDirs { + if str.HasFilePathPrefix(absDir, absArg) { + if _, ok := keepDirs[absDir]; !ok { + keepDirs[absDir] = "" // Mark for deletion. + } + } + } + } + + // Update the work file. + for absDir, keepDir := range keepDirs { + nKept := 0 + for _, dir := range haveDirs[absDir] { + if dir == keepDir { // (note that dir is always non-empty) + nKept++ + } else { + wf.DropUse(dir) + } + } + if keepDir != "" && nKept != 1 { + // If we kept more than one copy, delete them all. + // We'll recreate a unique copy with AddUse. + if nKept > 1 { + wf.DropUse(keepDir) + } + wf.AddUse(keepDir, "") + } + } + + // Read the Go versions from all the use entries, old and new (but not dropped). + goV := gover.FromGoWork(wf) + for _, use := range wf.Use { + if use.Path == "" { // deleted + continue + } + var abs string + if filepath.IsAbs(use.Path) { + abs = filepath.Clean(use.Path) + } else { + abs = filepath.Join(workDir, use.Path) + } + _, mf, err := modload.ReadModFile(base.ShortPath(filepath.Join(abs, "go.mod")), nil) + if err != nil { + sw.Error(err) + continue + } + goV = gover.Max(goV, gover.FromGoMod(mf)) + } + sw.Switch(ctx) + base.ExitIfErrors() + + modload.UpdateWorkGoVersion(wf, goV) + modload.UpdateWorkFile(wf) +} + +// pathRel returns the absolute and canonical forms of dir for use in a +// go.work file located in directory workDir. +// +// If dir is relative, it is interpreted relative to base.Cwd() +// and its canonical form is relative to workDir if possible. +// If dir is absolute or cannot be made relative to workDir, +// its canonical form is absolute. +// +// Canonical absolute paths are clean. +// Canonical relative paths are clean and slash-separated. +func pathRel(workDir, dir string) (abs, canonical string) { + if filepath.IsAbs(dir) { + abs = filepath.Clean(dir) + return abs, abs + } + + abs = filepath.Join(base.Cwd(), dir) + rel, err := filepath.Rel(workDir, abs) + if err != nil { + // The path can't be made relative to the go.work file, + // so it must be kept absolute instead. + return abs, abs + } + + // Normalize relative paths to use slashes, so that checked-in go.work + // files with relative paths within the repo are platform-independent. + return abs, modload.ToDirectoryPath(rel) +} diff --git a/src/cmd/go/internal/workcmd/work.go b/src/cmd/go/internal/workcmd/work.go new file mode 100644 index 0000000..c99cc2a --- /dev/null +++ b/src/cmd/go/internal/workcmd/work.go @@ -0,0 +1,78 @@ +// 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. + +// Package workcmd implements the “go work” command. +package workcmd + +import ( + "cmd/go/internal/base" +) + +var CmdWork = &base.Command{ + UsageLine: "go work", + Short: "workspace maintenance", + Long: `Work provides access to operations on workspaces. + +Note that support for workspaces is built into many other commands, not +just 'go work'. + +See 'go help modules' for information about Go's module system of which +workspaces are a part. + +See https://go.dev/ref/mod#workspaces for an in-depth reference on +workspaces. + +See https://go.dev/doc/tutorial/workspaces for an introductory +tutorial on workspaces. + +A workspace is specified by a go.work file that specifies a set of +module directories with the "use" directive. These modules are used as +root modules by the go command for builds and related operations. A +workspace that does not specify modules to be used cannot be used to do +builds from local modules. + +go.work files are line-oriented. Each line holds a single directive, +made up of a keyword followed by arguments. For example: + + go 1.18 + + use ../foo/bar + use ./baz + + replace example.com/foo v1.2.3 => example.com/bar v1.4.5 + +The leading keyword can be factored out of adjacent lines to create a block, +like in Go imports. + + use ( + ../foo/bar + ./baz + ) + +The use directive specifies a module to be included in the workspace's +set of main modules. The argument to the use directive is the directory +containing the module's go.mod file. + +The go directive specifies the version of Go the file was written at. It +is possible there may be future changes in the semantics of workspaces +that could be controlled by this version, but for now the version +specified has no effect. + +The replace directive has the same syntax as the replace directive in a +go.mod file and takes precedence over replaces in go.mod files. It is +primarily intended to override conflicting replaces in different workspace +modules. + +To determine whether the go command is operating in workspace mode, use +the "go env GOWORK" command. This will specify the workspace file being +used. +`, + + Commands: []*base.Command{ + cmdEdit, + cmdInit, + cmdSync, + cmdUse, + }, +} -- cgit v1.2.3