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