1
2
3
4
5 package modload
6
7 import (
8 "errors"
9 "fmt"
10 "io/fs"
11 "os"
12 "path/filepath"
13 "strings"
14 "sync"
15
16 "cmd/go/internal/base"
17 "cmd/go/internal/gover"
18
19 "golang.org/x/mod/modfile"
20 "golang.org/x/mod/module"
21 "golang.org/x/mod/semver"
22 )
23
24 var (
25 vendorOnce sync.Once
26 vendorList []module.Version
27 vendorReplaced []module.Version
28 vendorVersion map[string]string
29 vendorPkgModule map[string]module.Version
30 vendorMeta map[module.Version]vendorMetadata
31 )
32
33 type vendorMetadata struct {
34 Explicit bool
35 Replacement module.Version
36 GoVersion string
37 }
38
39
40 func readVendorList(vendorDir string) {
41 vendorOnce.Do(func() {
42 vendorList = nil
43 vendorPkgModule = make(map[string]module.Version)
44 vendorVersion = make(map[string]string)
45 vendorMeta = make(map[module.Version]vendorMetadata)
46 vendorFile := filepath.Join(vendorDir, "modules.txt")
47 data, err := os.ReadFile(vendorFile)
48 if err != nil {
49 if !errors.Is(err, fs.ErrNotExist) {
50 base.Fatalf("go: %s", err)
51 }
52 return
53 }
54
55 var mod module.Version
56 for _, line := range strings.Split(string(data), "\n") {
57 if strings.HasPrefix(line, "# ") {
58 f := strings.Fields(line)
59
60 if len(f) < 3 {
61 continue
62 }
63 if semver.IsValid(f[2]) {
64
65
66 mod = module.Version{Path: f[1], Version: f[2]}
67 f = f[3:]
68 } else if f[2] == "=>" {
69
70 mod = module.Version{Path: f[1]}
71 f = f[2:]
72 } else {
73
74
75 mod = module.Version{}
76 continue
77 }
78
79 if len(f) >= 2 && f[0] == "=>" {
80 meta := vendorMeta[mod]
81 if len(f) == 2 {
82
83 meta.Replacement = module.Version{Path: f[1]}
84 vendorReplaced = append(vendorReplaced, mod)
85 } else if len(f) == 3 && semver.IsValid(f[2]) {
86
87 meta.Replacement = module.Version{Path: f[1], Version: f[2]}
88 vendorReplaced = append(vendorReplaced, mod)
89 } else {
90
91 }
92 vendorMeta[mod] = meta
93 }
94 continue
95 }
96
97
98
99 if mod.Path == "" {
100 continue
101 }
102
103 if annotations, ok := strings.CutPrefix(line, "## "); ok {
104
105 meta := vendorMeta[mod]
106 for _, entry := range strings.Split(annotations, ";") {
107 entry = strings.TrimSpace(entry)
108 if entry == "explicit" {
109 meta.Explicit = true
110 }
111 if goVersion, ok := strings.CutPrefix(entry, "go "); ok {
112 meta.GoVersion = goVersion
113 rawGoVersion.Store(mod, meta.GoVersion)
114 if gover.Compare(goVersion, gover.Local()) > 0 {
115 base.Fatal(&gover.TooNewError{What: mod.Path + " in " + base.ShortPath(vendorFile), GoVersion: goVersion})
116 }
117 }
118
119 }
120 vendorMeta[mod] = meta
121 continue
122 }
123
124 if f := strings.Fields(line); len(f) == 1 && module.CheckImportPath(f[0]) == nil {
125
126 vendorPkgModule[f[0]] = mod
127
128
129
130
131 if v, ok := vendorVersion[mod.Path]; !ok || gover.ModCompare(mod.Path, v, mod.Version) < 0 {
132 vendorList = append(vendorList, mod)
133 vendorVersion[mod.Path] = mod.Version
134 }
135 }
136 }
137 })
138 }
139
140
141
142
143 func checkVendorConsistency(indexes []*modFileIndex, modFiles []*modfile.File, modRoots []string) {
144
145
146 readVendorList(VendorDir())
147
148 if len(modFiles) < 1 {
149
150
151
152
153 panic("checkVendorConsistency called with zero modfiles")
154 }
155
156 pre114 := false
157 if !inWorkspaceMode() {
158 if len(indexes) != 1 {
159 panic(fmt.Errorf("not in workspace mode but number of indexes is %v, not 1", len(indexes)))
160 }
161 index := indexes[0]
162 if gover.Compare(index.goVersion, "1.14") < 0 {
163
164
165
166 pre114 = true
167 }
168 }
169
170 vendErrors := new(strings.Builder)
171 vendErrorf := func(mod module.Version, format string, args ...any) {
172 detail := fmt.Sprintf(format, args...)
173 if mod.Version == "" {
174 fmt.Fprintf(vendErrors, "\n\t%s: %s", mod.Path, detail)
175 } else {
176 fmt.Fprintf(vendErrors, "\n\t%s@%s: %s", mod.Path, mod.Version, detail)
177 }
178 }
179
180
181
182 for _, modFile := range modFiles {
183 for _, r := range modFile.Require {
184 if !vendorMeta[r.Mod].Explicit {
185 if pre114 {
186
187
188
189
190 if vv, ok := vendorVersion[r.Mod.Path]; ok && vv != r.Mod.Version {
191 vendErrorf(r.Mod, fmt.Sprintf("is explicitly required in go.mod, but vendor/modules.txt indicates %s@%s", r.Mod.Path, vv))
192 }
193 } else {
194 vendErrorf(r.Mod, "is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt")
195 }
196 }
197 }
198 }
199
200 describe := func(m module.Version) string {
201 if m.Version == "" {
202 return m.Path
203 }
204 return m.Path + "@" + m.Version
205 }
206
207
208
209
210
211 seenrep := make(map[module.Version]bool)
212 checkReplace := func(replaces []*modfile.Replace) {
213 for _, r := range replaces {
214 if seenrep[r.Old] {
215 continue
216 }
217 seenrep[r.Old] = true
218 rNew, modRoot, replacementSource := replacementFrom(r.Old)
219 rNewCanonical := canonicalizeReplacePath(rNew, modRoot)
220 vr := vendorMeta[r.Old].Replacement
221 if vr == (module.Version{}) {
222 if rNewCanonical == (module.Version{}) {
223
224
225 } else if pre114 && (r.Old.Version == "" || vendorVersion[r.Old.Path] != r.Old.Version) {
226
227
228 } else {
229 vendErrorf(r.Old, "is replaced in %s, but not marked as replaced in vendor/modules.txt", base.ShortPath(replacementSource))
230 }
231 } else if vr != rNewCanonical {
232 vendErrorf(r.Old, "is replaced by %s in %s, but marked as replaced by %s in vendor/modules.txt", describe(rNew), base.ShortPath(replacementSource), describe(vr))
233 }
234 }
235 }
236 for _, modFile := range modFiles {
237 checkReplace(modFile.Replace)
238 }
239 if MainModules.workFile != nil {
240 checkReplace(MainModules.workFile.Replace)
241 }
242
243 for _, mod := range vendorList {
244 meta := vendorMeta[mod]
245 if meta.Explicit {
246
247 var foundRequire bool
248 for _, index := range indexes {
249 if _, inGoMod := index.require[mod]; inGoMod {
250 foundRequire = true
251 }
252 }
253 if !foundRequire {
254 article := ""
255 if inWorkspaceMode() {
256 article = "a "
257 }
258 vendErrorf(mod, "is marked as explicit in vendor/modules.txt, but not explicitly required in %vgo.mod", article)
259 }
260
261 }
262 }
263
264 for _, mod := range vendorReplaced {
265 r := Replacement(mod)
266 replacementSource := "go.mod"
267 if inWorkspaceMode() {
268 replacementSource = "the workspace"
269 }
270 if r == (module.Version{}) {
271 vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in %s", replacementSource)
272 continue
273 }
274
275 }
276
277 if vendErrors.Len() > 0 {
278 subcmd := "mod"
279 if inWorkspaceMode() {
280 subcmd = "work"
281 }
282 base.Fatalf("go: inconsistent vendoring in %s:%s\n\n\tTo ignore the vendor directory, use -mod=readonly or -mod=mod.\n\tTo sync the vendor directory, run:\n\t\tgo %s vendor", filepath.Dir(VendorDir()), vendErrors, subcmd)
283 }
284 }
285
View as plain text