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