github.com/sc0rp1us/gb@v0.4.1-0.20160319180011-4ba8cf1baa5a/importer/pkg.go (about)

     1  package importer
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"go/ast" // for build.Default
     7  	"go/parser"
     8  	"go/token"
     9  	"os"
    10  	"path/filepath"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  )
    15  
    16  var knownOS = map[string]bool{
    17  	"android":   true,
    18  	"darwin":    true,
    19  	"dragonfly": true,
    20  	"freebsd":   true,
    21  	"linux":     true,
    22  	"nacl":      true,
    23  	"netbsd":    true,
    24  	"openbsd":   true,
    25  	"plan9":     true,
    26  	"solaris":   true,
    27  	"windows":   true,
    28  }
    29  
    30  var knownArch = map[string]bool{
    31  	"386":      true,
    32  	"amd64":    true,
    33  	"amd64p32": true,
    34  	"arm":      true,
    35  	"arm64":    true,
    36  	"ppc64":    true,
    37  	"ppc64le":  true,
    38  	"mips":     true,
    39  	"mipsle":   true,
    40  	"mips64":   true,
    41  	"mips64le": true,
    42  }
    43  
    44  // goodOSArchFile returns false if the name contains a $GOOS or $GOARCH
    45  // suffix which does not match the current system.
    46  // The recognized name formats are:
    47  //
    48  //     name_$(GOOS).*
    49  //     name_$(GOARCH).*
    50  //     name_$(GOOS)_$(GOARCH).*
    51  //     name_$(GOOS)_test.*
    52  //     name_$(GOARCH)_test.*
    53  //     name_$(GOOS)_$(GOARCH)_test.*
    54  //
    55  // An exception: if GOOS=android, then files with GOOS=linux are also matched.
    56  func goodOSArchFile(goos, goarch, name string, allTags map[string]bool) bool {
    57  	// Before Go 1.4, a file called "linux.go" would be equivalent to having a
    58  	// build tag "linux" in that file. For Go 1.4 and beyond, we require this
    59  	// auto-tagging to apply only to files with a non-empty prefix, so
    60  	// "foo_linux.go" is tagged but "linux.go" is not. This allows new operating
    61  	// systems, such as android, to arrive without breaking existing code with
    62  	// innocuous source code in "android.go". The easiest fix: cut everything
    63  	// in the name before the initial _.
    64  	i := strings.Index(name, "_")
    65  	if i < 0 {
    66  		return true
    67  	}
    68  	name = name[i:] // ignore everything before first _
    69  
    70  	// strip extension
    71  	if dot := strings.Index(name, "."); dot != -1 {
    72  		name = name[:dot]
    73  	}
    74  
    75  	l := strings.Split(name, "_")
    76  	if n := len(l); n > 0 && l[n-1] == "test" {
    77  		l = l[:n-1]
    78  	}
    79  	n := len(l)
    80  	switch {
    81  	case n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]]:
    82  		if allTags != nil {
    83  			allTags[l[n-2]] = true
    84  			allTags[l[n-1]] = true
    85  		}
    86  		if l[n-1] != goarch {
    87  			return false
    88  		}
    89  		if goos == "android" && l[n-2] == "linux" {
    90  			return true
    91  		}
    92  		return l[n-2] == goos
    93  	case n >= 1 && knownOS[l[n-1]]:
    94  		if allTags != nil {
    95  			allTags[l[n-1]] = true
    96  		}
    97  		if goos == "android" && l[n-1] == "linux" {
    98  			return true
    99  		}
   100  		return l[n-1] == goos
   101  	case n >= 1 && knownArch[l[n-1]]:
   102  		if allTags != nil {
   103  			allTags[l[n-1]] = true
   104  		}
   105  		return l[n-1] == goarch
   106  	default:
   107  		return true
   108  	}
   109  }
   110  
   111  type byName []os.FileInfo
   112  
   113  func (x byName) Len() int           { return len(x) }
   114  func (x byName) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }
   115  func (x byName) Less(i, j int) bool { return x[i].Name() < x[j].Name() }
   116  
   117  func loadPackage(p *Package) error {
   118  	dir, err := os.Open(p.Dir)
   119  	if err != nil {
   120  		return fmt.Errorf("loadPackage: unable open directory: %v", err)
   121  	}
   122  	defer dir.Close()
   123  
   124  	dents, err := dir.Readdir(-1)
   125  	if err != nil {
   126  		return fmt.Errorf("loadPackage: unable read directory: %v", err)
   127  	}
   128  
   129  	var Sfiles []string // files with ".S" (capital S)
   130  	var firstFile string
   131  	imported := make(map[string][]token.Position)
   132  	testImported := make(map[string][]token.Position)
   133  	xTestImported := make(map[string][]token.Position)
   134  	allTags := make(map[string]bool)
   135  	fset := token.NewFileSet()
   136  
   137  	// cmd/gb expects file names to be sorted ... this seems artificial
   138  	sort.Sort(byName(dents))
   139  	for _, fi := range dents {
   140  		if fi.IsDir() {
   141  			continue
   142  		}
   143  
   144  		name := fi.Name()
   145  		path := filepath.Join(p.Dir, name)
   146  		match, data, err := p.matchFile(path, allTags)
   147  		if err != nil {
   148  			return err
   149  		}
   150  		ext := filepath.Ext(name)
   151  		if !match {
   152  			if ext == ".go" {
   153  				p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
   154  			}
   155  			continue
   156  		}
   157  
   158  		switch ext {
   159  		case ".c":
   160  			p.CFiles = append(p.CFiles, name)
   161  		case ".cc", ".cpp", ".cxx":
   162  			p.CXXFiles = append(p.CXXFiles, name)
   163  		case ".m":
   164  			p.MFiles = append(p.MFiles, name)
   165  		case ".h", ".hh", ".hpp", ".hxx":
   166  			p.HFiles = append(p.HFiles, name)
   167  		case ".s":
   168  			p.SFiles = append(p.SFiles, name)
   169  		case ".S":
   170  			Sfiles = append(Sfiles, name)
   171  		case ".swig":
   172  			p.SwigFiles = append(p.SwigFiles, name)
   173  		case ".swigcxx":
   174  			p.SwigCXXFiles = append(p.SwigCXXFiles, name)
   175  		case ".syso":
   176  			// binary objects to add to package archive
   177  			// Likely of the form foo_windows.syso, but
   178  			// the name was vetted above with goodOSArchFile.
   179  			p.SysoFiles = append(p.SysoFiles, name)
   180  		default:
   181  			pf, err := parser.ParseFile(fset, path, data, parser.ImportsOnly|parser.ParseComments)
   182  			if err != nil {
   183  				return err
   184  			}
   185  
   186  			pkg := pf.Name.Name
   187  			if pkg == "documentation" {
   188  				p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
   189  				continue
   190  			}
   191  
   192  			isTest := strings.HasSuffix(name, "_test.go")
   193  			isXTest := false
   194  			if isTest && strings.HasSuffix(pkg, "_test") {
   195  				isXTest = true
   196  				pkg = pkg[:len(pkg)-len("_test")]
   197  			}
   198  
   199  			if p.Name == "" {
   200  				p.Name = pkg
   201  				firstFile = name
   202  			} else if pkg != p.Name {
   203  				return &MultiplePackageError{
   204  					Dir:      p.Dir,
   205  					Packages: []string{p.Name, pkg},
   206  					Files:    []string{firstFile, name},
   207  				}
   208  			}
   209  			// Record imports and information about cgo.
   210  			isCgo := false
   211  			for _, decl := range pf.Decls {
   212  				d, ok := decl.(*ast.GenDecl)
   213  				if !ok {
   214  					continue
   215  				}
   216  				for _, dspec := range d.Specs {
   217  					spec, ok := dspec.(*ast.ImportSpec)
   218  					if !ok {
   219  						continue
   220  					}
   221  					quoted := spec.Path.Value
   222  					path, err := strconv.Unquote(quoted)
   223  					if err != nil {
   224  						return fmt.Errorf("%s: parser returned invalid quoted string: <%s>", path, quoted)
   225  					}
   226  					if isXTest {
   227  						xTestImported[path] = append(xTestImported[path], fset.Position(spec.Pos()))
   228  					} else if isTest {
   229  						testImported[path] = append(testImported[path], fset.Position(spec.Pos()))
   230  					} else {
   231  						imported[path] = append(imported[path], fset.Position(spec.Pos()))
   232  					}
   233  					if path == "C" {
   234  						if isTest {
   235  							return fmt.Errorf("use of cgo in test %s not supported", path)
   236  						}
   237  						cg := spec.Doc
   238  						if cg == nil && len(d.Specs) == 1 {
   239  							cg = d.Doc
   240  						}
   241  						if cg != nil {
   242  							if err := saveCgo(p, path, cg); err != nil {
   243  								return err
   244  							}
   245  						}
   246  						isCgo = true
   247  					}
   248  				}
   249  			}
   250  			switch {
   251  			case isCgo:
   252  				allTags["cgo"] = true
   253  				if p.importer.(*Importer).CgoEnabled {
   254  					p.CgoFiles = append(p.CgoFiles, name)
   255  				} else {
   256  					p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
   257  				}
   258  			case isXTest:
   259  				p.XTestGoFiles = append(p.XTestGoFiles, name)
   260  			case isTest:
   261  				p.TestGoFiles = append(p.TestGoFiles, name)
   262  			default:
   263  				p.GoFiles = append(p.GoFiles, name)
   264  			}
   265  		}
   266  	}
   267  	if len(p.GoFiles)+len(p.CgoFiles)+len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
   268  		return &NoGoError{p.Dir}
   269  	}
   270  
   271  	for tag := range allTags {
   272  		p.AllTags = append(p.AllTags, tag)
   273  	}
   274  	sort.Strings(p.AllTags)
   275  
   276  	p.Imports, p.ImportPos = cleanImports(imported)
   277  	p.TestImports, p.TestImportPos = cleanImports(testImported)
   278  	p.XTestImports, p.XTestImportPos = cleanImports(xTestImported)
   279  
   280  	// add the .S files only if we are using cgo
   281  	// (which means gcc will compile them).
   282  	// The standard assemblers expect .s files.
   283  	if len(p.CgoFiles) > 0 {
   284  		p.SFiles = append(p.SFiles, Sfiles...)
   285  		sort.Strings(p.SFiles)
   286  	}
   287  	return nil
   288  }
   289  
   290  // saveCgo saves the information from the #cgo lines in the import "C" comment.
   291  // These lines set CFLAGS, CPPFLAGS, CXXFLAGS and LDFLAGS and pkg-config directives
   292  // that affect the way cgo's C code is built.
   293  func saveCgo(di *Package, filename string, cg *ast.CommentGroup) error {
   294  	r := strings.NewReader(cg.Text())
   295  	sc := bufio.NewScanner(r)
   296  	for sc.Scan() {
   297  		line := sc.Text()
   298  
   299  		// Line is
   300  		//	#cgo [GOOS/GOARCH...] LDFLAGS: stuff
   301  		//
   302  		line = strings.TrimSpace(line)
   303  		if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') {
   304  			continue
   305  		}
   306  
   307  		// Split at colon.
   308  		line = strings.TrimSpace(line[4:])
   309  		i := strings.Index(line, ":")
   310  		if i < 0 {
   311  			return fmt.Errorf("%s: invalid #cgo line: %s", filename, sc.Text())
   312  		}
   313  		line, argstr := line[:i], line[i+1:]
   314  
   315  		// Parse GOOS/GOARCH stuff.
   316  		f := strings.Fields(line)
   317  		if len(f) < 1 {
   318  			return fmt.Errorf("%s: invalid #cgo line: %s", filename, sc.Text())
   319  		}
   320  
   321  		cond, verb := f[:len(f)-1], f[len(f)-1]
   322  		if len(cond) > 0 {
   323  			ok := false
   324  			for _, c := range cond {
   325  				if di.match(c, nil) {
   326  					ok = true
   327  					break
   328  				}
   329  			}
   330  			if !ok {
   331  				continue
   332  			}
   333  		}
   334  
   335  		args, err := splitQuoted(argstr)
   336  		if err != nil {
   337  			return fmt.Errorf("%s: invalid #cgo line: %s", filename, sc.Text())
   338  		}
   339  		for i, arg := range args {
   340  			arg, ok := expandSrcDir(arg, di.Dir)
   341  			if !ok {
   342  				return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg)
   343  			}
   344  			args[i] = arg
   345  		}
   346  
   347  		switch verb {
   348  		case "CFLAGS":
   349  			di.CgoCFLAGS = append(di.CgoCFLAGS, args...)
   350  		case "CPPFLAGS":
   351  			di.CgoCPPFLAGS = append(di.CgoCPPFLAGS, args...)
   352  		case "CXXFLAGS":
   353  			di.CgoCXXFLAGS = append(di.CgoCXXFLAGS, args...)
   354  		case "LDFLAGS":
   355  			di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...)
   356  		case "pkg-config":
   357  			di.CgoPkgConfig = append(di.CgoPkgConfig, args...)
   358  		default:
   359  			return fmt.Errorf("%s: invalid #cgo verb: %s", filename, sc.Text())
   360  		}
   361  	}
   362  	return sc.Err()
   363  }
   364  
   365  func cleanImports(m map[string][]token.Position) ([]string, map[string][]token.Position) {
   366  	if len(m) == 0 {
   367  		return nil, nil
   368  	}
   369  	all := make([]string, 0, len(m))
   370  	for path := range m {
   371  		all = append(all, path)
   372  	}
   373  	sort.Strings(all)
   374  	return all, m
   375  }