1
2
3
4
5
6
7 package workcmd
8
9 import (
10 "context"
11 "fmt"
12 "io/fs"
13 "os"
14 "path/filepath"
15
16 "cmd/go/internal/base"
17 "cmd/go/internal/fsys"
18 "cmd/go/internal/gover"
19 "cmd/go/internal/modload"
20 "cmd/go/internal/str"
21 "cmd/go/internal/toolchain"
22
23 "golang.org/x/mod/modfile"
24 )
25
26 var cmdUse = &base.Command{
27 UsageLine: "go work use [-r] [moddirs]",
28 Short: "add modules to workspace file",
29 Long: `Use provides a command-line interface for adding
30 directories, optionally recursively, to a go.work file.
31
32 A use directive will be added to the go.work file for each argument
33 directory listed on the command line go.work file, if it exists,
34 or removed from the go.work file if it does not exist.
35 Use fails if any remaining use directives refer to modules that
36 do not exist.
37
38 Use updates the go line in go.work to specify a version at least as
39 new as all the go lines in the used modules, both preexisting ones
40 and newly added ones. With no arguments, this update is the only
41 thing that go work use does.
42
43 The -r flag searches recursively for modules in the argument
44 directories, and the use command operates as if each of the directories
45 were specified as arguments. When -r is used, symlinks to directories
46 within the argument tree are ignored.
47
48 The go command matches use paths to module directories without resolving
49 symbolic links. A use directive that names a symlink to a directory is
50 not interchangeable with one that names the symlink's target.
51
52 See the workspaces reference at https://go.dev/ref/mod#workspaces
53 for more information.
54 `,
55 }
56
57 var useR = cmdUse.Flag.Bool("r", false, "")
58
59 func init() {
60 cmdUse.Run = runUse
61
62 base.AddChdirFlag(&cmdUse.Flag)
63 base.AddModCommonFlags(&cmdUse.Flag)
64 }
65
66 func runUse(ctx context.Context, cmd *base.Command, args []string) {
67 moduleLoaderState := modload.NewState()
68 moduleLoaderState.ForceUseModules = true
69 moduleLoaderState.InitWorkfile()
70 gowork := modload.WorkFilePath(moduleLoaderState)
71 if gowork == "" {
72 base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
73 }
74 wf, err := modload.ReadWorkFile(gowork)
75 if err != nil {
76 base.Fatal(err)
77 }
78 workUse(ctx, moduleLoaderState, gowork, wf, args)
79 modload.WriteWorkFile(gowork, wf)
80 }
81
82 func workUse(ctx context.Context, s *modload.State, gowork string, wf *modfile.WorkFile, args []string) {
83 workDir := filepath.Dir(gowork)
84
85 haveDirs := make(map[string][]string)
86 for _, use := range wf.Use {
87 var abs string
88 if filepath.IsAbs(use.Path) {
89 abs = filepath.Clean(use.Path)
90 } else {
91 abs = filepath.Join(workDir, use.Path)
92 }
93 haveDirs[abs] = append(haveDirs[abs], use.Path)
94 }
95
96
97
98
99 keepDirs := make(map[string]string)
100
101 sw := toolchain.NewSwitcher(s)
102
103
104
105
106 lookDir := func(dir string) {
107 absDir, dir := pathRel(workDir, dir)
108
109 file := filepath.Join(absDir, "go.mod")
110 fi, err := fsys.Stat(file)
111 if err != nil {
112 if os.IsNotExist(err) {
113 keepDirs[absDir] = ""
114 } else {
115 sw.Error(err)
116 }
117 return
118 }
119
120 if !fi.Mode().IsRegular() {
121 sw.Error(fmt.Errorf("%v is not a regular file", base.ShortPath(file)))
122 return
123 }
124
125 if dup := keepDirs[absDir]; dup != "" && dup != dir {
126 base.Errorf(`go: already added "%s" as "%s"`, dir, dup)
127 }
128 keepDirs[absDir] = dir
129 }
130
131 for _, useDir := range args {
132 absArg, _ := pathRel(workDir, useDir)
133
134 info, err := fsys.Stat(absArg)
135 if err != nil {
136
137 if os.IsNotExist(err) {
138 err = fmt.Errorf("directory %v does not exist", base.ShortPath(absArg))
139 }
140 sw.Error(err)
141 continue
142 } else if !info.IsDir() {
143 sw.Error(fmt.Errorf("%s is not a directory", base.ShortPath(absArg)))
144 continue
145 }
146
147 if !*useR {
148 lookDir(useDir)
149 continue
150 }
151
152
153
154
155
156 fsys.WalkDir(str.WithFilePathSeparator(useDir), func(path string, d fs.DirEntry, err error) error {
157 if err != nil {
158 return err
159 }
160
161 if !d.IsDir() {
162 if d.Type()&fs.ModeSymlink != 0 {
163 if target, err := fsys.Stat(path); err == nil && target.IsDir() {
164 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", base.ShortPath(path))
165 }
166 }
167 return nil
168 }
169 if d.Name() == "vendor" {
170 return filepath.SkipDir
171 }
172 lookDir(path)
173 return nil
174 })
175
176
177
178 for absDir := range haveDirs {
179 if str.HasFilePathPrefix(absDir, absArg) {
180 if _, ok := keepDirs[absDir]; !ok {
181 keepDirs[absDir] = ""
182 }
183 }
184 }
185 }
186
187
188 for absDir, keepDir := range keepDirs {
189 nKept := 0
190 for _, dir := range haveDirs[absDir] {
191 if dir == keepDir {
192 nKept++
193 } else {
194 wf.DropUse(dir)
195 }
196 }
197 if keepDir != "" && nKept != 1 {
198
199
200 if nKept > 1 {
201 wf.DropUse(keepDir)
202 }
203 wf.AddUse(keepDir, "")
204 }
205 }
206
207
208 goV := gover.FromGoWork(wf)
209 for _, use := range wf.Use {
210 if use.Path == "" {
211 continue
212 }
213 var abs string
214 if filepath.IsAbs(use.Path) {
215 abs = filepath.Clean(use.Path)
216 } else {
217 abs = filepath.Join(workDir, use.Path)
218 }
219 _, mf, err := modload.ReadModFile(filepath.Join(abs, "go.mod"), nil)
220 if err != nil {
221 sw.Error(err)
222 continue
223 }
224 goV = gover.Max(goV, gover.FromGoMod(mf))
225 }
226 sw.Switch(ctx)
227 base.ExitIfErrors()
228
229 modload.UpdateWorkGoVersion(wf, goV)
230 modload.UpdateWorkFile(wf)
231 }
232
233
234
235
236
237
238
239
240
241
242
243 func pathRel(workDir, dir string) (abs, canonical string) {
244 if filepath.IsAbs(dir) {
245 abs = filepath.Clean(dir)
246 return abs, abs
247 }
248
249 abs = filepath.Join(base.Cwd(), dir)
250 rel, err := filepath.Rel(workDir, abs)
251 if err != nil {
252
253
254 return abs, abs
255 }
256
257
258
259 return abs, modload.ToDirectoryPath(rel)
260 }
261
View as plain text