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