Source file
src/go/build/build.go
1
2
3
4
5 package build
6
7 import (
8 "bytes"
9 "errors"
10 "fmt"
11 "go/ast"
12 "go/doc"
13 "go/parser"
14 "go/token"
15 "io"
16 "io/ioutil"
17 "log"
18 "os"
19 pathpkg "path"
20 "path/filepath"
21 "runtime"
22 "sort"
23 "strconv"
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 CgoEnabled bool
36 UseAllFiles bool
37 Compiler string
38
39
40
41
42
43
44
45
46 BuildTags []string
47 ReleaseTags []string
48
49
50
51
52
53
54
55 InstallSuffix string
56
57
58
59
60
61
62
63
64
65 JoinPath func(elem ...string) string
66
67
68
69 SplitPathList func(list string) []string
70
71
72
73 IsAbsPath func(path string) bool
74
75
76
77 IsDir func(path string) bool
78
79
80
81
82
83
84
85
86 HasSubdir func(root, dir string) (rel string, ok bool)
87
88
89
90
91 ReadDir func(dir string) ([]os.FileInfo, error)
92
93
94
95 OpenFile func(path string) (io.ReadCloser, error)
96 }
97
98
99 func (ctxt *Context) joinPath(elem ...string) string {
100 if f := ctxt.JoinPath; f != nil {
101 return f(elem...)
102 }
103 return filepath.Join(elem...)
104 }
105
106
107 func (ctxt *Context) splitPathList(s string) []string {
108 if f := ctxt.SplitPathList; f != nil {
109 return f(s)
110 }
111 return filepath.SplitList(s)
112 }
113
114
115 func (ctxt *Context) isAbsPath(path string) bool {
116 if f := ctxt.IsAbsPath; f != nil {
117 return f(path)
118 }
119 return filepath.IsAbs(path)
120 }
121
122
123 func (ctxt *Context) isDir(path string) bool {
124 if f := ctxt.IsDir; f != nil {
125 return f(path)
126 }
127 fi, err := os.Stat(path)
128 return err == nil && fi.IsDir()
129 }
130
131
132
133 func (ctxt *Context) hasSubdir(root, dir string) (rel string, ok bool) {
134 if f := ctxt.HasSubdir; f != nil {
135 return f(root, dir)
136 }
137
138
139 if rel, ok = hasSubdir(root, dir); ok {
140 return
141 }
142
143
144
145
146 rootSym, _ := filepath.EvalSymlinks(root)
147 dirSym, _ := filepath.EvalSymlinks(dir)
148
149 if rel, ok = hasSubdir(rootSym, dir); ok {
150 return
151 }
152 if rel, ok = hasSubdir(root, dirSym); ok {
153 return
154 }
155 return hasSubdir(rootSym, dirSym)
156 }
157
158
159 func hasSubdir(root, dir string) (rel string, ok bool) {
160 const sep = string(filepath.Separator)
161 root = filepath.Clean(root)
162 if !strings.HasSuffix(root, sep) {
163 root += sep
164 }
165 dir = filepath.Clean(dir)
166 if !strings.HasPrefix(dir, root) {
167 return "", false
168 }
169 return filepath.ToSlash(dir[len(root):]), true
170 }
171
172
173 func (ctxt *Context) readDir(path string) ([]os.FileInfo, error) {
174 if f := ctxt.ReadDir; f != nil {
175 return f(path)
176 }
177 return ioutil.ReadDir(path)
178 }
179
180
181 func (ctxt *Context) openFile(path string) (io.ReadCloser, error) {
182 if fn := ctxt.OpenFile; fn != nil {
183 return fn(path)
184 }
185
186 f, err := os.Open(path)
187 if err != nil {
188 return nil, err
189 }
190 return f, nil
191 }
192
193
194
195
196 func (ctxt *Context) isFile(path string) bool {
197 f, err := ctxt.openFile(path)
198 if err != nil {
199 return false
200 }
201 f.Close()
202 return true
203 }
204
205
206 func (ctxt *Context) gopath() []string {
207 var all []string
208 for _, p := range ctxt.splitPathList(ctxt.GOPATH) {
209 if p == "" || p == ctxt.GOROOT {
210
211
212
213
214 continue
215 }
216 if strings.HasPrefix(p, "~") {
217
218
219
220
221
222
223
224
225
226
227
228
229 continue
230 }
231 all = append(all, p)
232 }
233 return all
234 }
235
236
237
238
239 func (ctxt *Context) SrcDirs() []string {
240 var all []string
241 if ctxt.GOROOT != "" {
242 dir := ctxt.joinPath(ctxt.GOROOT, "src")
243 if ctxt.isDir(dir) {
244 all = append(all, dir)
245 }
246 }
247 for _, p := range ctxt.gopath() {
248 dir := ctxt.joinPath(p, "src")
249 if ctxt.isDir(dir) {
250 all = append(all, dir)
251 }
252 }
253 return all
254 }
255
256
257
258
259 var Default Context = defaultContext()
260
261 func defaultGOPATH() string {
262 env := "HOME"
263 if runtime.GOOS == "windows" {
264 env = "USERPROFILE"
265 } else if runtime.GOOS == "plan9" {
266 env = "home"
267 }
268 if home := os.Getenv(env); home != "" {
269 def := filepath.Join(home, "go")
270 if filepath.Clean(def) == filepath.Clean(runtime.GOROOT()) {
271
272
273 return ""
274 }
275 return def
276 }
277 return ""
278 }
279
280 func defaultContext() Context {
281 var c Context
282
283 c.GOARCH = envOr("GOARCH", runtime.GOARCH)
284 c.GOOS = envOr("GOOS", runtime.GOOS)
285 c.GOROOT = pathpkg.Clean(runtime.GOROOT())
286 c.GOPATH = envOr("GOPATH", defaultGOPATH())
287 c.Compiler = runtime.Compiler
288
289
290
291
292
293
294
295 const version = 10
296 for i := 1; i <= version; i++ {
297 c.ReleaseTags = append(c.ReleaseTags, "go1."+strconv.Itoa(i))
298 }
299
300 env := os.Getenv("CGO_ENABLED")
301 if env == "" {
302 env = defaultCGO_ENABLED
303 }
304 switch env {
305 case "1":
306 c.CgoEnabled = true
307 case "0":
308 c.CgoEnabled = false
309 default:
310
311 if runtime.GOARCH == c.GOARCH && runtime.GOOS == c.GOOS {
312 c.CgoEnabled = cgoEnabled[c.GOOS+"/"+c.GOARCH]
313 break
314 }
315 c.CgoEnabled = false
316 }
317
318 return c
319 }
320
321 func envOr(name, def string) string {
322 s := os.Getenv(name)
323 if s == "" {
324 return def
325 }
326 return s
327 }
328
329
330 type ImportMode uint
331
332 const (
333
334
335
336 FindOnly ImportMode = 1 << iota
337
338
339
340
341
342
343
344
345
346
347 AllowBinary
348
349
350
351
352
353 ImportComment
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373 IgnoreVendor
374 )
375
376
377 type Package struct {
378 Dir string
379 Name string
380 ImportComment string
381 Doc string
382 ImportPath string
383 Root string
384 SrcRoot string
385 PkgRoot string
386 PkgTargetRoot string
387 BinDir string
388 Goroot bool
389 PkgObj string
390 AllTags []string
391 ConflictDir string
392 BinaryOnly bool
393
394
395 GoFiles []string
396 CgoFiles []string
397 IgnoredGoFiles []string
398 InvalidGoFiles []string
399 CFiles []string
400 CXXFiles []string
401 MFiles []string
402 HFiles []string
403 FFiles []string
404 SFiles []string
405 SwigFiles []string
406 SwigCXXFiles []string
407 SysoFiles []string
408
409
410 CgoCFLAGS []string
411 CgoCPPFLAGS []string
412 CgoCXXFLAGS []string
413 CgoFFLAGS []string
414 CgoLDFLAGS []string
415 CgoPkgConfig []string
416
417
418 Imports []string
419 ImportPos map[string][]token.Position
420
421
422 TestGoFiles []string
423 TestImports []string
424 TestImportPos map[string][]token.Position
425 XTestGoFiles []string
426 XTestImports []string
427 XTestImportPos map[string][]token.Position
428 }
429
430
431
432
433 func (p *Package) IsCommand() bool {
434 return p.Name == "main"
435 }
436
437
438
439 func (ctxt *Context) ImportDir(dir string, mode ImportMode) (*Package, error) {
440 return ctxt.Import(".", dir, mode)
441 }
442
443
444
445
446 type NoGoError struct {
447 Dir string
448 }
449
450 func (e *NoGoError) Error() string {
451 return "no buildable Go source files in " + e.Dir
452 }
453
454
455
456 type MultiplePackageError struct {
457 Dir string
458 Packages []string
459 Files []string
460 }
461
462 func (e *MultiplePackageError) Error() string {
463
464 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)
465 }
466
467 func nameExt(name string) string {
468 i := strings.LastIndex(name, ".")
469 if i < 0 {
470 return ""
471 }
472 return name[i:]
473 }
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491 func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Package, error) {
492 p := &Package{
493 ImportPath: path,
494 }
495 if path == "" {
496 return p, fmt.Errorf("import %q: invalid import path", path)
497 }
498
499 var pkgtargetroot string
500 var pkga string
501 var pkgerr error
502 suffix := ""
503 if ctxt.InstallSuffix != "" {
504 suffix = "_" + ctxt.InstallSuffix
505 }
506 switch ctxt.Compiler {
507 case "gccgo":
508 pkgtargetroot = "pkg/gccgo_" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix
509 case "gc":
510 pkgtargetroot = "pkg/" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix
511 default:
512
513 pkgerr = fmt.Errorf("import %q: unknown compiler %q", path, ctxt.Compiler)
514 }
515 setPkga := func() {
516 switch ctxt.Compiler {
517 case "gccgo":
518 dir, elem := pathpkg.Split(p.ImportPath)
519 pkga = pkgtargetroot + "/" + dir + "lib" + elem + ".a"
520 case "gc":
521 pkga = pkgtargetroot + "/" + p.ImportPath + ".a"
522 }
523 }
524 setPkga()
525
526 binaryOnly := false
527 if IsLocalImport(path) {
528 pkga = ""
529 if srcDir == "" {
530 return p, fmt.Errorf("import %q: import relative to unknown directory", path)
531 }
532 if !ctxt.isAbsPath(path) {
533 p.Dir = ctxt.joinPath(srcDir, path)
534 }
535
536
537
538 inTestdata := func(sub string) bool {
539 return strings.Contains(sub, "/testdata/") || strings.HasSuffix(sub, "/testdata") || strings.HasPrefix(sub, "testdata/") || sub == "testdata"
540 }
541 if ctxt.GOROOT != "" {
542 root := ctxt.joinPath(ctxt.GOROOT, "src")
543 if sub, ok := ctxt.hasSubdir(root, p.Dir); ok && !inTestdata(sub) {
544 p.Goroot = true
545 p.ImportPath = sub
546 p.Root = ctxt.GOROOT
547 setPkga()
548 goto Found
549 }
550 }
551 all := ctxt.gopath()
552 for i, root := range all {
553 rootsrc := ctxt.joinPath(root, "src")
554 if sub, ok := ctxt.hasSubdir(rootsrc, p.Dir); ok && !inTestdata(sub) {
555
556
557
558 if ctxt.GOROOT != "" {
559 if dir := ctxt.joinPath(ctxt.GOROOT, "src", sub); ctxt.isDir(dir) {
560 p.ConflictDir = dir
561 goto Found
562 }
563 }
564 for _, earlyRoot := range all[:i] {
565 if dir := ctxt.joinPath(earlyRoot, "src", sub); ctxt.isDir(dir) {
566 p.ConflictDir = dir
567 goto Found
568 }
569 }
570
571
572
573 p.ImportPath = sub
574 p.Root = root
575 setPkga()
576 goto Found
577 }
578 }
579
580
581 } else {
582 if strings.HasPrefix(path, "/") {
583 return p, fmt.Errorf("import %q: cannot import absolute path", path)
584 }
585
586
587 var tried struct {
588 vendor []string
589 goroot string
590 gopath []string
591 }
592 gopath := ctxt.gopath()
593
594
595 if mode&IgnoreVendor == 0 && srcDir != "" {
596 searchVendor := func(root string, isGoroot bool) bool {
597 sub, ok := ctxt.hasSubdir(root, srcDir)
598 if !ok || !strings.HasPrefix(sub, "src/") || strings.Contains(sub, "/testdata/") {
599 return false
600 }
601 for {
602 vendor := ctxt.joinPath(root, sub, "vendor")
603 if ctxt.isDir(vendor) {
604 dir := ctxt.joinPath(vendor, path)
605 if ctxt.isDir(dir) && hasGoFiles(ctxt, dir) {
606 p.Dir = dir
607 p.ImportPath = strings.TrimPrefix(pathpkg.Join(sub, "vendor", path), "src/")
608 p.Goroot = isGoroot
609 p.Root = root
610 setPkga()
611 return true
612 }
613 tried.vendor = append(tried.vendor, dir)
614 }
615 i := strings.LastIndex(sub, "/")
616 if i < 0 {
617 break
618 }
619 sub = sub[:i]
620 }
621 return false
622 }
623 if searchVendor(ctxt.GOROOT, true) {
624 goto Found
625 }
626 for _, root := range gopath {
627 if searchVendor(root, false) {
628 goto Found
629 }
630 }
631 }
632
633
634 if ctxt.GOROOT != "" {
635 dir := ctxt.joinPath(ctxt.GOROOT, "src", path)
636 isDir := ctxt.isDir(dir)
637 binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(ctxt.GOROOT, pkga))
638 if isDir || binaryOnly {
639 p.Dir = dir
640 p.Goroot = true
641 p.Root = ctxt.GOROOT
642 goto Found
643 }
644 tried.goroot = dir
645 }
646 for _, root := range gopath {
647 dir := ctxt.joinPath(root, "src", path)
648 isDir := ctxt.isDir(dir)
649 binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(root, pkga))
650 if isDir || binaryOnly {
651 p.Dir = dir
652 p.Root = root
653 goto Found
654 }
655 tried.gopath = append(tried.gopath, dir)
656 }
657
658
659 var paths []string
660 format := "\t%s (vendor tree)"
661 for _, dir := range tried.vendor {
662 paths = append(paths, fmt.Sprintf(format, dir))
663 format = "\t%s"
664 }
665 if tried.goroot != "" {
666 paths = append(paths, fmt.Sprintf("\t%s (from $GOROOT)", tried.goroot))
667 } else {
668 paths = append(paths, "\t($GOROOT not set)")
669 }
670 format = "\t%s (from $GOPATH)"
671 for _, dir := range tried.gopath {
672 paths = append(paths, fmt.Sprintf(format, dir))
673 format = "\t%s"
674 }
675 if len(tried.gopath) == 0 {
676 paths = append(paths, "\t($GOPATH not set. For more details see: 'go help gopath')")
677 }
678 return p, fmt.Errorf("cannot find package %q in any of:\n%s", path, strings.Join(paths, "\n"))
679 }
680
681 Found:
682 if p.Root != "" {
683 p.SrcRoot = ctxt.joinPath(p.Root, "src")
684 p.PkgRoot = ctxt.joinPath(p.Root, "pkg")
685 p.BinDir = ctxt.joinPath(p.Root, "bin")
686 if pkga != "" {
687 p.PkgTargetRoot = ctxt.joinPath(p.Root, pkgtargetroot)
688 p.PkgObj = ctxt.joinPath(p.Root, pkga)
689 }
690 }
691
692
693
694
695
696
697 if IsLocalImport(path) && !ctxt.isDir(p.Dir) {
698
699 return p, fmt.Errorf("cannot find package %q in:\n\t%s", path, p.Dir)
700 }
701
702 if mode&FindOnly != 0 {
703 return p, pkgerr
704 }
705 if binaryOnly && (mode&AllowBinary) != 0 {
706 return p, pkgerr
707 }
708
709 dirs, err := ctxt.readDir(p.Dir)
710 if err != nil {
711 return p, err
712 }
713
714 var badGoError error
715 var Sfiles []string
716 var firstFile, firstCommentFile string
717 imported := make(map[string][]token.Position)
718 testImported := make(map[string][]token.Position)
719 xTestImported := make(map[string][]token.Position)
720 allTags := make(map[string]bool)
721 fset := token.NewFileSet()
722 for _, d := range dirs {
723 if d.IsDir() {
724 continue
725 }
726
727 name := d.Name()
728 ext := nameExt(name)
729
730 badFile := func(err error) {
731 if badGoError == nil {
732 badGoError = err
733 }
734 p.InvalidGoFiles = append(p.InvalidGoFiles, name)
735 }
736
737 match, data, filename, err := ctxt.matchFile(p.Dir, name, allTags, &p.BinaryOnly)
738 if err != nil {
739 badFile(err)
740 continue
741 }
742 if !match {
743 if ext == ".go" {
744 p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
745 }
746 continue
747 }
748
749
750 switch ext {
751 case ".c":
752 p.CFiles = append(p.CFiles, name)
753 continue
754 case ".cc", ".cpp", ".cxx":
755 p.CXXFiles = append(p.CXXFiles, name)
756 continue
757 case ".m":
758 p.MFiles = append(p.MFiles, name)
759 continue
760 case ".h", ".hh", ".hpp", ".hxx":
761 p.HFiles = append(p.HFiles, name)
762 continue
763 case ".f", ".F", ".for", ".f90":
764 p.FFiles = append(p.FFiles, name)
765 continue
766 case ".s":
767 p.SFiles = append(p.SFiles, name)
768 continue
769 case ".S":
770 Sfiles = append(Sfiles, name)
771 continue
772 case ".swig":
773 p.SwigFiles = append(p.SwigFiles, name)
774 continue
775 case ".swigcxx":
776 p.SwigCXXFiles = append(p.SwigCXXFiles, name)
777 continue
778 case ".syso":
779
780
781
782 p.SysoFiles = append(p.SysoFiles, name)
783 continue
784 }
785
786 pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly|parser.ParseComments)
787 if err != nil {
788 badFile(err)
789 continue
790 }
791
792 pkg := pf.Name.Name
793 if pkg == "documentation" {
794 p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
795 continue
796 }
797
798 isTest := strings.HasSuffix(name, "_test.go")
799 isXTest := false
800 if isTest && strings.HasSuffix(pkg, "_test") {
801 isXTest = true
802 pkg = pkg[:len(pkg)-len("_test")]
803 }
804
805 if p.Name == "" {
806 p.Name = pkg
807 firstFile = name
808 } else if pkg != p.Name {
809 badFile(&MultiplePackageError{
810 Dir: p.Dir,
811 Packages: []string{p.Name, pkg},
812 Files: []string{firstFile, name},
813 })
814 p.InvalidGoFiles = append(p.InvalidGoFiles, name)
815 }
816 if pf.Doc != nil && p.Doc == "" {
817 p.Doc = doc.Synopsis(pf.Doc.Text())
818 }
819
820 if mode&ImportComment != 0 {
821 qcom, line := findImportComment(data)
822 if line != 0 {
823 com, err := strconv.Unquote(qcom)
824 if err != nil {
825 badFile(fmt.Errorf("%s:%d: cannot parse import comment", filename, line))
826 } else if p.ImportComment == "" {
827 p.ImportComment = com
828 firstCommentFile = name
829 } else if p.ImportComment != com {
830 badFile(fmt.Errorf("found import comments %q (%s) and %q (%s) in %s", p.ImportComment, firstCommentFile, com, name, p.Dir))
831 }
832 }
833 }
834
835
836 isCgo := false
837 for _, decl := range pf.Decls {
838 d, ok := decl.(*ast.GenDecl)
839 if !ok {
840 continue
841 }
842 for _, dspec := range d.Specs {
843 spec, ok := dspec.(*ast.ImportSpec)
844 if !ok {
845 continue
846 }
847 quoted := spec.Path.Value
848 path, err := strconv.Unquote(quoted)
849 if err != nil {
850 log.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted)
851 }
852 if isXTest {
853 xTestImported[path] = append(xTestImported[path], fset.Position(spec.Pos()))
854 } else if isTest {
855 testImported[path] = append(testImported[path], fset.Position(spec.Pos()))
856 } else {
857 imported[path] = append(imported[path], fset.Position(spec.Pos()))
858 }
859 if path == "C" {
860 if isTest {
861 badFile(fmt.Errorf("use of cgo in test %s not supported", filename))
862 } else {
863 cg := spec.Doc
864 if cg == nil && len(d.Specs) == 1 {
865 cg = d.Doc
866 }
867 if cg != nil {
868 if err := ctxt.saveCgo(filename, p, cg); err != nil {
869 badFile(err)
870 }
871 }
872 isCgo = true
873 }
874 }
875 }
876 }
877 if isCgo {
878 allTags["cgo"] = true
879 if ctxt.CgoEnabled {
880 p.CgoFiles = append(p.CgoFiles, name)
881 } else {
882 p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
883 }
884 } else if isXTest {
885 p.XTestGoFiles = append(p.XTestGoFiles, name)
886 } else if isTest {
887 p.TestGoFiles = append(p.TestGoFiles, name)
888 } else {
889 p.GoFiles = append(p.GoFiles, name)
890 }
891 }
892 if badGoError != nil {
893 return p, badGoError
894 }
895 if len(p.GoFiles)+len(p.CgoFiles)+len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
896 return p, &NoGoError{p.Dir}
897 }
898
899 for tag := range allTags {
900 p.AllTags = append(p.AllTags, tag)
901 }
902 sort.Strings(p.AllTags)
903
904 p.Imports, p.ImportPos = cleanImports(imported)
905 p.TestImports, p.TestImportPos = cleanImports(testImported)
906 p.XTestImports, p.XTestImportPos = cleanImports(xTestImported)
907
908
909
910
911 if len(p.CgoFiles) > 0 {
912 p.SFiles = append(p.SFiles, Sfiles...)
913 sort.Strings(p.SFiles)
914 }
915
916 return p, pkgerr
917 }
918
919
920
921
922
923 func hasGoFiles(ctxt *Context, dir string) bool {
924 ents, _ := ctxt.readDir(dir)
925 for _, ent := range ents {
926 if !ent.IsDir() && strings.HasSuffix(ent.Name(), ".go") {
927 return true
928 }
929 }
930 return false
931 }
932
933 func findImportComment(data []byte) (s string, line int) {
934
935 word, data := parseWord(data)
936 if string(word) != "package" {
937 return "", 0
938 }
939
940
941 _, data = parseWord(data)
942
943
944
945 for len(data) > 0 && (data[0] == ' ' || data[0] == '\t' || data[0] == '\r') {
946 data = data[1:]
947 }
948
949 var comment []byte
950 switch {
951 case bytes.HasPrefix(data, slashSlash):
952 i := bytes.Index(data, newline)
953 if i < 0 {
954 i = len(data)
955 }
956 comment = data[2:i]
957 case bytes.HasPrefix(data, slashStar):
958 data = data[2:]
959 i := bytes.Index(data, starSlash)
960 if i < 0 {
961
962 return "", 0
963 }
964 comment = data[:i]
965 if bytes.Contains(comment, newline) {
966 return "", 0
967 }
968 }
969 comment = bytes.TrimSpace(comment)
970
971
972 word, arg := parseWord(comment)
973 if string(word) != "import" {
974 return "", 0
975 }
976
977 line = 1 + bytes.Count(data[:cap(data)-cap(arg)], newline)
978 return strings.TrimSpace(string(arg)), line
979 }
980
981 var (
982 slashSlash = []byte("//")
983 slashStar = []byte("/*")
984 starSlash = []byte("*/")
985 newline = []byte("\n")
986 )
987
988
989 func skipSpaceOrComment(data []byte) []byte {
990 for len(data) > 0 {
991 switch data[0] {
992 case ' ', '\t', '\r', '\n':
993 data = data[1:]
994 continue
995 case '/':
996 if bytes.HasPrefix(data, slashSlash) {
997 i := bytes.Index(data, newline)
998 if i < 0 {
999 return nil
1000 }
1001 data = data[i+1:]
1002 continue
1003 }
1004 if bytes.HasPrefix(data, slashStar) {
1005 data = data[2:]
1006 i := bytes.Index(data, starSlash)
1007 if i < 0 {
1008 return nil
1009 }
1010 data = data[i+2:]
1011 continue
1012 }
1013 }
1014 break
1015 }
1016 return data
1017 }
1018
1019
1020
1021
1022 func parseWord(data []byte) (word, rest []byte) {
1023 data = skipSpaceOrComment(data)
1024
1025
1026 rest = data
1027 for {
1028 r, size := utf8.DecodeRune(rest)
1029 if unicode.IsLetter(r) || '0' <= r && r <= '9' || r == '_' {
1030 rest = rest[size:]
1031 continue
1032 }
1033 break
1034 }
1035
1036 word = data[:len(data)-len(rest)]
1037 if len(word) == 0 {
1038 return nil, nil
1039 }
1040
1041 return word, rest
1042 }
1043
1044
1045
1046
1047
1048
1049
1050 func (ctxt *Context) MatchFile(dir, name string) (match bool, err error) {
1051 match, _, _, err = ctxt.matchFile(dir, name, nil, nil)
1052 return
1053 }
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063 func (ctxt *Context) matchFile(dir, name string, allTags map[string]bool, binaryOnly *bool) (match bool, data []byte, filename string, err error) {
1064 if strings.HasPrefix(name, "_") ||
1065 strings.HasPrefix(name, ".") {
1066 return
1067 }
1068
1069 i := strings.LastIndex(name, ".")
1070 if i < 0 {
1071 i = len(name)
1072 }
1073 ext := name[i:]
1074
1075 if !ctxt.goodOSArchFile(name, allTags) && !ctxt.UseAllFiles {
1076 return
1077 }
1078
1079 switch ext {
1080 case ".go", ".c", ".cc", ".cxx", ".cpp", ".m", ".s", ".h", ".hh", ".hpp", ".hxx", ".f", ".F", ".f90", ".S", ".swig", ".swigcxx":
1081
1082 case ".syso":
1083
1084 match = true
1085 return
1086 default:
1087
1088 return
1089 }
1090
1091 filename = ctxt.joinPath(dir, name)
1092 f, err := ctxt.openFile(filename)
1093 if err != nil {
1094 return
1095 }
1096
1097 if strings.HasSuffix(filename, ".go") {
1098 data, err = readImports(f, false, nil)
1099 if strings.HasSuffix(filename, "_test.go") {
1100 binaryOnly = nil
1101 }
1102 } else {
1103 binaryOnly = nil
1104 data, err = readComments(f)
1105 }
1106 f.Close()
1107 if err != nil {
1108 err = fmt.Errorf("read %s: %v", filename, err)
1109 return
1110 }
1111
1112
1113 var sawBinaryOnly bool
1114 if !ctxt.shouldBuild(data, allTags, &sawBinaryOnly) && !ctxt.UseAllFiles {
1115 return
1116 }
1117
1118 if binaryOnly != nil && sawBinaryOnly {
1119 *binaryOnly = true
1120 }
1121 match = true
1122 return
1123 }
1124
1125 func cleanImports(m map[string][]token.Position) ([]string, map[string][]token.Position) {
1126 all := make([]string, 0, len(m))
1127 for path := range m {
1128 all = append(all, path)
1129 }
1130 sort.Strings(all)
1131 return all, m
1132 }
1133
1134
1135 func Import(path, srcDir string, mode ImportMode) (*Package, error) {
1136 return Default.Import(path, srcDir, mode)
1137 }
1138
1139
1140 func ImportDir(dir string, mode ImportMode) (*Package, error) {
1141 return Default.ImportDir(dir, mode)
1142 }
1143
1144 var slashslash = []byte("//")
1145
1146
1147
1148
1149 var binaryOnlyComment = []byte("//go:binary-only-package")
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167 func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool, binaryOnly *bool) bool {
1168 sawBinaryOnly := false
1169
1170
1171
1172 end := 0
1173 p := content
1174 for len(p) > 0 {
1175 line := p
1176 if i := bytes.IndexByte(line, '\n'); i >= 0 {
1177 line, p = line[:i], p[i+1:]
1178 } else {
1179 p = p[len(p):]
1180 }
1181 line = bytes.TrimSpace(line)
1182 if len(line) == 0 {
1183 end = len(content) - len(p)
1184 continue
1185 }
1186 if !bytes.HasPrefix(line, slashslash) {
1187 break
1188 }
1189 }
1190 content = content[:end]
1191
1192
1193 p = content
1194 allok := true
1195 for len(p) > 0 {
1196 line := p
1197 if i := bytes.IndexByte(line, '\n'); i >= 0 {
1198 line, p = line[:i], p[i+1:]
1199 } else {
1200 p = p[len(p):]
1201 }
1202 line = bytes.TrimSpace(line)
1203 if !bytes.HasPrefix(line, slashslash) {
1204 continue
1205 }
1206 if bytes.Equal(line, binaryOnlyComment) {
1207 sawBinaryOnly = true
1208 }
1209 line = bytes.TrimSpace(line[len(slashslash):])
1210 if len(line) > 0 && line[0] == '+' {
1211
1212 f := strings.Fields(string(line))
1213 if f[0] == "+build" {
1214 ok := false
1215 for _, tok := range f[1:] {
1216 if ctxt.match(tok, allTags) {
1217 ok = true
1218 }
1219 }
1220 if !ok {
1221 allok = false
1222 }
1223 }
1224 }
1225 }
1226
1227 if binaryOnly != nil && sawBinaryOnly {
1228 *binaryOnly = true
1229 }
1230
1231 return allok
1232 }
1233
1234
1235
1236
1237 func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup) error {
1238 text := cg.Text()
1239 for _, line := range strings.Split(text, "\n") {
1240 orig := line
1241
1242
1243
1244
1245 line = strings.TrimSpace(line)
1246 if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') {
1247 continue
1248 }
1249
1250
1251 line = strings.TrimSpace(line[4:])
1252 i := strings.Index(line, ":")
1253 if i < 0 {
1254 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
1255 }
1256 line, argstr := line[:i], line[i+1:]
1257
1258
1259 f := strings.Fields(line)
1260 if len(f) < 1 {
1261 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
1262 }
1263
1264 cond, verb := f[:len(f)-1], f[len(f)-1]
1265 if len(cond) > 0 {
1266 ok := false
1267 for _, c := range cond {
1268 if ctxt.match(c, nil) {
1269 ok = true
1270 break
1271 }
1272 }
1273 if !ok {
1274 continue
1275 }
1276 }
1277
1278 args, err := splitQuoted(argstr)
1279 if err != nil {
1280 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
1281 }
1282 var ok bool
1283 for i, arg := range args {
1284 if arg, ok = expandSrcDir(arg, di.Dir); !ok {
1285 return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg)
1286 }
1287 args[i] = arg
1288 }
1289
1290 switch verb {
1291 case "CFLAGS", "CPPFLAGS", "CXXFLAGS", "FFLAGS", "LDFLAGS":
1292
1293 ctxt.makePathsAbsolute(args, di.Dir)
1294 }
1295
1296 switch verb {
1297 case "CFLAGS":
1298 di.CgoCFLAGS = append(di.CgoCFLAGS, args...)
1299 case "CPPFLAGS":
1300 di.CgoCPPFLAGS = append(di.CgoCPPFLAGS, args...)
1301 case "CXXFLAGS":
1302 di.CgoCXXFLAGS = append(di.CgoCXXFLAGS, args...)
1303 case "FFLAGS":
1304 di.CgoFFLAGS = append(di.CgoFFLAGS, args...)
1305 case "LDFLAGS":
1306 di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...)
1307 case "pkg-config":
1308 di.CgoPkgConfig = append(di.CgoPkgConfig, args...)
1309 default:
1310 return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig)
1311 }
1312 }
1313 return nil
1314 }
1315
1316
1317
1318 func expandSrcDir(str string, srcdir string) (string, bool) {
1319
1320
1321
1322 srcdir = filepath.ToSlash(srcdir)
1323
1324 chunks := strings.Split(str, "${SRCDIR}")
1325 if len(chunks) < 2 {
1326 return str, safeCgoName(str)
1327 }
1328 ok := true
1329 for _, chunk := range chunks {
1330 ok = ok && (chunk == "" || safeCgoName(chunk))
1331 }
1332 ok = ok && (srcdir == "" || safeCgoName(srcdir))
1333 res := strings.Join(chunks, srcdir)
1334 return res, ok && res != ""
1335 }
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348 func (ctxt *Context) makePathsAbsolute(args []string, srcDir string) {
1349 nextPath := false
1350 for i, arg := range args {
1351 if nextPath {
1352 if !filepath.IsAbs(arg) {
1353 args[i] = filepath.Join(srcDir, arg)
1354 }
1355 nextPath = false
1356 } else if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") {
1357 if len(arg) == 2 {
1358 nextPath = true
1359 } else {
1360 if !filepath.IsAbs(arg[2:]) {
1361 args[i] = arg[:2] + filepath.Join(srcDir, arg[2:])
1362 }
1363 }
1364 }
1365 }
1366 }
1367
1368
1369
1370
1371
1372
1373 const safeString = "+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:$@% "
1374
1375 func safeCgoName(s string) bool {
1376 if s == "" {
1377 return false
1378 }
1379 for i := 0; i < len(s); i++ {
1380 if c := s[i]; c < utf8.RuneSelf && strings.IndexByte(safeString, c) < 0 {
1381 return false
1382 }
1383 }
1384 return true
1385 }
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403 func splitQuoted(s string) (r []string, err error) {
1404 var args []string
1405 arg := make([]rune, len(s))
1406 escaped := false
1407 quoted := false
1408 quote := '\x00'
1409 i := 0
1410 for _, rune := range s {
1411 switch {
1412 case escaped:
1413 escaped = false
1414 case rune == '\\':
1415 escaped = true
1416 continue
1417 case quote != '\x00':
1418 if rune == quote {
1419 quote = '\x00'
1420 continue
1421 }
1422 case rune == '"' || rune == '\'':
1423 quoted = true
1424 quote = rune
1425 continue
1426 case unicode.IsSpace(rune):
1427 if quoted || i > 0 {
1428 quoted = false
1429 args = append(args, string(arg[:i]))
1430 i = 0
1431 }
1432 continue
1433 }
1434 arg[i] = rune
1435 i++
1436 }
1437 if quoted || i > 0 {
1438 args = append(args, string(arg[:i]))
1439 }
1440 if quote != 0 {
1441 err = errors.New("unclosed quote")
1442 } else if escaped {
1443 err = errors.New("unfinished escaping")
1444 }
1445 return args, err
1446 }
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460 func (ctxt *Context) match(name string, allTags map[string]bool) bool {
1461 if name == "" {
1462 if allTags != nil {
1463 allTags[name] = true
1464 }
1465 return false
1466 }
1467 if i := strings.Index(name, ","); i >= 0 {
1468
1469 ok1 := ctxt.match(name[:i], allTags)
1470 ok2 := ctxt.match(name[i+1:], allTags)
1471 return ok1 && ok2
1472 }
1473 if strings.HasPrefix(name, "!!") {
1474 return false
1475 }
1476 if strings.HasPrefix(name, "!") {
1477 return len(name) > 1 && !ctxt.match(name[1:], allTags)
1478 }
1479
1480 if allTags != nil {
1481 allTags[name] = true
1482 }
1483
1484
1485
1486 for _, c := range name {
1487 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
1488 return false
1489 }
1490 }
1491
1492
1493 if ctxt.CgoEnabled && name == "cgo" {
1494 return true
1495 }
1496 if name == ctxt.GOOS || name == ctxt.GOARCH || name == ctxt.Compiler {
1497 return true
1498 }
1499 if ctxt.GOOS == "android" && name == "linux" {
1500 return true
1501 }
1502
1503
1504 for _, tag := range ctxt.BuildTags {
1505 if tag == name {
1506 return true
1507 }
1508 }
1509 for _, tag := range ctxt.ReleaseTags {
1510 if tag == name {
1511 return true
1512 }
1513 }
1514
1515 return false
1516 }
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530 func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool {
1531 if dot := strings.Index(name, "."); dot != -1 {
1532 name = name[:dot]
1533 }
1534
1535
1536
1537
1538
1539
1540
1541
1542 i := strings.Index(name, "_")
1543 if i < 0 {
1544 return true
1545 }
1546 name = name[i:]
1547
1548 l := strings.Split(name, "_")
1549 if n := len(l); n > 0 && l[n-1] == "test" {
1550 l = l[:n-1]
1551 }
1552 n := len(l)
1553 if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
1554 if allTags != nil {
1555 allTags[l[n-2]] = true
1556 allTags[l[n-1]] = true
1557 }
1558 if l[n-1] != ctxt.GOARCH {
1559 return false
1560 }
1561 if ctxt.GOOS == "android" && l[n-2] == "linux" {
1562 return true
1563 }
1564 return l[n-2] == ctxt.GOOS
1565 }
1566 if n >= 1 && knownOS[l[n-1]] {
1567 if allTags != nil {
1568 allTags[l[n-1]] = true
1569 }
1570 if ctxt.GOOS == "android" && l[n-1] == "linux" {
1571 return true
1572 }
1573 return l[n-1] == ctxt.GOOS
1574 }
1575 if n >= 1 && knownArch[l[n-1]] {
1576 if allTags != nil {
1577 allTags[l[n-1]] = true
1578 }
1579 return l[n-1] == ctxt.GOARCH
1580 }
1581 return true
1582 }
1583
1584 var knownOS = make(map[string]bool)
1585 var knownArch = make(map[string]bool)
1586
1587 func init() {
1588 for _, v := range strings.Fields(goosList) {
1589 knownOS[v] = true
1590 }
1591 for _, v := range strings.Fields(goarchList) {
1592 knownArch[v] = true
1593 }
1594 }
1595
1596
1597 var ToolDir = filepath.Join(runtime.GOROOT(), "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH)
1598
1599
1600
1601 func IsLocalImport(path string) bool {
1602 return path == "." || path == ".." ||
1603 strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
1604 }
1605
1606
1607
1608
1609
1610
1611 func ArchChar(goarch string) (string, error) {
1612 return "?", errors.New("architecture letter no longer used")
1613 }
1614
View as plain text