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