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