OSDN Git Service

2012-03-10 Tobias Burnus <burnus@net-b.de>
[pf3gnuchains/gcc-fork.git] / libgo / go / go / build / dir.go
1 // Copyright 2011 The Go Authors.  All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package build
6
7 import (
8         "bytes"
9         "errors"
10         "fmt"
11         "go/ast"
12         "go/parser"
13         "go/token"
14         "io/ioutil"
15         "log"
16         "os"
17         "path"
18         "path/filepath"
19         "runtime"
20         "sort"
21         "strconv"
22         "strings"
23         "unicode"
24 )
25
26 // A Context specifies the supporting context for a build.
27 type Context struct {
28         GOARCH     string   // target architecture
29         GOOS       string   // target operating system
30         CgoEnabled bool     // whether cgo can be used
31         BuildTags  []string // additional tags to recognize in +build lines
32
33         // By default, ScanDir uses the operating system's
34         // file system calls to read directories and files.
35         // Callers can override those calls to provide other
36         // ways to read data by setting ReadDir and ReadFile.
37         // ScanDir does not make any assumptions about the
38         // format of the strings dir and file: they can be
39         // slash-separated, backslash-separated, even URLs.
40
41         // ReadDir returns a slice of os.FileInfo, sorted by Name,
42         // describing the content of the named directory.
43         // The dir argument is the argument to ScanDir.
44         // If ReadDir is nil, ScanDir uses io.ReadDir.
45         ReadDir func(dir string) (fi []os.FileInfo, err error)
46
47         // ReadFile returns the content of the file named file
48         // in the directory named dir.  The dir argument is the
49         // argument to ScanDir, and the file argument is the
50         // Name field from an os.FileInfo returned by ReadDir.
51         // The returned path is the full name of the file, to be
52         // used in error messages.
53         //
54         // If ReadFile is nil, ScanDir uses filepath.Join(dir, file)
55         // as the path and ioutil.ReadFile to read the data.
56         ReadFile func(dir, file string) (path string, content []byte, err error)
57 }
58
59 func (ctxt *Context) readDir(dir string) ([]os.FileInfo, error) {
60         if f := ctxt.ReadDir; f != nil {
61                 return f(dir)
62         }
63         return ioutil.ReadDir(dir)
64 }
65
66 func (ctxt *Context) readFile(dir, file string) (string, []byte, error) {
67         if f := ctxt.ReadFile; f != nil {
68                 return f(dir, file)
69         }
70         p := filepath.Join(dir, file)
71         content, err := ioutil.ReadFile(p)
72         return p, content, err
73 }
74
75 // The DefaultContext is the default Context for builds.
76 // It uses the GOARCH and GOOS environment variables
77 // if set, or else the compiled code's GOARCH and GOOS.
78 var DefaultContext Context = defaultContext()
79
80 var cgoEnabled = map[string]bool{
81         "darwin/386":    true,
82         "darwin/amd64":  true,
83         "linux/386":     true,
84         "linux/amd64":   true,
85         "freebsd/386":   true,
86         "freebsd/amd64": true,
87         "windows/386":   true,
88         "windows/amd64": true,
89 }
90
91 func defaultContext() Context {
92         var c Context
93
94         c.GOARCH = envOr("GOARCH", runtime.GOARCH)
95         c.GOOS = envOr("GOOS", runtime.GOOS)
96
97         s := os.Getenv("CGO_ENABLED")
98         switch s {
99         case "1":
100                 c.CgoEnabled = true
101         case "0":
102                 c.CgoEnabled = false
103         default:
104                 c.CgoEnabled = cgoEnabled[c.GOOS+"/"+c.GOARCH]
105         }
106
107         return c
108 }
109
110 func envOr(name, def string) string {
111         s := os.Getenv(name)
112         if s == "" {
113                 return def
114         }
115         return s
116 }
117
118 type DirInfo struct {
119         Package        string                      // Name of package in dir
120         PackageComment *ast.CommentGroup           // Package comments from GoFiles
121         ImportPath     string                      // Import path of package in dir
122         Imports        []string                    // All packages imported by GoFiles
123         ImportPos      map[string][]token.Position // Source code location of imports
124
125         // Source files
126         GoFiles  []string // .go files in dir (excluding CgoFiles, TestGoFiles, XTestGoFiles)
127         HFiles   []string // .h files in dir
128         CFiles   []string // .c files in dir
129         SFiles   []string // .s (and, when using cgo, .S files in dir)
130         CgoFiles []string // .go files that import "C"
131
132         // Cgo directives
133         CgoPkgConfig []string // Cgo pkg-config directives
134         CgoCFLAGS    []string // Cgo CFLAGS directives
135         CgoLDFLAGS   []string // Cgo LDFLAGS directives
136
137         // Test information
138         TestGoFiles   []string // _test.go files in package
139         XTestGoFiles  []string // _test.go files outside package
140         TestImports   []string // All packages imported by (X)TestGoFiles
141         TestImportPos map[string][]token.Position
142 }
143
144 func (d *DirInfo) IsCommand() bool {
145         // TODO(rsc): This is at least a little bogus.
146         return d.Package == "main"
147 }
148
149 // ScanDir calls DefaultContext.ScanDir.
150 func ScanDir(dir string) (info *DirInfo, err error) {
151         return DefaultContext.ScanDir(dir)
152 }
153
154 // TODO(rsc): Move this comment to a more appropriate place.
155
156 // ScanDir returns a structure with details about the Go package
157 // found in the given directory.
158 //
159 // Most .go, .c, .h, and .s files in the directory are considered part
160 // of the package.  The exceptions are:
161 //
162 //      - .go files in package main (unless no other package is found)
163 //      - .go files in package documentation
164 //      - files starting with _ or .
165 //      - files with build constraints not satisfied by the context
166 //
167 // Build Constraints
168 //
169 // A build constraint is a line comment beginning with the directive +build
170 // that lists the conditions under which a file should be included in the package.
171 // Constraints may appear in any kind of source file (not just Go), but
172 // they must be appear near the top of the file, preceded
173 // only by blank lines and other line comments.
174 //
175 // A build constraint is evaluated as the OR of space-separated options;
176 // each option evaluates as the AND of its comma-separated terms;
177 // and each term is an alphanumeric word or, preceded by !, its negation.
178 // That is, the build constraint:
179 //
180 //      // +build linux,386 darwin,!cgo
181 //
182 // corresponds to the boolean formula:
183 //
184 //      (linux AND 386) OR (darwin AND (NOT cgo))
185 //
186 // During a particular build, the following words are satisfied:
187 //
188 //      - the target operating system, as spelled by runtime.GOOS
189 //      - the target architecture, as spelled by runtime.GOARCH
190 //      - "cgo", if ctxt.CgoEnabled is true
191 //      - any additional words listed in ctxt.BuildTags
192 //
193 // If a file's name, after stripping the extension and a possible _test suffix,
194 // matches *_GOOS, *_GOARCH, or *_GOOS_GOARCH for any known operating
195 // system and architecture values, then the file is considered to have an implicit
196 // build constraint requiring those terms.
197 //
198 // Examples
199 //
200 // To keep a file from being considered for the build:
201 //
202 //      // +build ignore
203 //
204 // (any other unsatisfied word will work as well, but ``ignore'' is conventional.)
205 //
206 // To build a file only when using cgo, and only on Linux and OS X:
207 //
208 //      // +build linux,cgo darwin,cgo
209 // 
210 // Such a file is usually paired with another file implementing the
211 // default functionality for other systems, which in this case would
212 // carry the constraint:
213 //
214 //      // +build !linux !darwin !cgo
215 //
216 // Naming a file dns_windows.go will cause it to be included only when
217 // building the package for Windows; similarly, math_386.s will be included
218 // only when building the package for 32-bit x86.
219 //
220 func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) {
221         dirs, err := ctxt.readDir(dir)
222         if err != nil {
223                 return nil, err
224         }
225
226         var Sfiles []string // files with ".S" (capital S)
227         var di DirInfo
228         imported := make(map[string][]token.Position)
229         testImported := make(map[string][]token.Position)
230         fset := token.NewFileSet()
231         for _, d := range dirs {
232                 if d.IsDir() {
233                         continue
234                 }
235                 name := d.Name()
236                 if strings.HasPrefix(name, "_") ||
237                         strings.HasPrefix(name, ".") {
238                         continue
239                 }
240                 if !ctxt.goodOSArchFile(name) {
241                         continue
242                 }
243
244                 ext := path.Ext(name)
245                 switch ext {
246                 case ".go", ".c", ".s", ".h", ".S":
247                         // tentatively okay
248                 default:
249                         // skip
250                         continue
251                 }
252
253                 // Look for +build comments to accept or reject the file.
254                 filename, data, err := ctxt.readFile(dir, name)
255                 if err != nil {
256                         return nil, err
257                 }
258                 if !ctxt.shouldBuild(data) {
259                         continue
260                 }
261
262                 // Going to save the file.  For non-Go files, can stop here.
263                 switch ext {
264                 case ".c":
265                         di.CFiles = append(di.CFiles, name)
266                         continue
267                 case ".h":
268                         di.HFiles = append(di.HFiles, name)
269                         continue
270                 case ".s":
271                         di.SFiles = append(di.SFiles, name)
272                         continue
273                 case ".S":
274                         Sfiles = append(Sfiles, name)
275                         continue
276                 }
277
278                 pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly|parser.ParseComments)
279                 if err != nil {
280                         return nil, err
281                 }
282
283                 pkg := string(pf.Name.Name)
284                 if pkg == "main" && di.Package != "" && di.Package != "main" {
285                         continue
286                 }
287                 if pkg == "documentation" {
288                         continue
289                 }
290
291                 isTest := strings.HasSuffix(name, "_test.go")
292                 if isTest && strings.HasSuffix(pkg, "_test") {
293                         pkg = pkg[:len(pkg)-len("_test")]
294                 }
295
296                 if pkg != di.Package && di.Package == "main" {
297                         // Found non-main package but was recording
298                         // information about package main.  Reset.
299                         di = DirInfo{}
300                 }
301                 if di.Package == "" {
302                         di.Package = pkg
303                 } else if pkg != di.Package {
304                         return nil, fmt.Errorf("%s: found packages %s and %s", dir, pkg, di.Package)
305                 }
306                 if pf.Doc != nil {
307                         if di.PackageComment != nil {
308                                 di.PackageComment.List = append(di.PackageComment.List, pf.Doc.List...)
309                         } else {
310                                 di.PackageComment = pf.Doc
311                         }
312                 }
313
314                 // Record imports and information about cgo.
315                 isCgo := false
316                 for _, decl := range pf.Decls {
317                         d, ok := decl.(*ast.GenDecl)
318                         if !ok {
319                                 continue
320                         }
321                         for _, dspec := range d.Specs {
322                                 spec, ok := dspec.(*ast.ImportSpec)
323                                 if !ok {
324                                         continue
325                                 }
326                                 quoted := string(spec.Path.Value)
327                                 path, err := strconv.Unquote(quoted)
328                                 if err != nil {
329                                         log.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted)
330                                 }
331                                 if isTest {
332                                         testImported[path] = append(testImported[path], fset.Position(spec.Pos()))
333                                 } else {
334                                         imported[path] = append(imported[path], fset.Position(spec.Pos()))
335                                 }
336                                 if path == "C" {
337                                         if isTest {
338                                                 return nil, fmt.Errorf("%s: use of cgo in test not supported", filename)
339                                         }
340                                         cg := spec.Doc
341                                         if cg == nil && len(d.Specs) == 1 {
342                                                 cg = d.Doc
343                                         }
344                                         if cg != nil {
345                                                 if err := ctxt.saveCgo(filename, &di, cg); err != nil {
346                                                         return nil, err
347                                                 }
348                                         }
349                                         isCgo = true
350                                 }
351                         }
352                 }
353                 if isCgo {
354                         if ctxt.CgoEnabled {
355                                 di.CgoFiles = append(di.CgoFiles, name)
356                         }
357                 } else if isTest {
358                         if pkg == string(pf.Name.Name) {
359                                 di.TestGoFiles = append(di.TestGoFiles, name)
360                         } else {
361                                 di.XTestGoFiles = append(di.XTestGoFiles, name)
362                         }
363                 } else {
364                         di.GoFiles = append(di.GoFiles, name)
365                 }
366         }
367         if di.Package == "" {
368                 return nil, fmt.Errorf("%s: no Go source files", dir)
369         }
370         di.Imports = make([]string, len(imported))
371         di.ImportPos = imported
372         i := 0
373         for p := range imported {
374                 di.Imports[i] = p
375                 i++
376         }
377         di.TestImports = make([]string, len(testImported))
378         di.TestImportPos = testImported
379         i = 0
380         for p := range testImported {
381                 di.TestImports[i] = p
382                 i++
383         }
384
385         // add the .S files only if we are using cgo
386         // (which means gcc will compile them).
387         // The standard assemblers expect .s files.
388         if len(di.CgoFiles) > 0 {
389                 di.SFiles = append(di.SFiles, Sfiles...)
390                 sort.Strings(di.SFiles)
391         }
392
393         // File name lists are sorted because ReadDir sorts.
394         sort.Strings(di.Imports)
395         sort.Strings(di.TestImports)
396         return &di, nil
397 }
398
399 var slashslash = []byte("//")
400
401 // shouldBuild reports whether it is okay to use this file,
402 // The rule is that in the file's leading run of // comments
403 // and blank lines, which must be followed by a blank line
404 // (to avoid including a Go package clause doc comment),
405 // lines beginning with '// +build' are taken as build directives.
406 //
407 // The file is accepted only if each such line lists something
408 // matching the file.  For example:
409 //
410 //      // +build windows linux
411 //
412 // marks the file as applicable only on Windows and Linux.
413 //
414 func (ctxt *Context) shouldBuild(content []byte) bool {
415         // Pass 1. Identify leading run of // comments and blank lines,
416         // which must be followed by a blank line.
417         end := 0
418         p := content
419         for len(p) > 0 {
420                 line := p
421                 if i := bytes.IndexByte(line, '\n'); i >= 0 {
422                         line, p = line[:i], p[i+1:]
423                 } else {
424                         p = p[len(p):]
425                 }
426                 line = bytes.TrimSpace(line)
427                 if len(line) == 0 { // Blank line
428                         end = cap(content) - cap(line) // &line[0] - &content[0]
429                         continue
430                 }
431                 if !bytes.HasPrefix(line, slashslash) { // Not comment line
432                         break
433                 }
434         }
435         content = content[:end]
436
437         // Pass 2.  Process each line in the run.
438         p = content
439         for len(p) > 0 {
440                 line := p
441                 if i := bytes.IndexByte(line, '\n'); i >= 0 {
442                         line, p = line[:i], p[i+1:]
443                 } else {
444                         p = p[len(p):]
445                 }
446                 line = bytes.TrimSpace(line)
447                 if bytes.HasPrefix(line, slashslash) {
448                         line = bytes.TrimSpace(line[len(slashslash):])
449                         if len(line) > 0 && line[0] == '+' {
450                                 // Looks like a comment +line.
451                                 f := strings.Fields(string(line))
452                                 if f[0] == "+build" {
453                                         ok := false
454                                         for _, tok := range f[1:] {
455                                                 if ctxt.match(tok) {
456                                                         ok = true
457                                                         break
458                                                 }
459                                         }
460                                         if !ok {
461                                                 return false // this one doesn't match
462                                         }
463                                 }
464                         }
465                 }
466         }
467         return true // everything matches
468 }
469
470 // saveCgo saves the information from the #cgo lines in the import "C" comment.
471 // These lines set CFLAGS and LDFLAGS and pkg-config directives that affect
472 // the way cgo's C code is built.
473 //
474 // TODO(rsc): This duplicates code in cgo.
475 // Once the dust settles, remove this code from cgo.
476 func (ctxt *Context) saveCgo(filename string, di *DirInfo, cg *ast.CommentGroup) error {
477         text := cg.Text()
478         for _, line := range strings.Split(text, "\n") {
479                 orig := line
480
481                 // Line is
482                 //      #cgo [GOOS/GOARCH...] LDFLAGS: stuff
483                 //
484                 line = strings.TrimSpace(line)
485                 if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') {
486                         continue
487                 }
488
489                 // Split at colon.
490                 line = strings.TrimSpace(line[4:])
491                 i := strings.Index(line, ":")
492                 if i < 0 {
493                         return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
494                 }
495                 line, argstr := line[:i], line[i+1:]
496
497                 // Parse GOOS/GOARCH stuff.
498                 f := strings.Fields(line)
499                 if len(f) < 1 {
500                         return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
501                 }
502
503                 cond, verb := f[:len(f)-1], f[len(f)-1]
504                 if len(cond) > 0 {
505                         ok := false
506                         for _, c := range cond {
507                                 if ctxt.match(c) {
508                                         ok = true
509                                         break
510                                 }
511                         }
512                         if !ok {
513                                 continue
514                         }
515                 }
516
517                 args, err := splitQuoted(argstr)
518                 if err != nil {
519                         return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
520                 }
521                 for _, arg := range args {
522                         if !safeName(arg) {
523                                 return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg)
524                         }
525                 }
526
527                 switch verb {
528                 case "CFLAGS":
529                         di.CgoCFLAGS = append(di.CgoCFLAGS, args...)
530                 case "LDFLAGS":
531                         di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...)
532                 case "pkg-config":
533                         di.CgoPkgConfig = append(di.CgoPkgConfig, args...)
534                 default:
535                         return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig)
536                 }
537         }
538         return nil
539 }
540
541 var safeBytes = []byte("+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:")
542
543 func safeName(s string) bool {
544         if s == "" {
545                 return false
546         }
547         for i := 0; i < len(s); i++ {
548                 if c := s[i]; c < 0x80 && bytes.IndexByte(safeBytes, c) < 0 {
549                         return false
550                 }
551         }
552         return true
553 }
554
555 // splitQuoted splits the string s around each instance of one or more consecutive
556 // white space characters while taking into account quotes and escaping, and
557 // returns an array of substrings of s or an empty list if s contains only white space.
558 // Single quotes and double quotes are recognized to prevent splitting within the
559 // quoted region, and are removed from the resulting substrings. If a quote in s
560 // isn't closed err will be set and r will have the unclosed argument as the
561 // last element.  The backslash is used for escaping.
562 //
563 // For example, the following string:
564 //
565 //     a b:"c d" 'e''f'  "g\""
566 //
567 // Would be parsed as:
568 //
569 //     []string{"a", "b:c d", "ef", `g"`}
570 //
571 func splitQuoted(s string) (r []string, err error) {
572         var args []string
573         arg := make([]rune, len(s))
574         escaped := false
575         quoted := false
576         quote := '\x00'
577         i := 0
578         for _, rune := range s {
579                 switch {
580                 case escaped:
581                         escaped = false
582                 case rune == '\\':
583                         escaped = true
584                         continue
585                 case quote != '\x00':
586                         if rune == quote {
587                                 quote = '\x00'
588                                 continue
589                         }
590                 case rune == '"' || rune == '\'':
591                         quoted = true
592                         quote = rune
593                         continue
594                 case unicode.IsSpace(rune):
595                         if quoted || i > 0 {
596                                 quoted = false
597                                 args = append(args, string(arg[:i]))
598                                 i = 0
599                         }
600                         continue
601                 }
602                 arg[i] = rune
603                 i++
604         }
605         if quoted || i > 0 {
606                 args = append(args, string(arg[:i]))
607         }
608         if quote != 0 {
609                 err = errors.New("unclosed quote")
610         } else if escaped {
611                 err = errors.New("unfinished escaping")
612         }
613         return args, err
614 }
615
616 // match returns true if the name is one of:
617 //
618 //      $GOOS
619 //      $GOARCH
620 //      cgo (if cgo is enabled)
621 //      !cgo (if cgo is disabled)
622 //      tag (if tag is listed in ctxt.BuildTags)
623 //      !tag (if tag is not listed in ctxt.BuildTags)
624 //      a slash-separated list of any of these
625 //
626 func (ctxt *Context) match(name string) bool {
627         if name == "" {
628                 return false
629         }
630         if i := strings.Index(name, ","); i >= 0 {
631                 // comma-separated list
632                 return ctxt.match(name[:i]) && ctxt.match(name[i+1:])
633         }
634         if strings.HasPrefix(name, "!!") { // bad syntax, reject always
635                 return false
636         }
637         if strings.HasPrefix(name, "!") { // negation
638                 return !ctxt.match(name[1:])
639         }
640
641         // Tags must be letters, digits, underscores.
642         // Unlike in Go identifiers, all digits is fine (e.g., "386").
643         for _, c := range name {
644                 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' {
645                         return false
646                 }
647         }
648
649         // special tags
650         if ctxt.CgoEnabled && name == "cgo" {
651                 return true
652         }
653         if name == ctxt.GOOS || name == ctxt.GOARCH {
654                 return true
655         }
656
657         // other tags
658         for _, tag := range ctxt.BuildTags {
659                 if tag == name {
660                         return true
661                 }
662         }
663
664         return false
665 }
666
667 // goodOSArchFile returns false if the name contains a $GOOS or $GOARCH
668 // suffix which does not match the current system.
669 // The recognized name formats are:
670 //
671 //     name_$(GOOS).*
672 //     name_$(GOARCH).*
673 //     name_$(GOOS)_$(GOARCH).*
674 //     name_$(GOOS)_test.*
675 //     name_$(GOARCH)_test.*
676 //     name_$(GOOS)_$(GOARCH)_test.*
677 //
678 func (ctxt *Context) goodOSArchFile(name string) bool {
679         if dot := strings.Index(name, "."); dot != -1 {
680                 name = name[:dot]
681         }
682         l := strings.Split(name, "_")
683         if n := len(l); n > 0 && l[n-1] == "test" {
684                 l = l[:n-1]
685         }
686         n := len(l)
687         if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
688                 return l[n-2] == ctxt.GOOS && l[n-1] == ctxt.GOARCH
689         }
690         if n >= 1 && knownOS[l[n-1]] {
691                 return l[n-1] == ctxt.GOOS
692         }
693         if n >= 1 && knownArch[l[n-1]] {
694                 return l[n-1] == ctxt.GOARCH
695         }
696         return true
697 }
698
699 var knownOS = make(map[string]bool)
700 var knownArch = make(map[string]bool)
701
702 func init() {
703         for _, v := range strings.Fields(goosList) {
704                 knownOS[v] = true
705         }
706         for _, v := range strings.Fields(goarchList) {
707                 knownArch[v] = true
708         }
709 }