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