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.
10 "cmd/go/internal/base"
11 "cmd/go/internal/fsys"
12 "cmd/go/internal/modload"
21 var cmdUse = &base.Command{
22 UsageLine: "go work use [-r] moddirs",
23 Short: "add modules to workspace file",
24 Long: `Use provides a command-line interface for adding
25 directories, optionally recursively, to a go.work file.
27 A use directive will be added to the go.work file for each argument
28 directory listed on the command line go.work file, if it exists on disk,
29 or removed from the go.work file if it does not exist on disk.
31 The -r flag searches recursively for modules in the argument
32 directories, and the use command operates as if each of the directories
33 were specified as arguments: namely, use directives will be added for
34 directories that exist, and removed for directories that do not exist.
36 See the workspaces reference at https://go.dev/ref/mod#workspaces
41 var useR = cmdUse.Flag.Bool("r", false, "")
44 cmdUse.Run = runUse // break init cycle
46 base.AddChdirFlag(&cmdUse.Flag)
47 base.AddModCommonFlags(&cmdUse.Flag)
50 func runUse(ctx context.Context, cmd *base.Command, args []string) {
51 modload.ForceUseModules = true
54 modload.InitWorkfile()
55 gowork = modload.WorkFilePath()
58 base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
60 workFile, err := modload.ReadWorkFile(gowork)
62 base.Fatalf("go: %v", err)
64 workDir := filepath.Dir(gowork) // Absolute, since gowork itself is absolute.
66 haveDirs := make(map[string][]string) // absolute → original(s)
67 for _, use := range workFile.Use {
69 if filepath.IsAbs(use.Path) {
70 abs = filepath.Clean(use.Path)
72 abs = filepath.Join(workDir, use.Path)
74 haveDirs[abs] = append(haveDirs[abs], use.Path)
77 // keepDirs maps each absolute path to keep to the literal string to use for
78 // that path (either an absolute or a relative path), or the empty string if
79 // all entries for the absolute path should be removed.
80 keepDirs := make(map[string]string)
82 // lookDir updates the entry in keepDirs for the directory dir,
83 // which is either absolute or relative to the current working directory
84 // (not necessarily the directory containing the workfile).
85 lookDir := func(dir string) {
86 absDir, dir := pathRel(workDir, dir)
88 fi, err := fsys.Stat(filepath.Join(absDir, "go.mod"))
90 if os.IsNotExist(err) {
93 base.Errorf("go: %v", err)
98 if !fi.Mode().IsRegular() {
99 base.Errorf("go: %v is not regular", filepath.Join(dir, "go.mod"))
102 if dup := keepDirs[absDir]; dup != "" && dup != dir {
103 base.Errorf(`go: already added "%s" as "%s"`, dir, dup)
105 keepDirs[absDir] = dir
109 base.Fatalf("go: 'go work use' requires one or more directory arguments")
111 for _, useDir := range args {
112 absArg, _ := pathRel(workDir, useDir)
114 info, err := fsys.Stat(absArg)
116 // Errors raised from os.Stat are formatted to be more user-friendly.
117 if os.IsNotExist(err) {
118 base.Errorf("go: directory %v does not exist", absArg)
120 base.Errorf("go: %v", err)
123 } else if !info.IsDir() {
124 base.Errorf("go: %s is not a directory", absArg)
133 // Add or remove entries for any subdirectories that still exist.
134 fsys.Walk(useDir, func(path string, info fs.FileInfo, err error) error {
140 if info.Mode()&fs.ModeSymlink != 0 {
141 if target, err := fsys.Stat(path); err == nil && target.IsDir() {
142 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
151 // Remove entries for subdirectories that no longer exist.
152 // Because they don't exist, they will be skipped by Walk.
153 for absDir := range haveDirs {
154 if str.HasFilePathPrefix(absDir, absArg) {
155 if _, ok := keepDirs[absDir]; !ok {
156 keepDirs[absDir] = "" // Mark for deletion.
164 for absDir, keepDir := range keepDirs {
166 for _, dir := range haveDirs[absDir] {
167 if dir == keepDir { // (note that dir is always non-empty)
170 workFile.DropUse(dir)
173 if keepDir != "" && nKept != 1 {
174 // If we kept more than one copy, delete them all.
175 // We'll recreate a unique copy with AddUse.
177 workFile.DropUse(keepDir)
179 workFile.AddUse(keepDir, "")
182 modload.UpdateWorkFile(workFile)
183 modload.WriteWorkFile(gowork, workFile)
186 // pathRel returns the absolute and canonical forms of dir for use in a
187 // go.work file located in directory workDir.
189 // If dir is relative, it is intepreted relative to base.Cwd()
190 // and its canonical form is relative to workDir if possible.
191 // If dir is absolute or cannot be made relative to workDir,
192 // its canonical form is absolute.
194 // Canonical absolute paths are clean.
195 // Canonical relative paths are clean and slash-separated.
196 func pathRel(workDir, dir string) (abs, canonical string) {
197 if filepath.IsAbs(dir) {
198 abs = filepath.Clean(dir)
202 abs = filepath.Join(base.Cwd(), dir)
203 rel, err := filepath.Rel(workDir, abs)
205 // The path can't be made relative to the go.work file,
206 // so it must be kept absolute instead.
210 // Normalize relative paths to use slashes, so that checked-in go.work
211 // files with relative paths within the repo are platform-independent.
212 return abs, modload.ToDirectoryPath(rel)