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