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.
46
47
48
49 See the workspaces reference at https://go.dev/ref/mod#workspaces
50 for more information.
51 `,
52 }
53
54 var useR = cmdUse.Flag.Bool("r", false, "")
55
56 func init() {
57 cmdUse.Run = runUse
58
59 base.AddChdirFlag(&cmdUse.Flag)
60 base.AddModCommonFlags(&cmdUse.Flag)
61 }
62
63 func runUse(ctx context.Context, cmd *base.Command, args []string) {
64 modload.ForceUseModules = true
65 modload.InitWorkfile()
66 gowork := modload.WorkFilePath()
67 if gowork == "" {
68 base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
69 }
70 wf, err := modload.ReadWorkFile(gowork)
71 if err != nil {
72 base.Fatal(err)
73 }
74 workUse(ctx, gowork, wf, args)
75 modload.WriteWorkFile(gowork, wf)
76 }
77
78 func workUse(ctx context.Context, gowork string, wf *modfile.WorkFile, args []string) {
79 workDir := filepath.Dir(gowork)
80
81 haveDirs := make(map[string][]string)
82 for _, use := range wf.Use {
83 var abs string
84 if filepath.IsAbs(use.Path) {
85 abs = filepath.Clean(use.Path)
86 } else {
87 abs = filepath.Join(workDir, use.Path)
88 }
89 haveDirs[abs] = append(haveDirs[abs], use.Path)
90 }
91
92
93
94
95 keepDirs := make(map[string]string)
96
97 var sw toolchain.Switcher
98
99
100
101
102 lookDir := func(dir string) {
103 absDir, dir := pathRel(workDir, dir)
104
105 file := base.ShortPath(filepath.Join(absDir, "go.mod"))
106 fi, err := fsys.Stat(file)
107 if err != nil {
108 if os.IsNotExist(err) {
109 keepDirs[absDir] = ""
110 } else {
111 sw.Error(err)
112 }
113 return
114 }
115
116 if !fi.Mode().IsRegular() {
117 sw.Error(fmt.Errorf("%v is not a regular file", file))
118 return
119 }
120
121 if dup := keepDirs[absDir]; dup != "" && dup != dir {
122 base.Errorf(`go: already added "%s" as "%s"`, dir, dup)
123 }
124 keepDirs[absDir] = dir
125 }
126
127 for _, useDir := range args {
128 absArg, _ := pathRel(workDir, useDir)
129
130 info, err := fsys.Stat(base.ShortPath(absArg))
131 if err != nil {
132
133 if os.IsNotExist(err) {
134 err = fmt.Errorf("directory %v does not exist", base.ShortPath(absArg))
135 }
136 sw.Error(err)
137 continue
138 } else if !info.IsDir() {
139 sw.Error(fmt.Errorf("%s is not a directory", base.ShortPath(absArg)))
140 continue
141 }
142
143 if !*useR {
144 lookDir(useDir)
145 continue
146 }
147
148
149
150
151
152 fsys.Walk(str.WithFilePathSeparator(useDir), func(path string, info fs.FileInfo, err error) error {
153 if err != nil {
154 return err
155 }
156
157 if !info.IsDir() {
158 if info.Mode()&fs.ModeSymlink != 0 {
159 if target, err := fsys.Stat(path); err == nil && target.IsDir() {
160 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", base.ShortPath(path))
161 }
162 }
163 return nil
164 }
165 lookDir(path)
166 return nil
167 })
168
169
170
171 for absDir := range haveDirs {
172 if str.HasFilePathPrefix(absDir, absArg) {
173 if _, ok := keepDirs[absDir]; !ok {
174 keepDirs[absDir] = ""
175 }
176 }
177 }
178 }
179
180
181 for absDir, keepDir := range keepDirs {
182 nKept := 0
183 for _, dir := range haveDirs[absDir] {
184 if dir == keepDir {
185 nKept++
186 } else {
187 wf.DropUse(dir)
188 }
189 }
190 if keepDir != "" && nKept != 1 {
191
192
193 if nKept > 1 {
194 wf.DropUse(keepDir)
195 }
196 wf.AddUse(keepDir, "")
197 }
198 }
199
200
201 goV := gover.FromGoWork(wf)
202 for _, use := range wf.Use {
203 if use.Path == "" {
204 continue
205 }
206 var abs string
207 if filepath.IsAbs(use.Path) {
208 abs = filepath.Clean(use.Path)
209 } else {
210 abs = filepath.Join(workDir, use.Path)
211 }
212 _, mf, err := modload.ReadModFile(base.ShortPath(filepath.Join(abs, "go.mod")), nil)
213 if err != nil {
214 sw.Error(err)
215 continue
216 }
217 goV = gover.Max(goV, gover.FromGoMod(mf))
218 }
219 sw.Switch(ctx)
220 base.ExitIfErrors()
221
222 modload.UpdateWorkGoVersion(wf, goV)
223 modload.UpdateWorkFile(wf)
224 }
225
226
227
228
229
230
231
232
233
234
235
236 func pathRel(workDir, dir string) (abs, canonical string) {
237 if filepath.IsAbs(dir) {
238 abs = filepath.Clean(dir)
239 return abs, abs
240 }
241
242 abs = filepath.Join(base.Cwd(), dir)
243 rel, err := filepath.Rel(workDir, abs)
244 if err != nil {
245
246
247 return abs, abs
248 }
249
250
251
252 return abs, modload.ToDirectoryPath(rel)
253 }
254
View as plain text