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