1
2
3
4
5
6
7
8 package modindex
9
10 import (
11 "bytes"
12 "cmd/go/internal/fsys"
13 "cmd/go/internal/str"
14 "errors"
15 "fmt"
16 "go/ast"
17 "go/build"
18 "go/build/constraint"
19 "go/token"
20 "internal/syslist"
21 "io"
22 "io/fs"
23 "path/filepath"
24 "slices"
25 "sort"
26 "strings"
27 "unicode"
28 "unicode/utf8"
29 )
30
31
32 type Context struct {
33 GOARCH string
34 GOOS string
35 GOROOT string
36 GOPATH string
37
38
39
40
41
42
43
44 Dir string
45
46 CgoEnabled bool
47 UseAllFiles bool
48 Compiler string
49
50
51
52
53
54
55
56
57
58
59
60 BuildTags []string
61 ToolTags []string
62 ReleaseTags []string
63
64
65
66
67
68
69
70 InstallSuffix string
71
72
73
74
75
76
77
78
79
80 JoinPath func(elem ...string) string
81
82
83
84 SplitPathList func(list string) []string
85
86
87
88 IsAbsPath func(path string) bool
89
90
91
92 IsDir func(path string) bool
93
94
95
96
97
98
99
100
101 HasSubdir func(root, dir string) (rel string, ok bool)
102
103
104
105
106 ReadDir func(dir string) ([]fs.FileInfo, error)
107
108
109
110 OpenFile func(path string) (io.ReadCloser, error)
111 }
112
113
114 func (ctxt *Context) joinPath(elem ...string) string {
115 if f := ctxt.JoinPath; f != nil {
116 return f(elem...)
117 }
118 return filepath.Join(elem...)
119 }
120
121
122 func (ctxt *Context) splitPathList(s string) []string {
123 if f := ctxt.SplitPathList; f != nil {
124 return f(s)
125 }
126 return filepath.SplitList(s)
127 }
128
129
130 func (ctxt *Context) isAbsPath(path string) bool {
131 if f := ctxt.IsAbsPath; f != nil {
132 return f(path)
133 }
134 return filepath.IsAbs(path)
135 }
136
137
138 func isDir(path string) bool {
139 fi, err := fsys.Stat(path)
140 return err == nil && fi.IsDir()
141 }
142
143
144
145 func (ctxt *Context) hasSubdir(root, dir string) (rel string, ok bool) {
146 if f := ctxt.HasSubdir; f != nil {
147 return f(root, dir)
148 }
149
150
151 if rel, ok = hasSubdir(root, dir); ok {
152 return
153 }
154
155
156
157
158 rootSym, _ := filepath.EvalSymlinks(root)
159 dirSym, _ := filepath.EvalSymlinks(dir)
160
161 if rel, ok = hasSubdir(rootSym, dir); ok {
162 return
163 }
164 if rel, ok = hasSubdir(root, dirSym); ok {
165 return
166 }
167 return hasSubdir(rootSym, dirSym)
168 }
169
170
171 func hasSubdir(root, dir string) (rel string, ok bool) {
172 root = str.WithFilePathSeparator(filepath.Clean(root))
173 dir = filepath.Clean(dir)
174 if !strings.HasPrefix(dir, root) {
175 return "", false
176 }
177 return filepath.ToSlash(dir[len(root):]), true
178 }
179
180
181 func (ctxt *Context) gopath() []string {
182 var all []string
183 for _, p := range ctxt.splitPathList(ctxt.GOPATH) {
184 if p == "" || p == ctxt.GOROOT {
185
186
187
188
189 continue
190 }
191 if strings.HasPrefix(p, "~") {
192
193
194
195
196
197
198
199
200
201
202
203
204 continue
205 }
206 all = append(all, p)
207 }
208 return all
209 }
210
211 var defaultToolTags, defaultReleaseTags []string
212
213
214
215
216 type NoGoError struct {
217 Dir string
218 }
219
220 func (e *NoGoError) Error() string {
221 return "no buildable Go source files in " + e.Dir
222 }
223
224
225
226 type MultiplePackageError struct {
227 Dir string
228 Packages []string
229 Files []string
230 }
231
232 func (e *MultiplePackageError) Error() string {
233
234 return fmt.Sprintf("found packages %s (%s) and %s (%s) in %s", e.Packages[0], e.Files[0], e.Packages[1], e.Files[1], e.Dir)
235 }
236
237 func nameExt(name string) string {
238 i := strings.LastIndex(name, ".")
239 if i < 0 {
240 return ""
241 }
242 return name[i:]
243 }
244
245 func fileListForExt(p *build.Package, ext string) *[]string {
246 switch ext {
247 case ".c":
248 return &p.CFiles
249 case ".cc", ".cpp", ".cxx":
250 return &p.CXXFiles
251 case ".m":
252 return &p.MFiles
253 case ".h", ".hh", ".hpp", ".hxx":
254 return &p.HFiles
255 case ".f", ".F", ".for", ".f90":
256 return &p.FFiles
257 case ".s", ".S", ".sx":
258 return &p.SFiles
259 case ".swig":
260 return &p.SwigFiles
261 case ".swigcxx":
262 return &p.SwigCXXFiles
263 case ".syso":
264 return &p.SysoFiles
265 }
266 return nil
267 }
268
269 var errNoModules = errors.New("not using modules")
270
271 func findImportComment(data []byte) (s string, line int) {
272
273 word, data := parseWord(data)
274 if string(word) != "package" {
275 return "", 0
276 }
277
278
279 _, data = parseWord(data)
280
281
282
283 for len(data) > 0 && (data[0] == ' ' || data[0] == '\t' || data[0] == '\r') {
284 data = data[1:]
285 }
286
287 var comment []byte
288 switch {
289 case bytes.HasPrefix(data, slashSlash):
290 comment, _, _ = bytes.Cut(data[2:], newline)
291 case bytes.HasPrefix(data, slashStar):
292 var ok bool
293 comment, _, ok = bytes.Cut(data[2:], starSlash)
294 if !ok {
295
296 return "", 0
297 }
298 if bytes.Contains(comment, newline) {
299 return "", 0
300 }
301 }
302 comment = bytes.TrimSpace(comment)
303
304
305 word, arg := parseWord(comment)
306 if string(word) != "import" {
307 return "", 0
308 }
309
310 line = 1 + bytes.Count(data[:cap(data)-cap(arg)], newline)
311 return strings.TrimSpace(string(arg)), line
312 }
313
314 var (
315 slashSlash = []byte("//")
316 slashStar = []byte("/*")
317 starSlash = []byte("*/")
318 newline = []byte("\n")
319 )
320
321
322 func skipSpaceOrComment(data []byte) []byte {
323 for len(data) > 0 {
324 switch data[0] {
325 case ' ', '\t', '\r', '\n':
326 data = data[1:]
327 continue
328 case '/':
329 if bytes.HasPrefix(data, slashSlash) {
330 i := bytes.Index(data, newline)
331 if i < 0 {
332 return nil
333 }
334 data = data[i+1:]
335 continue
336 }
337 if bytes.HasPrefix(data, slashStar) {
338 data = data[2:]
339 i := bytes.Index(data, starSlash)
340 if i < 0 {
341 return nil
342 }
343 data = data[i+2:]
344 continue
345 }
346 }
347 break
348 }
349 return data
350 }
351
352
353
354
355 func parseWord(data []byte) (word, rest []byte) {
356 data = skipSpaceOrComment(data)
357
358
359 rest = data
360 for {
361 r, size := utf8.DecodeRune(rest)
362 if unicode.IsLetter(r) || '0' <= r && r <= '9' || r == '_' {
363 rest = rest[size:]
364 continue
365 }
366 break
367 }
368
369 word = data[:len(data)-len(rest)]
370 if len(word) == 0 {
371 return nil, nil
372 }
373
374 return word, rest
375 }
376
377 var dummyPkg build.Package
378
379
380 type fileInfo struct {
381 name string
382 header []byte
383 fset *token.FileSet
384 parsed *ast.File
385 parseErr error
386 imports []fileImport
387 embeds []fileEmbed
388 directives []build.Directive
389
390
391 binaryOnly bool
392 goBuildConstraint string
393 plusBuildConstraints []string
394 }
395
396 type fileImport struct {
397 path string
398 pos token.Pos
399 doc *ast.CommentGroup
400 }
401
402 type fileEmbed struct {
403 pattern string
404 pos token.Position
405 }
406
407 var errNonSource = errors.New("non source file")
408
409
410
411
412
413
414
415
416
417
418
419 func getFileInfo(dir, name string, fset *token.FileSet) (*fileInfo, error) {
420 if strings.HasPrefix(name, "_") ||
421 strings.HasPrefix(name, ".") {
422 return nil, nil
423 }
424
425 i := strings.LastIndex(name, ".")
426 if i < 0 {
427 i = len(name)
428 }
429 ext := name[i:]
430
431 if ext != ".go" && fileListForExt(&dummyPkg, ext) == nil {
432
433 return nil, errNonSource
434 }
435
436 info := &fileInfo{name: filepath.Join(dir, name), fset: fset}
437 if ext == ".syso" {
438
439 return info, nil
440 }
441
442 f, err := fsys.Open(info.name)
443 if err != nil {
444 return nil, err
445 }
446
447
448
449 var ignoreBinaryOnly bool
450 if strings.HasSuffix(name, ".go") {
451 err = readGoInfo(f, info)
452 if strings.HasSuffix(name, "_test.go") {
453 ignoreBinaryOnly = true
454 }
455 } else {
456 info.header, err = readComments(f)
457 }
458 f.Close()
459 if err != nil {
460 return nil, fmt.Errorf("read %s: %v", info.name, err)
461 }
462
463
464 info.goBuildConstraint, info.plusBuildConstraints, info.binaryOnly, err = getConstraints(info.header)
465 if err != nil {
466 return nil, fmt.Errorf("%s: %v", name, err)
467 }
468
469 if ignoreBinaryOnly && info.binaryOnly {
470 info.binaryOnly = false
471 }
472
473 return info, nil
474 }
475
476 func cleanDecls(m map[string][]token.Position) ([]string, map[string][]token.Position) {
477 all := make([]string, 0, len(m))
478 for path := range m {
479 all = append(all, path)
480 }
481 sort.Strings(all)
482 return all, m
483 }
484
485 var (
486 bSlashSlash = []byte(slashSlash)
487 bStarSlash = []byte(starSlash)
488 bSlashStar = []byte(slashStar)
489 bPlusBuild = []byte("+build")
490
491 goBuildComment = []byte("//go:build")
492
493 errMultipleGoBuild = errors.New("multiple //go:build comments")
494 )
495
496 func isGoBuildComment(line []byte) bool {
497 if !bytes.HasPrefix(line, goBuildComment) {
498 return false
499 }
500 line = bytes.TrimSpace(line)
501 rest := line[len(goBuildComment):]
502 return len(rest) == 0 || len(bytes.TrimSpace(rest)) < len(rest)
503 }
504
505
506
507
508 var binaryOnlyComment = []byte("//go:binary-only-package")
509
510 func getConstraints(content []byte) (goBuild string, plusBuild []string, binaryOnly bool, err error) {
511
512
513
514 content, goBuildBytes, sawBinaryOnly, err := parseFileHeader(content)
515 if err != nil {
516 return "", nil, false, err
517 }
518
519
520
521 if goBuildBytes == nil {
522 p := content
523 for len(p) > 0 {
524 line := p
525 if i := bytes.IndexByte(line, '\n'); i >= 0 {
526 line, p = line[:i], p[i+1:]
527 } else {
528 p = p[len(p):]
529 }
530 line = bytes.TrimSpace(line)
531 if !bytes.HasPrefix(line, bSlashSlash) || !bytes.Contains(line, bPlusBuild) {
532 continue
533 }
534 text := string(line)
535 if !constraint.IsPlusBuild(text) {
536 continue
537 }
538 plusBuild = append(plusBuild, text)
539 }
540 }
541
542 return string(goBuildBytes), plusBuild, sawBinaryOnly, nil
543 }
544
545 func parseFileHeader(content []byte) (trimmed, goBuild []byte, sawBinaryOnly bool, err error) {
546 end := 0
547 p := content
548 ended := false
549 inSlashStar := false
550
551 Lines:
552 for len(p) > 0 {
553 line := p
554 if i := bytes.IndexByte(line, '\n'); i >= 0 {
555 line, p = line[:i], p[i+1:]
556 } else {
557 p = p[len(p):]
558 }
559 line = bytes.TrimSpace(line)
560 if len(line) == 0 && !ended {
561
562
563
564
565
566
567
568
569 end = len(content) - len(p)
570 continue Lines
571 }
572 if !bytes.HasPrefix(line, slashSlash) {
573 ended = true
574 }
575
576 if !inSlashStar && isGoBuildComment(line) {
577 if goBuild != nil {
578 return nil, nil, false, errMultipleGoBuild
579 }
580 goBuild = line
581 }
582 if !inSlashStar && bytes.Equal(line, binaryOnlyComment) {
583 sawBinaryOnly = true
584 }
585
586 Comments:
587 for len(line) > 0 {
588 if inSlashStar {
589 if i := bytes.Index(line, starSlash); i >= 0 {
590 inSlashStar = false
591 line = bytes.TrimSpace(line[i+len(starSlash):])
592 continue Comments
593 }
594 continue Lines
595 }
596 if bytes.HasPrefix(line, bSlashSlash) {
597 continue Lines
598 }
599 if bytes.HasPrefix(line, bSlashStar) {
600 inSlashStar = true
601 line = bytes.TrimSpace(line[len(bSlashStar):])
602 continue Comments
603 }
604
605 break Lines
606 }
607 }
608
609 return content[:end], goBuild, sawBinaryOnly, nil
610 }
611
612
613
614
615 func (ctxt *Context) saveCgo(filename string, di *build.Package, text string) error {
616 for _, line := range strings.Split(text, "\n") {
617 orig := line
618
619
620
621
622 line = strings.TrimSpace(line)
623 if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') {
624 continue
625 }
626
627
628 if fields := strings.Fields(line); len(fields) == 3 && (fields[1] == "nocallback" || fields[1] == "noescape") {
629 continue
630 }
631
632
633 line, argstr, ok := strings.Cut(strings.TrimSpace(line[4:]), ":")
634 if !ok {
635 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
636 }
637
638
639 f := strings.Fields(line)
640 if len(f) < 1 {
641 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
642 }
643
644 cond, verb := f[:len(f)-1], f[len(f)-1]
645 if len(cond) > 0 {
646 ok := false
647 for _, c := range cond {
648 if ctxt.matchAuto(c, nil) {
649 ok = true
650 break
651 }
652 }
653 if !ok {
654 continue
655 }
656 }
657
658 args, err := splitQuoted(argstr)
659 if err != nil {
660 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
661 }
662 for i, arg := range args {
663 if arg, ok = expandSrcDir(arg, di.Dir); !ok {
664 return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg)
665 }
666 args[i] = arg
667 }
668
669 switch verb {
670 case "CFLAGS", "CPPFLAGS", "CXXFLAGS", "FFLAGS", "LDFLAGS":
671
672 ctxt.makePathsAbsolute(args, di.Dir)
673 }
674
675 switch verb {
676 case "CFLAGS":
677 di.CgoCFLAGS = append(di.CgoCFLAGS, args...)
678 case "CPPFLAGS":
679 di.CgoCPPFLAGS = append(di.CgoCPPFLAGS, args...)
680 case "CXXFLAGS":
681 di.CgoCXXFLAGS = append(di.CgoCXXFLAGS, args...)
682 case "FFLAGS":
683 di.CgoFFLAGS = append(di.CgoFFLAGS, args...)
684 case "LDFLAGS":
685 di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...)
686 case "pkg-config":
687 di.CgoPkgConfig = append(di.CgoPkgConfig, args...)
688 default:
689 return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig)
690 }
691 }
692 return nil
693 }
694
695
696
697 func expandSrcDir(str string, srcdir string) (string, bool) {
698
699
700
701 srcdir = filepath.ToSlash(srcdir)
702
703 chunks := strings.Split(str, "${SRCDIR}")
704 if len(chunks) < 2 {
705 return str, safeCgoName(str)
706 }
707 ok := true
708 for _, chunk := range chunks {
709 ok = ok && (chunk == "" || safeCgoName(chunk))
710 }
711 ok = ok && (srcdir == "" || safeCgoName(srcdir))
712 res := strings.Join(chunks, srcdir)
713 return res, ok && res != ""
714 }
715
716
717
718
719
720
721
722
723
724
725
726
727 func (ctxt *Context) makePathsAbsolute(args []string, srcDir string) {
728 nextPath := false
729 for i, arg := range args {
730 if nextPath {
731 if !filepath.IsAbs(arg) {
732 args[i] = filepath.Join(srcDir, arg)
733 }
734 nextPath = false
735 } else if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") {
736 if len(arg) == 2 {
737 nextPath = true
738 } else {
739 if !filepath.IsAbs(arg[2:]) {
740 args[i] = arg[:2] + filepath.Join(srcDir, arg[2:])
741 }
742 }
743 }
744 }
745 }
746
747
748
749
750
751
752
753
754 const safeString = "+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:$@%! ~^"
755
756 func safeCgoName(s string) bool {
757 if s == "" {
758 return false
759 }
760 for i := 0; i < len(s); i++ {
761 if c := s[i]; c < utf8.RuneSelf && strings.IndexByte(safeString, c) < 0 {
762 return false
763 }
764 }
765 return true
766 }
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783 func splitQuoted(s string) (r []string, err error) {
784 var args []string
785 arg := make([]rune, len(s))
786 escaped := false
787 quoted := false
788 quote := '\x00'
789 i := 0
790 for _, rune := range s {
791 switch {
792 case escaped:
793 escaped = false
794 case rune == '\\':
795 escaped = true
796 continue
797 case quote != '\x00':
798 if rune == quote {
799 quote = '\x00'
800 continue
801 }
802 case rune == '"' || rune == '\'':
803 quoted = true
804 quote = rune
805 continue
806 case unicode.IsSpace(rune):
807 if quoted || i > 0 {
808 quoted = false
809 args = append(args, string(arg[:i]))
810 i = 0
811 }
812 continue
813 }
814 arg[i] = rune
815 i++
816 }
817 if quoted || i > 0 {
818 args = append(args, string(arg[:i]))
819 }
820 if quote != 0 {
821 err = errors.New("unclosed quote")
822 } else if escaped {
823 err = errors.New("unfinished escaping")
824 }
825 return args, err
826 }
827
828
829
830
831
832
833 func (ctxt *Context) matchAuto(text string, allTags map[string]bool) bool {
834 if strings.ContainsAny(text, "&|()") {
835 text = "//go:build " + text
836 } else {
837 text = "// +build " + text
838 }
839 x, err := constraint.Parse(text)
840 if err != nil {
841 return false
842 }
843 return ctxt.eval(x, allTags)
844 }
845
846 func (ctxt *Context) eval(x constraint.Expr, allTags map[string]bool) bool {
847 return x.Eval(func(tag string) bool { return ctxt.matchTag(tag, allTags) })
848 }
849
850
851
852
853
854
855
856
857
858
859
860
861
862 func (ctxt *Context) matchTag(name string, allTags map[string]bool) bool {
863 if allTags != nil {
864 allTags[name] = true
865 }
866
867
868 if ctxt.CgoEnabled && name == "cgo" {
869 return true
870 }
871 if name == ctxt.GOOS || name == ctxt.GOARCH || name == ctxt.Compiler {
872 return true
873 }
874 if ctxt.GOOS == "android" && name == "linux" {
875 return true
876 }
877 if ctxt.GOOS == "illumos" && name == "solaris" {
878 return true
879 }
880 if ctxt.GOOS == "ios" && name == "darwin" {
881 return true
882 }
883 if name == "unix" && syslist.UnixOS[ctxt.GOOS] {
884 return true
885 }
886 if name == "boringcrypto" {
887 name = "goexperiment.boringcrypto"
888 }
889
890
891 return slices.Contains(ctxt.BuildTags, name) || slices.Contains(ctxt.ToolTags, name) ||
892 slices.Contains(ctxt.ReleaseTags, name)
893 }
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910 func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool {
911 name, _, _ = strings.Cut(name, ".")
912
913
914
915
916
917
918
919
920 i := strings.Index(name, "_")
921 if i < 0 {
922 return true
923 }
924 name = name[i:]
925
926 l := strings.Split(name, "_")
927 if n := len(l); n > 0 && l[n-1] == "test" {
928 l = l[:n-1]
929 }
930 n := len(l)
931 if n >= 2 && syslist.KnownOS[l[n-2]] && syslist.KnownArch[l[n-1]] {
932 if allTags != nil {
933
934 allTags[l[n-2]] = true
935 }
936 return ctxt.matchTag(l[n-1], allTags) && ctxt.matchTag(l[n-2], allTags)
937 }
938 if n >= 1 && (syslist.KnownOS[l[n-1]] || syslist.KnownArch[l[n-1]]) {
939 return ctxt.matchTag(l[n-1], allTags)
940 }
941 return true
942 }
943
View as plain text