1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
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)
}
|