github.com/goplus/gogen@v1.16.0/packages/imp.go (about)

     1  /*
     2   Copyright 2022 The GoPlus Authors (goplus.org)
     3   Licensed under the Apache License, Version 2.0 (the "License");
     4   you may not use this file except in compliance with the License.
     5   You may obtain a copy of the License at
     6       http://www.apache.org/licenses/LICENSE-2.0
     7   Unless required by applicable law or agreed to in writing, software
     8   distributed under the License is distributed on an "AS IS" BASIS,
     9   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    10   See the License for the specific language governing permissions and
    11   limitations under the License.
    12  */
    13  
    14  package packages
    15  
    16  import (
    17  	"bytes"
    18  	"errors"
    19  	"go/token"
    20  	"go/types"
    21  	"io"
    22  	"os"
    23  	"os/exec"
    24  	"sync"
    25  	"sync/atomic"
    26  
    27  	"golang.org/x/tools/go/gcexportdata"
    28  )
    29  
    30  // ----------------------------------------------------------------------------
    31  
    32  // Cache represents a cache for the importer.
    33  type Cache interface {
    34  	Find(dir, pkgPath string) (f io.ReadCloser, err error)
    35  }
    36  
    37  // Importer represents a Go package importer.
    38  type Importer struct {
    39  	loaded map[string]*types.Package
    40  	fset   *token.FileSet
    41  	dir    string
    42  	m      sync.RWMutex
    43  	cache  Cache
    44  }
    45  
    46  // NewImporter creates an Importer object that meets types.ImporterFrom and types.Importer interface.
    47  func NewImporter(fset *token.FileSet, workDir ...string) *Importer {
    48  	dir := ""
    49  	if len(workDir) > 0 {
    50  		dir = workDir[0]
    51  	}
    52  	if fset == nil {
    53  		fset = token.NewFileSet()
    54  	}
    55  	loaded := make(map[string]*types.Package)
    56  	loaded["unsafe"] = types.Unsafe
    57  	return &Importer{loaded: loaded, fset: fset, dir: dir}
    58  }
    59  
    60  // SetCache sets an optional cache for the importer.
    61  func (p *Importer) SetCache(cache Cache) {
    62  	p.cache = cache
    63  }
    64  
    65  // Cache returns the cache of the importer.
    66  func (p *Importer) Cache() Cache {
    67  	return p.cache
    68  }
    69  
    70  // Import returns the imported package for the given import path.
    71  func (p *Importer) Import(pkgPath string) (pkg *types.Package, err error) {
    72  	return p.ImportFrom(pkgPath, p.dir, 0)
    73  }
    74  
    75  // ImportFrom returns the imported package for the given import
    76  // path when imported by a package file located in dir.
    77  // If the import failed, besides returning an error, ImportFrom
    78  // is encouraged to cache and return a package anyway, if one
    79  // was created. This will reduce package inconsistencies and
    80  // follow-on type checker errors due to the missing package.
    81  // The mode value must be 0; it is reserved for future use.
    82  // Two calls to ImportFrom with the same path and dir must
    83  // return the same package.
    84  func (p *Importer) ImportFrom(pkgPath, dir string, mode types.ImportMode) (*types.Package, error) {
    85  	p.m.RLock()
    86  	if ret, ok := p.loaded[pkgPath]; ok && ret.Complete() {
    87  		p.m.RUnlock()
    88  		return ret, nil
    89  	}
    90  	p.m.RUnlock()
    91  	f, err := p.findExport(dir, pkgPath)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	defer f.Close()
    96  	return p.loadByExport(f, pkgPath)
    97  }
    98  
    99  func (p *Importer) loadByExport(f io.ReadCloser, pkgPath string) (ret *types.Package, err error) {
   100  	r, err := gcexportdata.NewReader(f)
   101  	if err == nil {
   102  		p.m.Lock() // use mutex because Import should be multi-thread safe
   103  		defer p.m.Unlock()
   104  		ret, err = gcexportdata.Read(r, p.fset, p.loaded, pkgPath)
   105  	}
   106  	return
   107  }
   108  
   109  // ----------------------------------------------------------------------------
   110  
   111  // findExport lookups export file (.a) of a package by its pkgPath.
   112  func (p *Importer) findExport(dir, pkgPath string) (f io.ReadCloser, err error) {
   113  	if c := p.cache; c != nil {
   114  		return c.Find(dir, pkgPath)
   115  	}
   116  	atomic.AddInt32(&nlist, 1)
   117  	data, err := golistExport(dir, pkgPath)
   118  	if err != nil {
   119  		return
   120  	}
   121  	expfile := string(bytes.TrimSuffix(data, []byte{'\n'}))
   122  	return os.Open(expfile)
   123  }
   124  
   125  func golistExport(dir, pkgPath string) (ret []byte, err error) {
   126  	var stdout, stderr bytes.Buffer
   127  	cmd := exec.Command("go", "list", "-f={{.Export}}", "-export", pkgPath)
   128  	cmd.Stdout = &stdout
   129  	cmd.Stderr = &stderr
   130  	cmd.Dir = dir
   131  	err = cmd.Run()
   132  	if err == nil {
   133  		ret = stdout.Bytes()
   134  	} else if stderr.Len() > 0 {
   135  		err = errors.New(stderr.String())
   136  	}
   137  	return
   138  }
   139  
   140  var (
   141  	nlist int32
   142  )
   143  
   144  // ListTimes returns the number of times of calling `go list`.
   145  func ListTimes() int {
   146  	return int(atomic.LoadInt32(&nlist))
   147  }
   148  
   149  // ----------------------------------------------------------------------------