1
2
3
4
5 package modload
6
7 import (
8 "context"
9 "errors"
10 "fmt"
11 "os"
12 "path/filepath"
13 "strings"
14 "sync"
15 "unicode"
16
17 "cmd/go/internal/base"
18 "cmd/go/internal/cfg"
19 "cmd/go/internal/fsys"
20 "cmd/go/internal/lockedfile"
21 "cmd/go/internal/modfetch"
22 "cmd/go/internal/par"
23 "cmd/go/internal/trace"
24
25 "golang.org/x/mod/modfile"
26 "golang.org/x/mod/module"
27 "golang.org/x/mod/semver"
28 )
29
30 const (
31
32
33
34 narrowAllVersionV = "v1.16"
35
36
37
38
39
40
41
42 ExplicitIndirectVersionV = "v1.17"
43
44
45
46
47 separateIndirectVersionV = "v1.17"
48 )
49
50
51
52 func ReadModFile(gomod string, fix modfile.VersionFixer) (data []byte, f *modfile.File, err error) {
53 if gomodActual, ok := fsys.OverlayPath(gomod); ok {
54
55
56
57 data, err = os.ReadFile(gomodActual)
58 } else {
59 data, err = lockedfile.Read(gomodActual)
60 }
61 if err != nil {
62 return nil, nil, err
63 }
64
65 f, err = modfile.Parse(gomod, data, fix)
66 if err != nil {
67
68 return nil, nil, fmt.Errorf("errors parsing go.mod:\n%s\n", err)
69 }
70 if f.Module == nil {
71
72 return nil, nil, errors.New("no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod")
73 }
74
75 return data, f, err
76 }
77
78
79
80 func modFileGoVersion(modFile *modfile.File) string {
81 if modFile == nil {
82 return LatestGoVersion()
83 }
84 if modFile.Go == nil || modFile.Go.Version == "" {
85
86
87
88
89
90
91
92
93
94 return "1.16"
95 }
96 return modFile.Go.Version
97 }
98
99
100
101 type modFileIndex struct {
102 data []byte
103 dataNeedsFix bool
104 module module.Version
105 goVersionV string
106 require map[module.Version]requireMeta
107 replace map[module.Version]module.Version
108 exclude map[module.Version]bool
109 }
110
111 type requireMeta struct {
112 indirect bool
113 }
114
115
116
117
118 type modPruning uint8
119
120 const (
121 pruned modPruning = iota
122 unpruned
123 workspace
124 )
125
126 func pruningForGoVersion(goVersion string) modPruning {
127 if semver.Compare("v"+goVersion, ExplicitIndirectVersionV) < 0 {
128
129
130 return unpruned
131 }
132 return pruned
133 }
134
135
136
137
138 func CheckAllowed(ctx context.Context, m module.Version) error {
139 if err := CheckExclusions(ctx, m); err != nil {
140 return err
141 }
142 if err := CheckRetractions(ctx, m); err != nil {
143 return err
144 }
145 return nil
146 }
147
148
149
150 var ErrDisallowed = errors.New("disallowed module version")
151
152
153
154 func CheckExclusions(ctx context.Context, m module.Version) error {
155 for _, mainModule := range MainModules.Versions() {
156 if index := MainModules.Index(mainModule); index != nil && index.exclude[m] {
157 return module.VersionError(m, errExcluded)
158 }
159 }
160 return nil
161 }
162
163 var errExcluded = &excludedError{}
164
165 type excludedError struct{}
166
167 func (e *excludedError) Error() string { return "excluded by go.mod" }
168 func (e *excludedError) Is(err error) bool { return err == ErrDisallowed }
169
170
171
172 func CheckRetractions(ctx context.Context, m module.Version) (err error) {
173 defer func() {
174 if retractErr := (*ModuleRetractedError)(nil); err == nil || errors.As(err, &retractErr) {
175 return
176 }
177
178
179 if mErr := (*module.ModuleError)(nil); errors.As(err, &mErr) {
180 err = mErr.Err
181 }
182 err = &retractionLoadingError{m: m, err: err}
183 }()
184
185 if m.Version == "" {
186
187
188 return nil
189 }
190 if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
191
192
193 return nil
194 }
195
196
197
198
199
200
201
202
203
204
205
206
207 rm, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
208 if err != nil {
209 return err
210 }
211 summary, err := rawGoModSummary(rm)
212 if err != nil {
213 return err
214 }
215
216 var rationale []string
217 isRetracted := false
218 for _, r := range summary.retract {
219 if semver.Compare(r.Low, m.Version) <= 0 && semver.Compare(m.Version, r.High) <= 0 {
220 isRetracted = true
221 if r.Rationale != "" {
222 rationale = append(rationale, r.Rationale)
223 }
224 }
225 }
226 if isRetracted {
227 return module.VersionError(m, &ModuleRetractedError{Rationale: rationale})
228 }
229 return nil
230 }
231
232 type ModuleRetractedError struct {
233 Rationale []string
234 }
235
236 func (e *ModuleRetractedError) Error() string {
237 msg := "retracted by module author"
238 if len(e.Rationale) > 0 {
239
240
241 msg += ": " + ShortMessage(e.Rationale[0], "retracted by module author")
242 }
243 return msg
244 }
245
246 func (e *ModuleRetractedError) Is(err error) bool {
247 return err == ErrDisallowed
248 }
249
250 type retractionLoadingError struct {
251 m module.Version
252 err error
253 }
254
255 func (e *retractionLoadingError) Error() string {
256 return fmt.Sprintf("loading module retractions for %v: %v", e.m, e.err)
257 }
258
259 func (e *retractionLoadingError) Unwrap() error {
260 return e.err
261 }
262
263
264
265
266
267
268
269 func ShortMessage(message, emptyDefault string) string {
270 const maxLen = 500
271 if i := strings.Index(message, "\n"); i >= 0 {
272 message = message[:i]
273 }
274 message = strings.TrimSpace(message)
275 if message == "" {
276 return emptyDefault
277 }
278 if len(message) > maxLen {
279 return "(message omitted: too long)"
280 }
281 for _, r := range message {
282 if !unicode.IsGraphic(r) && !unicode.IsSpace(r) {
283 return "(message omitted: contains non-printable characters)"
284 }
285 }
286
287 return message
288 }
289
290
291
292
293
294
295
296
297 func CheckDeprecation(ctx context.Context, m module.Version) (deprecation string, err error) {
298 defer func() {
299 if err != nil {
300 err = fmt.Errorf("loading deprecation for %s: %w", m.Path, err)
301 }
302 }()
303
304 if m.Version == "" {
305
306
307 return "", nil
308 }
309 if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
310
311
312 return "", nil
313 }
314
315 latest, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
316 if err != nil {
317 return "", err
318 }
319 summary, err := rawGoModSummary(latest)
320 if err != nil {
321 return "", err
322 }
323 return summary.deprecated, nil
324 }
325
326 func replacement(mod module.Version, replace map[module.Version]module.Version) (fromVersion string, to module.Version, ok bool) {
327 if r, ok := replace[mod]; ok {
328 return mod.Version, r, true
329 }
330 if r, ok := replace[module.Version{Path: mod.Path}]; ok {
331 return "", r, true
332 }
333 return "", module.Version{}, false
334 }
335
336
337
338
339 func Replacement(mod module.Version) module.Version {
340 foundFrom, found, foundModRoot := "", module.Version{}, ""
341 if MainModules == nil {
342 return module.Version{}
343 } else if MainModules.Contains(mod.Path) && mod.Version == "" {
344
345 return module.Version{}
346 }
347 if _, r, ok := replacement(mod, MainModules.WorkFileReplaceMap()); ok {
348 return r
349 }
350 for _, v := range MainModules.Versions() {
351 if index := MainModules.Index(v); index != nil {
352 if from, r, ok := replacement(mod, index.replace); ok {
353 modRoot := MainModules.ModRoot(v)
354 if foundModRoot != "" && foundFrom != from && found != r {
355 base.Errorf("conflicting replacements found for %v in workspace modules defined by %v and %v",
356 mod, modFilePath(foundModRoot), modFilePath(modRoot))
357 return canonicalizeReplacePath(found, foundModRoot)
358 }
359 found, foundModRoot = r, modRoot
360 }
361 }
362 }
363 return canonicalizeReplacePath(found, foundModRoot)
364 }
365
366 func replaceRelativeTo() string {
367 if workFilePath := WorkFilePath(); workFilePath != "" {
368 return filepath.Dir(workFilePath)
369 }
370 return MainModules.ModRoot(MainModules.mustGetSingleMainModule())
371 }
372
373
374
375
376 func canonicalizeReplacePath(r module.Version, modRoot string) module.Version {
377 if filepath.IsAbs(r.Path) || r.Version != "" {
378 return r
379 }
380 workFilePath := WorkFilePath()
381 if workFilePath == "" {
382 return r
383 }
384 abs := filepath.Join(modRoot, r.Path)
385 if rel, err := filepath.Rel(filepath.Dir(workFilePath), abs); err == nil {
386 return module.Version{Path: rel, Version: r.Version}
387 }
388
389
390 return module.Version{Path: abs, Version: r.Version}
391 }
392
393
394
395
396
397 func resolveReplacement(m module.Version) module.Version {
398 if r := Replacement(m); r.Path != "" {
399 return r
400 }
401 return m
402 }
403
404 func toReplaceMap(replacements []*modfile.Replace) map[module.Version]module.Version {
405 replaceMap := make(map[module.Version]module.Version, len(replacements))
406 for _, r := range replacements {
407 if prev, dup := replaceMap[r.Old]; dup && prev != r.New {
408 base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v", r.Old, prev, r.New)
409 }
410 replaceMap[r.Old] = r.New
411 }
412 return replaceMap
413 }
414
415
416
417
418 func indexModFile(data []byte, modFile *modfile.File, mod module.Version, needsFix bool) *modFileIndex {
419 i := new(modFileIndex)
420 i.data = data
421 i.dataNeedsFix = needsFix
422
423 i.module = module.Version{}
424 if modFile.Module != nil {
425 i.module = modFile.Module.Mod
426 }
427
428 i.goVersionV = ""
429 if modFile.Go == nil {
430 rawGoVersion.Store(mod, "")
431 } else {
432
433
434 i.goVersionV = "v" + modFile.Go.Version
435 rawGoVersion.Store(mod, modFile.Go.Version)
436 }
437
438 i.require = make(map[module.Version]requireMeta, len(modFile.Require))
439 for _, r := range modFile.Require {
440 i.require[r.Mod] = requireMeta{indirect: r.Indirect}
441 }
442
443 i.replace = toReplaceMap(modFile.Replace)
444
445 i.exclude = make(map[module.Version]bool, len(modFile.Exclude))
446 for _, x := range modFile.Exclude {
447 i.exclude[x.Mod] = true
448 }
449
450 return i
451 }
452
453
454
455
456
457 func (i *modFileIndex) modFileIsDirty(modFile *modfile.File) bool {
458 if i == nil {
459 return modFile != nil
460 }
461
462 if i.dataNeedsFix {
463 return true
464 }
465
466 if modFile.Module == nil {
467 if i.module != (module.Version{}) {
468 return true
469 }
470 } else if modFile.Module.Mod != i.module {
471 return true
472 }
473
474 if modFile.Go == nil {
475 if i.goVersionV != "" {
476 return true
477 }
478 } else if "v"+modFile.Go.Version != i.goVersionV {
479 if i.goVersionV == "" && cfg.BuildMod != "mod" {
480
481
482
483 } else {
484 return true
485 }
486 }
487
488 if len(modFile.Require) != len(i.require) ||
489 len(modFile.Replace) != len(i.replace) ||
490 len(modFile.Exclude) != len(i.exclude) {
491 return true
492 }
493
494 for _, r := range modFile.Require {
495 if meta, ok := i.require[r.Mod]; !ok {
496 return true
497 } else if r.Indirect != meta.indirect {
498 if cfg.BuildMod == "readonly" {
499
500
501
502
503 } else {
504 return true
505 }
506 }
507 }
508
509 for _, r := range modFile.Replace {
510 if r.New != i.replace[r.Old] {
511 return true
512 }
513 }
514
515 for _, x := range modFile.Exclude {
516 if !i.exclude[x.Mod] {
517 return true
518 }
519 }
520
521 return false
522 }
523
524
525
526
527
528 var rawGoVersion sync.Map
529
530
531
532
533 type modFileSummary struct {
534 module module.Version
535 goVersion string
536 pruning modPruning
537 require []module.Version
538 retract []retraction
539 deprecated string
540 }
541
542
543
544 type retraction struct {
545 modfile.VersionInterval
546 Rationale string
547 }
548
549
550
551
552
553
554
555
556
557
558
559
560 func goModSummary(m module.Version) (*modFileSummary, error) {
561 if m.Version == "" && !inWorkspaceMode() && MainModules.Contains(m.Path) {
562 panic("internal error: goModSummary called on a main module")
563 }
564
565 if cfg.BuildMod == "vendor" {
566 summary := &modFileSummary{
567 module: module.Version{Path: m.Path},
568 }
569 if vendorVersion[m.Path] != m.Version {
570
571
572 return summary, nil
573 }
574
575
576
577 readVendorList(MainModules.mustGetSingleMainModule())
578
579
580
581 summary.require = vendorList
582 return summary, nil
583 }
584
585 actual := resolveReplacement(m)
586 if HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode() && actual.Version != "" {
587 key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"}
588 if !modfetch.HaveSum(key) {
589 suggestion := fmt.Sprintf("; to add it:\n\tgo mod download %s", m.Path)
590 return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion})
591 }
592 }
593 summary, err := rawGoModSummary(actual)
594 if err != nil {
595 return nil, err
596 }
597
598 if actual.Version == "" {
599
600
601
602
603
604
605 } else {
606 if summary.module.Path == "" {
607 return nil, module.VersionError(actual, errors.New("parsing go.mod: missing module line"))
608 }
609
610
611
612
613
614
615
616
617 if mpath := summary.module.Path; mpath != m.Path && mpath != actual.Path {
618 return nil, module.VersionError(actual, fmt.Errorf(`parsing go.mod:
619 module declares its path as: %s
620 but was required as: %s`, mpath, m.Path))
621 }
622 }
623
624 for _, mainModule := range MainModules.Versions() {
625 if index := MainModules.Index(mainModule); index != nil && len(index.exclude) > 0 {
626
627
628
629 haveExcludedReqs := false
630 for _, r := range summary.require {
631 if index.exclude[r] {
632 haveExcludedReqs = true
633 break
634 }
635 }
636 if haveExcludedReqs {
637 s := new(modFileSummary)
638 *s = *summary
639 s.require = make([]module.Version, 0, len(summary.require))
640 for _, r := range summary.require {
641 if !index.exclude[r] {
642 s.require = append(s.require, r)
643 }
644 }
645 summary = s
646 }
647 }
648 }
649 return summary, nil
650 }
651
652
653
654
655
656
657
658 func rawGoModSummary(m module.Version) (*modFileSummary, error) {
659 if m.Path == "" && MainModules.Contains(m.Path) {
660 panic("internal error: rawGoModSummary called on the Target module")
661 }
662
663 type key struct {
664 m module.Version
665 }
666 type cached struct {
667 summary *modFileSummary
668 err error
669 }
670 c := rawGoModSummaryCache.Do(key{m}, func() any {
671 summary := new(modFileSummary)
672 name, data, err := rawGoModData(m)
673 if err != nil {
674 return cached{nil, err}
675 }
676 f, err := modfile.ParseLax(name, data, nil)
677 if err != nil {
678 return cached{nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(name), err))}
679 }
680 if f.Module != nil {
681 summary.module = f.Module.Mod
682 summary.deprecated = f.Module.Deprecated
683 }
684 if f.Go != nil && f.Go.Version != "" {
685 rawGoVersion.LoadOrStore(m, f.Go.Version)
686 summary.goVersion = f.Go.Version
687 summary.pruning = pruningForGoVersion(f.Go.Version)
688 } else {
689 summary.pruning = unpruned
690 }
691 if len(f.Require) > 0 {
692 summary.require = make([]module.Version, 0, len(f.Require))
693 for _, req := range f.Require {
694 summary.require = append(summary.require, req.Mod)
695 }
696 }
697 if len(f.Retract) > 0 {
698 summary.retract = make([]retraction, 0, len(f.Retract))
699 for _, ret := range f.Retract {
700 summary.retract = append(summary.retract, retraction{
701 VersionInterval: ret.VersionInterval,
702 Rationale: ret.Rationale,
703 })
704 }
705 }
706
707 return cached{summary, nil}
708 }).(cached)
709
710 return c.summary, c.err
711 }
712
713 var rawGoModSummaryCache par.Cache
714
715
716
717
718
719
720
721
722 func rawGoModData(m module.Version) (name string, data []byte, err error) {
723 if m.Version == "" {
724
725
726 dir := m.Path
727 if !filepath.IsAbs(dir) {
728 if inWorkspaceMode() && MainModules.Contains(m.Path) {
729 dir = MainModules.ModRoot(m)
730 } else {
731 dir = filepath.Join(replaceRelativeTo(), dir)
732 }
733 }
734 name = filepath.Join(dir, "go.mod")
735 if gomodActual, ok := fsys.OverlayPath(name); ok {
736
737
738
739 data, err = os.ReadFile(gomodActual)
740 } else {
741 data, err = lockedfile.Read(gomodActual)
742 }
743 if err != nil {
744 return "", nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(name), err))
745 }
746 } else {
747 if !semver.IsValid(m.Version) {
748
749 base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version)
750 }
751 name = "go.mod"
752 data, err = modfetch.GoMod(m.Path, m.Version)
753 }
754 return name, data, err
755 }
756
757
758
759
760
761
762
763
764
765
766
767 func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, err error) {
768 type entry struct {
769 latest module.Version
770 err error
771 }
772 e := latestVersionIgnoringRetractionsCache.Do(path, func() any {
773 ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path)
774 defer span.Done()
775
776 if repl := Replacement(module.Version{Path: path}); repl.Path != "" {
777
778
779 return &entry{latest: repl}
780 }
781
782
783
784 const ignoreSelected = ""
785 var allowAll AllowedFunc
786 rev, err := Query(ctx, path, "latest", ignoreSelected, allowAll)
787 if err != nil {
788 return &entry{err: err}
789 }
790 latest := module.Version{Path: path, Version: rev.Version}
791 if repl := resolveReplacement(latest); repl.Path != "" {
792 latest = repl
793 }
794 return &entry{latest: latest}
795 }).(*entry)
796 return e.latest, e.err
797 }
798
799 var latestVersionIgnoringRetractionsCache par.Cache
800
801
802
803
804 func ToDirectoryPath(path string) string {
805 if path == "." || modfile.IsDirectoryPath(path) {
806 return path
807 }
808
809
810 return "./" + filepath.ToSlash(filepath.Clean(path))
811 }
812
View as plain text