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