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.ShortPathConservative(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 useDirShort := base.ShortPathConservative(absArg)
130
131 info, err := fsys.Stat(useDirShort)
132 if err != nil {
133
134 if os.IsNotExist(err) {
135 err = fmt.Errorf("directory %v does not exist", useDirShort)
136 }
137 sw.Error(err)
138 continue
139 } else if !info.IsDir() {
140 sw.Error(fmt.Errorf("%s is not a directory", useDirShort))
141 continue
142 }
143
144 if !*useR {
145 lookDir(useDir)
146 continue
147 }
148
149
150
151
152
153 fsys.Walk(str.WithFilePathSeparator(useDir), func(path string, info fs.FileInfo, err error) error {
154 if err != nil {
155 return err
156 }
157
158 if !info.IsDir() {
159 if info.Mode()&fs.ModeSymlink != 0 {
160 if target, err := fsys.Stat(path); err == nil && target.IsDir() {
161 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", base.ShortPathConservative(path))
162 }
163 }
164 return nil
165 }
166 lookDir(path)
167 return nil
168 })
169
170
171
172 for absDir := range haveDirs {
173 if str.HasFilePathPrefix(absDir, absArg) {
174 if _, ok := keepDirs[absDir]; !ok {
175 keepDirs[absDir] = ""
176 }
177 }
178 }
179 }
180
181
182 for absDir, keepDir := range keepDirs {
183 nKept := 0
184 for _, dir := range haveDirs[absDir] {
185 if dir == keepDir {
186 nKept++
187 } else {
188 wf.DropUse(dir)
189 }
190 }
191 if keepDir != "" && nKept != 1 {
192
193
194 if nKept > 1 {
195 wf.DropUse(keepDir)
196 }
197 wf.AddUse(keepDir, "")
198 }
199 }
200
201
202 goV := gover.FromGoWork(wf)
203 for _, use := range wf.Use {
204 if use.Path == "" {
205 continue
206 }
207 var abs string
208 if filepath.IsAbs(use.Path) {
209 abs = filepath.Clean(use.Path)
210 } else {
211 abs = filepath.Join(workDir, use.Path)
212 }
213 _, mf, err := modload.ReadModFile(base.ShortPathConservative(filepath.Join(abs, "go.mod")), nil)
214 if err != nil {
215 sw.Error(err)
216 continue
217 }
218 goV = gover.Max(goV, gover.FromGoMod(mf))
219 }
220 sw.Switch(ctx)
221 base.ExitIfErrors()
222
223 modload.UpdateWorkGoVersion(wf, goV)
224 modload.UpdateWorkFile(wf)
225 }
226
227
228
229
230
231
232
233
234
235
236
237 func pathRel(workDir, dir string) (abs, canonical string) {
238 if filepath.IsAbs(dir) {
239 abs = filepath.Clean(dir)
240 return abs, abs
241 }
242
243 abs = filepath.Join(base.Cwd(), dir)
244 rel, err := filepath.Rel(workDir, abs)
245 if err != nil {
246
247
248 return abs, abs
249 }
250
251
252
253 return abs, modload.ToDirectoryPath(rel)
254 }
255
View as plain text