// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package srcimporter implements importing directly // from source files rather than installed packages.
package srcimporter // import "go/internal/srcimporter" import ( _ // for go:linkname ) // An Importer provides the context for importing packages from source code. type Importer struct { ctxt *build.Context fset *token.FileSet sizes types.Sizes packages map[string]*types.Package } // New returns a new Importer for the given context, file set, and map // of packages. The context is used to resolve import paths to package paths, // and identifying the files belonging to the package. If the context provides // non-nil file system functions, they are used instead of the regular package // os functions. The file set is used to track position information of package // files; and imported packages are added to the packages map. func ( *build.Context, *token.FileSet, map[string]*types.Package) *Importer { return &Importer{ ctxt: , fset: , sizes: types.SizesFor(.Compiler, .GOARCH), // uses go/types default if GOARCH not found packages: , } } // Importing is a sentinel taking the place in Importer.packages // for a package that is in the process of being imported. var importing types.Package // Import(path) is a shortcut for ImportFrom(path, ".", 0). func ( *Importer) ( string) (*types.Package, error) { return .ImportFrom(, ".", 0) // use "." rather than "" (see issue #24441) } // ImportFrom imports the package with the given import path resolved from the given srcDir, // adds the new package to the set of packages maintained by the importer, and returns the // package. Package path resolution and file system operations are controlled by the context // maintained with the importer. The import mode must be zero but is otherwise ignored. // Packages that are not comprised entirely of pure Go files may fail to import because the // type checker may not be able to determine all exported entities (e.g. due to cgo dependencies). func ( *Importer) (, string, types.ImportMode) (*types.Package, error) { if != 0 { panic("non-zero import mode") } if , := .absPath(); == nil { // see issue #14282 = } , := .ctxt.Import(, , 0) if != nil { return nil, // err may be *build.NoGoError - return as is } // package unsafe is known to the type checker if .ImportPath == "unsafe" { return types.Unsafe, nil } // no need to re-import if the package was imported completely before := .packages[.ImportPath] if != nil { if == &importing { return nil, fmt.Errorf("import cycle through package %q", .ImportPath) } if !.Complete() { // Package exists but is not complete - we cannot handle this // at the moment since the source importer replaces the package // wholesale rather than augmenting it (see #19337 for details). // Return incomplete package with error (see #16088). return , fmt.Errorf("reimported partially imported package %q", .ImportPath) } return , nil } .packages[.ImportPath] = &importing defer func() { // clean up in case of error // TODO(gri) Eventually we may want to leave a (possibly empty) // package in the map in all cases (and use that package to // identify cycles). See also issue 16088. if .packages[.ImportPath] == &importing { .packages[.ImportPath] = nil } }() var []string = append(, .GoFiles...) = append(, .CgoFiles...) , := .parseFiles(.Dir, ) if != nil { return nil, } // type-check package files var error := types.Config{ IgnoreFuncBodies: true, // continue type-checking after the first error Error: func( error) { if == nil && !.(types.Error).Soft { = } }, Importer: , Sizes: .sizes, } if len(.CgoFiles) > 0 { if .ctxt.OpenFile != nil { // cgo, gcc, pkg-config, etc. do not support // build.Context's VFS. .FakeImportC = true } else { setUsesCgo(&) , := .cgo() if != nil { return nil, fmt.Errorf("error processing cgo for package %q: %w", .ImportPath, ) } = append(, ) } } , = .Check(.ImportPath, .fset, , nil) if != nil { // If there was a hard error it is possibly unsafe // to use the package as it may not be fully populated. // Do not return it (see also #20837, #20855). if != nil { = nil = // give preference to first hard error over any soft error } return , fmt.Errorf("type-checking package %q failed (%v)", .ImportPath, ) } if != nil { // this can only happen if we have a bug in go/types panic("package is not safe yet no error was returned") } .packages[.ImportPath] = return , nil } func ( *Importer) ( string, []string) ([]*ast.File, error) { // use build.Context's OpenFile if there is one := .ctxt.OpenFile if == nil { = func( string) (io.ReadCloser, error) { return os.Open() } } := make([]*ast.File, len()) := make([]error, len()) var sync.WaitGroup .Add(len()) for , := range { go func( int, string) { defer .Done() , := () if != nil { [] = // open provides operation and filename in error return } [], [] = parser.ParseFile(.fset, , , parser.SkipObjectResolution) .Close() // ignore Close error - parsing may have succeeded which is all we need }(, .joinPath(, )) } .Wait() // if there are errors, return the first one for deterministic results for , := range { if != nil { return nil, } } return , nil } func ( *Importer) ( *build.Package) (*ast.File, error) { , := os.MkdirTemp("", "srcimporter") if != nil { return nil, } defer os.RemoveAll() := "go" if .ctxt.GOROOT != "" { = filepath.Join(.ctxt.GOROOT, "bin", "go") } := []string{, "tool", "cgo", "-objdir", } if .Goroot { switch .ImportPath { case "runtime/cgo": = append(, "-import_runtime_cgo=false", "-import_syscall=false") case "runtime/race": = append(, "-import_syscall=false") } } = append(, "--") = append(, strings.Fields(os.Getenv("CGO_CPPFLAGS"))...) = append(, .CgoCPPFLAGS...) if len(.CgoPkgConfig) > 0 { := exec.Command("pkg-config", append([]string{"--cflags"}, .CgoPkgConfig...)...) , := .Output() if != nil { return nil, fmt.Errorf("pkg-config --cflags: %w", ) } = append(, strings.Fields(string())...) } = append(, "-I", ) = append(, strings.Fields(os.Getenv("CGO_CFLAGS"))...) = append(, .CgoCFLAGS...) = append(, .CgoFiles...) := exec.Command([0], [1:]...) .Dir = .Dir if := .Run(); != nil { return nil, fmt.Errorf("go tool cgo: %w", ) } return parser.ParseFile(.fset, filepath.Join(, "_cgo_gotypes.go"), nil, parser.SkipObjectResolution) } // context-controlled file system operations func ( *Importer) ( string) (string, error) { // TODO(gri) This should be using p.ctxt.AbsPath which doesn't // exist but probably should. See also issue #14282. return filepath.Abs() } func ( *Importer) ( string) bool { if := .ctxt.IsAbsPath; != nil { return () } return filepath.IsAbs() } func ( *Importer) ( ...string) string { if := .ctxt.JoinPath; != nil { return (...) } return filepath.Join(...) } //go:linkname setUsesCgo go/types.srcimporter_setUsesCgo func setUsesCgo( *types.Config)