...

Source file src/pkg/go/internal/srcimporter/srcimporter.go

     1	// Copyright 2017 The Go Authors. All rights reserved.
     2	// Use of this source code is governed by a BSD-style
     3	// license that can be found in the LICENSE file.
     4	
     5	// Package srcimporter implements importing directly
     6	// from source files rather than installed packages.
     7	package srcimporter // import "go/internal/srcimporter"
     8	
     9	import (
    10		"fmt"
    11		"go/ast"
    12		"go/build"
    13		"go/parser"
    14		"go/token"
    15		"go/types"
    16		"io"
    17		"os"
    18		"path/filepath"
    19		"sync"
    20	)
    21	
    22	// An Importer provides the context for importing packages from source code.
    23	type Importer struct {
    24		ctxt     *build.Context
    25		fset     *token.FileSet
    26		sizes    types.Sizes
    27		packages map[string]*types.Package
    28	}
    29	
    30	// NewImporter returns a new Importer for the given context, file set, and map
    31	// of packages. The context is used to resolve import paths to package paths,
    32	// and identifying the files belonging to the package. If the context provides
    33	// non-nil file system functions, they are used instead of the regular package
    34	// os functions. The file set is used to track position information of package
    35	// files; and imported packages are added to the packages map.
    36	func New(ctxt *build.Context, fset *token.FileSet, packages map[string]*types.Package) *Importer {
    37		return &Importer{
    38			ctxt:     ctxt,
    39			fset:     fset,
    40			sizes:    types.SizesFor(ctxt.Compiler, ctxt.GOARCH), // uses go/types default if GOARCH not found
    41			packages: packages,
    42		}
    43	}
    44	
    45	// Importing is a sentinel taking the place in Importer.packages
    46	// for a package that is in the process of being imported.
    47	var importing types.Package
    48	
    49	// Import(path) is a shortcut for ImportFrom(path, ".", 0).
    50	func (p *Importer) Import(path string) (*types.Package, error) {
    51		return p.ImportFrom(path, ".", 0) // use "." rather than "" (see issue #24441)
    52	}
    53	
    54	// ImportFrom imports the package with the given import path resolved from the given srcDir,
    55	// adds the new package to the set of packages maintained by the importer, and returns the
    56	// package. Package path resolution and file system operations are controlled by the context
    57	// maintained with the importer. The import mode must be zero but is otherwise ignored.
    58	// Packages that are not comprised entirely of pure Go files may fail to import because the
    59	// type checker may not be able to determine all exported entities (e.g. due to cgo dependencies).
    60	func (p *Importer) ImportFrom(path, srcDir string, mode types.ImportMode) (*types.Package, error) {
    61		if mode != 0 {
    62			panic("non-zero import mode")
    63		}
    64	
    65		if abs, err := p.absPath(srcDir); err == nil { // see issue #14282
    66			srcDir = abs
    67		}
    68		bp, err := p.ctxt.Import(path, srcDir, 0)
    69		if err != nil {
    70			return nil, err // err may be *build.NoGoError - return as is
    71		}
    72	
    73		// package unsafe is known to the type checker
    74		if bp.ImportPath == "unsafe" {
    75			return types.Unsafe, nil
    76		}
    77	
    78		// no need to re-import if the package was imported completely before
    79		pkg := p.packages[bp.ImportPath]
    80		if pkg != nil {
    81			if pkg == &importing {
    82				return nil, fmt.Errorf("import cycle through package %q", bp.ImportPath)
    83			}
    84			if !pkg.Complete() {
    85				// Package exists but is not complete - we cannot handle this
    86				// at the moment since the source importer replaces the package
    87				// wholesale rather than augmenting it (see #19337 for details).
    88				// Return incomplete package with error (see #16088).
    89				return pkg, fmt.Errorf("reimported partially imported package %q", bp.ImportPath)
    90			}
    91			return pkg, nil
    92		}
    93	
    94		p.packages[bp.ImportPath] = &importing
    95		defer func() {
    96			// clean up in case of error
    97			// TODO(gri) Eventually we may want to leave a (possibly empty)
    98			// package in the map in all cases (and use that package to
    99			// identify cycles). See also issue 16088.
   100			if p.packages[bp.ImportPath] == &importing {
   101				p.packages[bp.ImportPath] = nil
   102			}
   103		}()
   104	
   105		var filenames []string
   106		filenames = append(filenames, bp.GoFiles...)
   107		filenames = append(filenames, bp.CgoFiles...)
   108	
   109		files, err := p.parseFiles(bp.Dir, filenames)
   110		if err != nil {
   111			return nil, err
   112		}
   113	
   114		// type-check package files
   115		var firstHardErr error
   116		conf := types.Config{
   117			IgnoreFuncBodies: true,
   118			FakeImportC:      true,
   119			// continue type-checking after the first error
   120			Error: func(err error) {
   121				if firstHardErr == nil && !err.(types.Error).Soft {
   122					firstHardErr = err
   123				}
   124			},
   125			Importer: p,
   126			Sizes:    p.sizes,
   127		}
   128		pkg, err = conf.Check(bp.ImportPath, p.fset, files, nil)
   129		if err != nil {
   130			// If there was a hard error it is possibly unsafe
   131			// to use the package as it may not be fully populated.
   132			// Do not return it (see also #20837, #20855).
   133			if firstHardErr != nil {
   134				pkg = nil
   135				err = firstHardErr // give preference to first hard error over any soft error
   136			}
   137			return pkg, fmt.Errorf("type-checking package %q failed (%v)", bp.ImportPath, err)
   138		}
   139		if firstHardErr != nil {
   140			// this can only happen if we have a bug in go/types
   141			panic("package is not safe yet no error was returned")
   142		}
   143	
   144		p.packages[bp.ImportPath] = pkg
   145		return pkg, nil
   146	}
   147	
   148	func (p *Importer) parseFiles(dir string, filenames []string) ([]*ast.File, error) {
   149		// use build.Context's OpenFile if there is one
   150		open := p.ctxt.OpenFile
   151		if open == nil {
   152			open = func(name string) (io.ReadCloser, error) { return os.Open(name) }
   153		}
   154	
   155		files := make([]*ast.File, len(filenames))
   156		errors := make([]error, len(filenames))
   157	
   158		var wg sync.WaitGroup
   159		wg.Add(len(filenames))
   160		for i, filename := range filenames {
   161			go func(i int, filepath string) {
   162				defer wg.Done()
   163				src, err := open(filepath)
   164				if err != nil {
   165					errors[i] = err // open provides operation and filename in error
   166					return
   167				}
   168				files[i], errors[i] = parser.ParseFile(p.fset, filepath, src, 0)
   169				src.Close() // ignore Close error - parsing may have succeeded which is all we need
   170			}(i, p.joinPath(dir, filename))
   171		}
   172		wg.Wait()
   173	
   174		// if there are errors, return the first one for deterministic results
   175		for _, err := range errors {
   176			if err != nil {
   177				return nil, err
   178			}
   179		}
   180	
   181		return files, nil
   182	}
   183	
   184	// context-controlled file system operations
   185	
   186	func (p *Importer) absPath(path string) (string, error) {
   187		// TODO(gri) This should be using p.ctxt.AbsPath which doesn't
   188		// exist but probably should. See also issue #14282.
   189		return filepath.Abs(path)
   190	}
   191	
   192	func (p *Importer) isAbsPath(path string) bool {
   193		if f := p.ctxt.IsAbsPath; f != nil {
   194			return f(path)
   195		}
   196		return filepath.IsAbs(path)
   197	}
   198	
   199	func (p *Importer) joinPath(elem ...string) string {
   200		if f := p.ctxt.JoinPath; f != nil {
   201			return f(elem...)
   202		}
   203		return filepath.Join(elem...)
   204	}
   205	

View as plain text