github.com/seh/gb@v0.4.4-0.20160724065125-065d2b2b1ba1/internal/importer/pkg.go (about)

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