github.com/aca02djr/gb@v0.4.1/importer/importer.go (about)

     1  package importer
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	pathpkg "path"
     9  	"path/filepath"
    10  	"runtime"
    11  	"strings"
    12  	"unicode"
    13  )
    14  
    15  type Context struct {
    16  	GOOS       string // target architecture
    17  	GOARCH     string // target operating system
    18  	CgoEnabled bool   // whether cgo can be used
    19  
    20  	// The build and release tags specify build constraints
    21  	// that should be considered satisfied when processing +build lines.
    22  	// Clients creating a new context may customize BuildTags, which
    23  	// defaults to empty, but it is usually an error to customize ReleaseTags,
    24  	// which defaults to the list of Go releases the current release is compatible with.
    25  	// In addition to the BuildTags and ReleaseTags, build constraints
    26  	// consider the values of GOARCH and GOOS as satisfied tags.
    27  	BuildTags   []string
    28  	ReleaseTags []string
    29  }
    30  
    31  type Importer struct {
    32  	*Context
    33  	Root string // root directory
    34  }
    35  
    36  func (i *Importer) Import(path string) (*Package, error) {
    37  	if path == "" {
    38  		return nil, fmt.Errorf("import %q: invalid import path", path)
    39  	}
    40  
    41  	if path == "." || path == ".." || strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../") {
    42  		return nil, fmt.Errorf("import %q: relative import not supported", path)
    43  	}
    44  
    45  	if strings.HasPrefix(path, "/") {
    46  		return nil, fmt.Errorf("import %q: cannot import absolute path", path)
    47  	}
    48  
    49  	p := &Package{
    50  		importer:   i,
    51  		ImportPath: path,
    52  		Standard:   i.Root == runtime.GOROOT(),
    53  	}
    54  	// if this is the stdlib, then search vendor first.
    55  	// this isn't real vendor support, just enough to make net/http compile.
    56  	if p.Standard {
    57  		path := pathpkg.Join("vendor", path)
    58  		dir := filepath.Join(i.Root, "src", filepath.FromSlash(path))
    59  		fi, err := os.Stat(dir)
    60  		if err == nil && fi.IsDir() {
    61  			p.Dir = dir
    62  			p.Root = i.Root
    63  			p.ImportPath = path
    64  			p.SrcRoot = filepath.Join(p.Root, "src")
    65  			err = loadPackage(p)
    66  			return p, err
    67  		}
    68  	}
    69  
    70  	dir := filepath.Join(i.Root, "src", filepath.FromSlash(path))
    71  	fi, err := os.Stat(dir)
    72  	if err == nil {
    73  		if fi.IsDir() {
    74  			p.Dir = dir
    75  			p.Root = i.Root
    76  			p.SrcRoot = filepath.Join(p.Root, "src")
    77  			err = loadPackage(p)
    78  			return p, err
    79  		}
    80  		err = fmt.Errorf("import %q: not a directory", path)
    81  	}
    82  	return nil, err
    83  }
    84  
    85  // matchFile determines whether the file with the given name in the given directory
    86  // should be included in the package being constructed.
    87  // It returns the data read from the file.
    88  // If allTags is non-nil, matchFile records any encountered build tag
    89  // by setting allTags[tag] = true.
    90  func (i *Importer) matchFile(path string, allTags map[string]bool) (match bool, data []byte, err error) {
    91  	name := filepath.Base(path)
    92  	if name[0] == '_' || name[0] == '.' {
    93  		return
    94  	}
    95  
    96  	if !goodOSArchFile(i.GOOS, i.GOARCH, name, allTags) {
    97  		return
    98  	}
    99  
   100  	read := func(path string, fn func(r io.Reader) ([]byte, error)) ([]byte, error) {
   101  		f, err := os.Open(path)
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  		defer f.Close()
   106  		data, err := fn(f)
   107  		if err != nil {
   108  			err = fmt.Errorf("read %s: %v", path, err)
   109  		}
   110  		return data, err
   111  	}
   112  
   113  	switch filepath.Ext(name) {
   114  	case ".go":
   115  		data, err = read(path, readImports)
   116  		if err != nil {
   117  			return
   118  		}
   119  		// Look for +build comments to accept or reject the file.
   120  		if !i.shouldBuild(data, allTags) {
   121  			return
   122  		}
   123  
   124  		match = true
   125  		return
   126  
   127  	case ".c", ".cc", ".cxx", ".cpp", ".m", ".s", ".h", ".hh", ".hpp", ".hxx", ".S", ".swig", ".swigcxx":
   128  		// tentatively okay - read to make sure
   129  		data, err = read(path, readComments)
   130  		if err != nil {
   131  			return
   132  		}
   133  		// Look for +build comments to accept or reject the file.
   134  		if !i.shouldBuild(data, allTags) {
   135  			return
   136  		}
   137  
   138  		match = true
   139  		return
   140  
   141  	case ".syso":
   142  		// binary, no reading
   143  		match = true
   144  		return
   145  	default:
   146  		// skip
   147  		return
   148  	}
   149  }
   150  
   151  // shouldBuild reports whether it is okay to use this file,
   152  // The rule is that in the file's leading run of // comments
   153  // and blank lines, which must be followed by a blank line
   154  // (to avoid including a Go package clause doc comment),
   155  // lines beginning with '// +build' are taken as build directives.
   156  //
   157  // The file is accepted only if each such line lists something
   158  // matching the file.  For example:
   159  //
   160  //      // +build windows linux
   161  //
   162  // marks the file as applicable only on Windows and Linux.
   163  //
   164  func (i *Importer) shouldBuild(content []byte, allTags map[string]bool) bool {
   165  	// Pass 1. Identify leading run of // comments and blank lines,
   166  	// which must be followed by a blank line.
   167  	end := 0
   168  	p := content
   169  	for len(p) > 0 {
   170  		line := p
   171  		if i := bytes.IndexByte(line, '\n'); i >= 0 {
   172  			line, p = line[:i], p[i+1:]
   173  		} else {
   174  			p = p[len(p):]
   175  		}
   176  		line = bytes.TrimSpace(line)
   177  		if len(line) == 0 { // Blank line
   178  			end = len(content) - len(p)
   179  			continue
   180  		}
   181  		if !bytes.HasPrefix(line, []byte{'/', '/'}) { // Not comment line
   182  			break
   183  		}
   184  	}
   185  	content = content[:end]
   186  
   187  	// Pass 2.  Process each line in the run.
   188  	p = content
   189  	allok := true
   190  	for len(p) > 0 {
   191  		line := p
   192  		if i := bytes.IndexByte(line, '\n'); i >= 0 {
   193  			line, p = line[:i], p[i+1:]
   194  		} else {
   195  			p = p[len(p):]
   196  		}
   197  		line = bytes.TrimSpace(line)
   198  		if bytes.HasPrefix(line, []byte{'/', '/'}) {
   199  			line = bytes.TrimSpace(line[2:])
   200  			if len(line) > 0 && line[0] == '+' {
   201  				// Looks like a comment +line.
   202  				f := strings.Fields(string(line))
   203  				if f[0] == "+build" {
   204  					ok := false
   205  					for _, tok := range f[1:] {
   206  						if i.match(tok, allTags) {
   207  							ok = true
   208  						}
   209  					}
   210  					if !ok {
   211  						allok = false
   212  					}
   213  				}
   214  			}
   215  		}
   216  	}
   217  
   218  	return allok
   219  }
   220  
   221  // match reports whether the name is one of:
   222  //
   223  //      $GOOS
   224  //      $GOARCH
   225  //      cgo (if cgo is enabled)
   226  //      !cgo (if cgo is disabled)
   227  //      ctxt.Compiler
   228  //      !ctxt.Compiler
   229  //      tag (if tag is listed in ctxt.BuildTags or ctxt.ReleaseTags)
   230  //      !tag (if tag is not listed in ctxt.BuildTags or ctxt.ReleaseTags)
   231  //      a comma-separated list of any of these
   232  //
   233  func (i *Importer) match(name string, allTags map[string]bool) bool {
   234  	if name == "" {
   235  		if allTags != nil {
   236  			allTags[name] = true
   237  		}
   238  		return false
   239  	}
   240  	if n := strings.Index(name, ","); n >= 0 {
   241  		// comma-separated list
   242  		ok1 := i.match(name[:n], allTags)
   243  		ok2 := i.match(name[n+1:], allTags)
   244  		return ok1 && ok2
   245  	}
   246  	if strings.HasPrefix(name, "!!") { // bad syntax, reject always
   247  		return false
   248  	}
   249  	if strings.HasPrefix(name, "!") { // negation
   250  		return len(name) > 1 && !i.match(name[1:], allTags)
   251  	}
   252  
   253  	if allTags != nil {
   254  		allTags[name] = true
   255  	}
   256  
   257  	// Tags must be letters, digits, underscores or dots.
   258  	// Unlike in Go identifiers, all digits are fine (e.g., "386").
   259  	for _, c := range name {
   260  		if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
   261  			return false
   262  		}
   263  	}
   264  
   265  	// special tags
   266  	if i.CgoEnabled && name == "cgo" {
   267  		return true
   268  	}
   269  	if name == i.GOOS || name == i.GOARCH || name == runtime.Compiler {
   270  		return true
   271  	}
   272  	if i.GOOS == "android" && name == "linux" {
   273  		return true
   274  	}
   275  
   276  	// other tags
   277  	for _, tag := range i.BuildTags {
   278  		if tag == name {
   279  			return true
   280  		}
   281  	}
   282  	for _, tag := range i.ReleaseTags {
   283  		if tag == name {
   284  			return true
   285  		}
   286  	}
   287  
   288  	return false
   289  }