github.com/goplus/gox@v1.14.13-0.20240308130321-6ff7f61cfae8/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  	"os"
    22  	"os/exec"
    23  	"sync"
    24  
    25  	"golang.org/x/tools/go/gcexportdata"
    26  )
    27  
    28  // ----------------------------------------------------------------------------
    29  
    30  type Importer struct {
    31  	loaded map[string]*types.Package
    32  	fset   *token.FileSet
    33  	dir    string
    34  	m      sync.RWMutex
    35  }
    36  
    37  // NewImporter creates an Importer object that meets types.Importer interface.
    38  func NewImporter(fset *token.FileSet, workDir ...string) *Importer {
    39  	dir := ""
    40  	if len(workDir) > 0 {
    41  		dir = workDir[0]
    42  	}
    43  	if fset == nil {
    44  		fset = token.NewFileSet()
    45  	}
    46  	loaded := make(map[string]*types.Package)
    47  	loaded["unsafe"] = types.Unsafe
    48  	return &Importer{loaded: loaded, fset: fset, dir: dir}
    49  }
    50  
    51  func (p *Importer) Import(pkgPath string) (pkg *types.Package, err error) {
    52  	return p.ImportFrom(pkgPath, p.dir, 0)
    53  }
    54  
    55  // ImportFrom returns the imported package for the given import
    56  // path when imported by a package file located in dir.
    57  // If the import failed, besides returning an error, ImportFrom
    58  // is encouraged to cache and return a package anyway, if one
    59  // was created. This will reduce package inconsistencies and
    60  // follow-on type checker errors due to the missing package.
    61  // The mode value must be 0; it is reserved for future use.
    62  // Two calls to ImportFrom with the same path and dir must
    63  // return the same package.
    64  func (p *Importer) ImportFrom(pkgPath, dir string, mode types.ImportMode) (*types.Package, error) {
    65  	p.m.RLock()
    66  	if ret, ok := p.loaded[pkgPath]; ok && ret.Complete() {
    67  		p.m.RUnlock()
    68  		return ret, nil
    69  	}
    70  	p.m.RUnlock()
    71  	expfile, err := FindExport(dir, pkgPath)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	return p.loadByExport(expfile, pkgPath)
    76  }
    77  
    78  func (p *Importer) loadByExport(expfile string, pkgPath string) (ret *types.Package, err error) {
    79  	f, err := os.Open(expfile)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	defer f.Close()
    84  
    85  	r, err := gcexportdata.NewReader(f)
    86  	if err == nil {
    87  		p.m.Lock() // use mutex because Import should be multi-thread safe
    88  		defer p.m.Unlock()
    89  		ret, err = gcexportdata.Read(r, p.fset, p.loaded, pkgPath)
    90  	}
    91  	return
    92  }
    93  
    94  // ----------------------------------------------------------------------------
    95  
    96  // FindExport lookups export file (.a) of a package by its pkgPath.
    97  // It returns empty if pkgPath not found.
    98  func FindExport(dir, pkgPath string) (expfile string, err error) {
    99  	data, err := golistExport(dir, pkgPath)
   100  	if err != nil {
   101  		return
   102  	}
   103  	expfile = string(bytes.TrimSuffix(data, []byte{'\n'}))
   104  	return
   105  }
   106  
   107  func golistExport(dir, pkgPath string) (ret []byte, err error) {
   108  	var stdout, stderr bytes.Buffer
   109  	cmd := exec.Command("go", "list", "-f={{.Export}}", "-export", pkgPath)
   110  	cmd.Stdout = &stdout
   111  	cmd.Stderr = &stderr
   112  	cmd.Dir = dir
   113  	err = cmd.Run()
   114  	if err == nil {
   115  		ret = stdout.Bytes()
   116  	} else if stderr.Len() > 0 {
   117  		err = errors.New(stderr.String())
   118  	}
   119  	return
   120  }
   121  
   122  // ----------------------------------------------------------------------------