@@ -10,7 +10,10 @@ import (
10
10
"cmd/go/internal/base"
11
11
"cmd/go/internal/fsys"
12
12
"cmd/go/internal/modload"
13
+ "cmd/go/internal/str"
13
14
"context"
15
+ "errors"
16
+ "fmt"
14
17
"io/fs"
15
18
"os"
16
19
"path/filepath"
@@ -56,44 +59,34 @@ func runUse(ctx context.Context, cmd *base.Command, args []string) {
56
59
if err != nil {
57
60
base .Fatalf ("go: %v" , err )
58
61
}
62
+ workDir := filepath .Dir (gowork ) // Absolute, since gowork itself is absolute.
59
63
60
64
haveDirs := make (map [string ][]string ) // absolute → original(s)
61
65
for _ , use := range workFile .Use {
62
- var absDir string
66
+ var abs string
63
67
if filepath .IsAbs (use .Path ) {
64
- absDir = filepath .Clean (use .Path )
68
+ abs = filepath .Clean (use .Path )
65
69
} else {
66
- absDir = filepath .Join (filepath . Dir ( gowork ) , use .Path )
70
+ abs = filepath .Join (workDir , use .Path )
67
71
}
68
- haveDirs [absDir ] = append (haveDirs [absDir ], use .Path )
72
+ haveDirs [abs ] = append (haveDirs [abs ], use .Path )
69
73
}
70
74
71
- addDirs := make (map [string ]bool )
72
- removeDirs := make (map [string ]bool )
75
+ // keepDirs maps each absolute path to keep to the literal string to use for
76
+ // that path (either an absolute or a relative path), or the empty string if
77
+ // all entries for the absolute path should be removed.
78
+ keepDirs := make (map [string ]string )
79
+
80
+ // lookDir updates the entry in keepDirs for the directory dir,
81
+ // which is either absolute or relative to the current working directory
82
+ // (not necessarily the directory containing the workfile).
73
83
lookDir := func (dir string ) {
74
- // If the path is absolute, try to keep it absolute. If it's relative,
75
- // make it relative to the go.work file rather than the working directory.
76
- absDir := dir
77
- if ! filepath .IsAbs (dir ) {
78
- absDir = filepath .Join (base .Cwd (), dir )
79
- rel , err := filepath .Rel (filepath .Dir (gowork ), absDir )
80
- if err == nil {
81
- // Normalize relative paths to use slashes, so that checked-in go.work
82
- // files with relative paths within the repo are platform-independent.
83
- dir = filepath .ToSlash (rel )
84
- } else {
85
- // The path can't be made relative to the go.work file,
86
- // so it must be kept absolute instead.
87
- dir = absDir
88
- }
89
- }
84
+ absDir , dir := pathRel (workDir , dir )
90
85
91
86
fi , err := os .Stat (filepath .Join (absDir , "go.mod" ))
92
87
if err != nil {
93
88
if os .IsNotExist (err ) {
94
- for _ , origDir := range haveDirs [absDir ] {
95
- removeDirs [origDir ] = true
96
- }
89
+ keepDirs [absDir ] = ""
97
90
return
98
91
}
99
92
base .Errorf ("go: %v" , err )
@@ -103,31 +96,96 @@ func runUse(ctx context.Context, cmd *base.Command, args []string) {
103
96
base .Errorf ("go: %v is not regular" , filepath .Join (dir , "go.mod" ))
104
97
}
105
98
106
- if len ( haveDirs [absDir ]) == 0 {
107
- addDirs [ dir ] = true
99
+ if dup := keepDirs [absDir ]; dup != "" && dup != dir {
100
+ base . Errorf ( `go: already added "%s" as "%s"` , dir , dup )
108
101
}
102
+ keepDirs [absDir ] = dir
109
103
}
110
104
111
105
for _ , useDir := range args {
112
- if * useR {
113
- fsys .Walk (useDir , func (path string , info fs.FileInfo , err error ) error {
114
- if ! info .IsDir () {
115
- return nil
106
+ if ! * useR {
107
+ lookDir (useDir )
108
+ continue
109
+ }
110
+
111
+ // Add or remove entries for any subdirectories that still exist.
112
+ err := fsys .Walk (useDir , func (path string , info fs.FileInfo , err error ) error {
113
+ if ! info .IsDir () {
114
+ if info .Mode ()& fs .ModeSymlink != 0 {
115
+ if target , err := fsys .Stat (path ); err == nil && target .IsDir () {
116
+ fmt .Fprintf (os .Stderr , "warning: ignoring symlink %s\n " , path )
117
+ }
116
118
}
117
- lookDir (path )
118
119
return nil
119
- })
120
- continue
120
+ }
121
+ lookDir (path )
122
+ return nil
123
+ })
124
+ if err != nil && ! errors .Is (err , os .ErrNotExist ) {
125
+ base .Errorf ("go: %v" , err )
121
126
}
122
- lookDir (useDir )
123
- }
124
127
125
- for dir := range removeDirs {
126
- workFile .DropUse (dir )
128
+ // Remove entries for subdirectories that no longer exist.
129
+ // Because they don't exist, they will be skipped by Walk.
130
+ absArg , _ := pathRel (workDir , useDir )
131
+ for absDir , _ := range haveDirs {
132
+ if str .HasFilePathPrefix (absDir , absArg ) {
133
+ if _ , ok := keepDirs [absDir ]; ! ok {
134
+ keepDirs [absDir ] = "" // Mark for deletion.
135
+ }
136
+ }
137
+ }
127
138
}
128
- for dir := range addDirs {
129
- workFile .AddUse (dir , "" )
139
+
140
+ base .ExitIfErrors ()
141
+
142
+ for absDir , keepDir := range keepDirs {
143
+ nKept := 0
144
+ for _ , dir := range haveDirs [absDir ] {
145
+ if dir == keepDir { // (note that dir is always non-empty)
146
+ nKept ++
147
+ } else {
148
+ workFile .DropUse (dir )
149
+ }
150
+ }
151
+ if keepDir != "" && nKept != 1 {
152
+ // If we kept more than one copy, delete them all.
153
+ // We'll recreate a unique copy with AddUse.
154
+ if nKept > 1 {
155
+ workFile .DropUse (keepDir )
156
+ }
157
+ workFile .AddUse (keepDir , "" )
158
+ }
130
159
}
131
160
modload .UpdateWorkFile (workFile )
132
161
modload .WriteWorkFile (gowork , workFile )
133
162
}
163
+
164
+ // pathRel returns the absolute and canonical forms of dir for use in a
165
+ // go.work file located in directory workDir.
166
+ //
167
+ // If dir is relative, it is intepreted relative to base.Cwd()
168
+ // and its canonical form is relative to workDir if possible.
169
+ // If dir is absolute or cannot be made relative to workDir,
170
+ // its canonical form is absolute.
171
+ //
172
+ // Canonical absolute paths are clean.
173
+ // Canonical relative paths are clean and slash-separated.
174
+ func pathRel (workDir , dir string ) (abs , canonical string ) {
175
+ if filepath .IsAbs (dir ) {
176
+ abs = filepath .Clean (dir )
177
+ return abs , abs
178
+ }
179
+
180
+ abs = filepath .Join (base .Cwd (), dir )
181
+ rel , err := filepath .Rel (workDir , abs )
182
+ if err != nil {
183
+ // The path can't be made relative to the go.work file,
184
+ // so it must be kept absolute instead.
185
+ return abs , abs
186
+ }
187
+
188
+ // Normalize relative paths to use slashes, so that checked-in go.work
189
+ // files with relative paths within the repo are platform-independent.
190
+ return abs , filepath .ToSlash (rel )
191
+ }
0 commit comments