mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-15 00:13:43 +00:00
loader: load packages using Go modules
This commit replaces the existing ad-hoc package loader with a package loader that uses the x/tools/go/packages package to find all to-be-loaded packages.
This commit is contained in:
+9
-21
@@ -156,6 +156,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
|
|||||||
Compiler: "gc", // must be one of the recognized compilers
|
Compiler: "gc", // must be one of the recognized compilers
|
||||||
BuildTags: c.BuildTags(),
|
BuildTags: c.BuildTags(),
|
||||||
},
|
},
|
||||||
|
Tests: c.TestConfig.CompileTestBinary,
|
||||||
TypeChecker: types.Config{
|
TypeChecker: types.Config{
|
||||||
Sizes: &stdSizes{
|
Sizes: &stdSizes{
|
||||||
IntSize: int64(c.targetData.TypeAllocSize(c.intType)),
|
IntSize: int64(c.targetData.TypeAllocSize(c.intType)),
|
||||||
@@ -169,33 +170,17 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
|
|||||||
ClangHeaders: c.ClangHeaders,
|
ClangHeaders: c.ClangHeaders,
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(pkgName, ".go") {
|
err = lprogram.Load(pkgName)
|
||||||
_, err = lprogram.ImportFile(pkgName)
|
|
||||||
if err != nil {
|
|
||||||
return c.mod, nil, []error{err}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = lprogram.Import(pkgName, wd, token.Position{
|
|
||||||
Filename: "build command-line-arguments",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return c.mod, nil, []error{err}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = lprogram.Import("runtime", "", token.Position{
|
|
||||||
Filename: "build default import",
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.mod, nil, []error{err}
|
return c.mod, nil, []error{err}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = lprogram.Parse(c.TestConfig.CompileTestBinary)
|
err = lprogram.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.mod, nil, []error{err}
|
return c.mod, nil, []error{err}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ir = ir.NewProgram(lprogram, pkgName)
|
c.ir = ir.NewProgram(lprogram)
|
||||||
|
|
||||||
// Run a simple dead code elimination pass.
|
// Run a simple dead code elimination pass.
|
||||||
err = c.ir.SimpleDCE()
|
err = c.ir.SimpleDCE()
|
||||||
@@ -339,8 +324,11 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
|
|||||||
// Gather the list of (C) file paths that should be included in the build.
|
// Gather the list of (C) file paths that should be included in the build.
|
||||||
var extraFiles []string
|
var extraFiles []string
|
||||||
for _, pkg := range c.ir.LoaderProgram.Sorted() {
|
for _, pkg := range c.ir.LoaderProgram.Sorted() {
|
||||||
for _, file := range pkg.CFiles {
|
for _, file := range pkg.OtherFiles {
|
||||||
extraFiles = append(extraFiles, filepath.Join(pkg.Package.Dir, file))
|
switch strings.ToLower(filepath.Ext(file)) {
|
||||||
|
case ".c":
|
||||||
|
extraFiles = append(extraFiles, file)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,23 +63,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Create and initialize a new *Program from a *ssa.Program.
|
// Create and initialize a new *Program from a *ssa.Program.
|
||||||
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
func NewProgram(lprogram *loader.Program) *Program {
|
||||||
program := lprogram.LoadSSA()
|
program := lprogram.LoadSSA()
|
||||||
program.Build()
|
program.Build()
|
||||||
|
|
||||||
// Find the main package, which is a bit difficult when running a .go file
|
// Find the main package, which is a bit difficult when running a .go file
|
||||||
// directly.
|
// directly.
|
||||||
mainPkg := program.ImportedPackage(mainPath)
|
mainPkg := program.ImportedPackage(lprogram.MainPkg.PkgPath)
|
||||||
if mainPkg == nil {
|
|
||||||
for _, pkgInfo := range program.AllPackages() {
|
|
||||||
if pkgInfo.Pkg.Name() == "main" {
|
|
||||||
if mainPkg != nil {
|
|
||||||
panic("more than one main package found")
|
|
||||||
}
|
|
||||||
mainPkg = pkgInfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if mainPkg == nil {
|
if mainPkg == nil {
|
||||||
panic("could not find main package")
|
panic("could not find main package")
|
||||||
}
|
}
|
||||||
@@ -87,21 +77,10 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
|||||||
// Make a list of packages in import order.
|
// Make a list of packages in import order.
|
||||||
packageList := []*ssa.Package{}
|
packageList := []*ssa.Package{}
|
||||||
packageSet := map[string]struct{}{}
|
packageSet := map[string]struct{}{}
|
||||||
worklist := []string{"runtime", mainPath}
|
worklist := []string{"runtime", lprogram.MainPkg.PkgPath}
|
||||||
for len(worklist) != 0 {
|
for len(worklist) != 0 {
|
||||||
pkgPath := worklist[0]
|
pkgPath := worklist[0]
|
||||||
var pkg *ssa.Package
|
pkg := program.ImportedPackage(pkgPath)
|
||||||
if pkgPath == mainPath {
|
|
||||||
pkg = mainPkg // necessary for compiling individual .go files
|
|
||||||
} else {
|
|
||||||
pkg = program.ImportedPackage(pkgPath)
|
|
||||||
}
|
|
||||||
if pkg == nil {
|
|
||||||
// Non-SSA package (e.g. cgo).
|
|
||||||
packageSet[pkgPath] = struct{}{}
|
|
||||||
worklist = worklist[1:]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := packageSet[pkgPath]; ok {
|
if _, ok := packageSet[pkgPath]; ok {
|
||||||
// Package already in the final package list.
|
// Package already in the final package list.
|
||||||
worklist = worklist[1:]
|
worklist = worklist[1:]
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
package loader
|
package loader
|
||||||
|
|
||||||
import (
|
|
||||||
"go/token"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Errors contains a list of parser errors or a list of typechecker errors for
|
// Errors contains a list of parser errors or a list of typechecker errors for
|
||||||
// the given package.
|
// the given package.
|
||||||
type Errors struct {
|
type Errors struct {
|
||||||
@@ -15,25 +10,3 @@ type Errors struct {
|
|||||||
func (e Errors) Error() string {
|
func (e Errors) Error() string {
|
||||||
return "could not compile: " + e.Errs[0].Error()
|
return "could not compile: " + e.Errs[0].Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImportCycleErrors is returned when encountering an import cycle. The list of
|
|
||||||
// packages is a list from the root package to the leaf package that imports one
|
|
||||||
// of the packages in the list.
|
|
||||||
type ImportCycleError struct {
|
|
||||||
Packages []string
|
|
||||||
ImportPositions []token.Position
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ImportCycleError) Error() string {
|
|
||||||
var msg strings.Builder
|
|
||||||
msg.WriteString("import cycle:\n\t")
|
|
||||||
msg.WriteString(strings.Join(e.Packages, "\n\t"))
|
|
||||||
msg.WriteString("\n at ")
|
|
||||||
for i, pos := range e.ImportPositions {
|
|
||||||
if i > 0 {
|
|
||||||
msg.WriteString(", ")
|
|
||||||
}
|
|
||||||
msg.WriteString(pos.String())
|
|
||||||
}
|
|
||||||
return msg.String()
|
|
||||||
}
|
|
||||||
|
|||||||
+55
-6
@@ -10,8 +10,10 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
@@ -58,8 +60,12 @@ func GetCachedGoroot(config *compileopts.Config) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the temporary directory if it wasn't moved to the right place
|
||||||
|
// (for example, when there was an error).
|
||||||
|
defer os.RemoveAll(tmpgoroot)
|
||||||
|
|
||||||
for _, name := range []string{"bin", "lib", "pkg"} {
|
for _, name := range []string{"bin", "lib", "pkg"} {
|
||||||
err = os.Symlink(filepath.Join(goroot, name), filepath.Join(tmpgoroot, name))
|
err = symlink(filepath.Join(goroot, name), filepath.Join(tmpgoroot, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -72,8 +78,8 @@ func GetCachedGoroot(config *compileopts.Config) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsExist(err) {
|
if os.IsExist(err) {
|
||||||
// Another invocation of TinyGo also seems to have created a GOROOT.
|
// Another invocation of TinyGo also seems to have created a GOROOT.
|
||||||
// Use that one instead and delete ours.
|
// Use that one instead. Our new GOROOT will be automatically
|
||||||
os.RemoveAll(tmpgoroot)
|
// deleted by the defer above.
|
||||||
return cachedgoroot, nil
|
return cachedgoroot, nil
|
||||||
}
|
}
|
||||||
return "", err
|
return "", err
|
||||||
@@ -91,7 +97,7 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides
|
|||||||
// root, so simply make a symlink.
|
// root, so simply make a symlink.
|
||||||
newname := filepath.Join(tmpgoroot, "src", importPath)
|
newname := filepath.Join(tmpgoroot, "src", importPath)
|
||||||
oldname := filepath.Join(tinygoroot, "src", importPath)
|
oldname := filepath.Join(tinygoroot, "src", importPath)
|
||||||
return os.Symlink(oldname, newname)
|
return symlink(oldname, newname)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge subdirectories. Start by making the directory to merge.
|
// Merge subdirectories. Start by making the directory to merge.
|
||||||
@@ -117,7 +123,7 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides
|
|||||||
// A file, so symlink this.
|
// A file, so symlink this.
|
||||||
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
|
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
|
||||||
oldname := filepath.Join(tinygoroot, "src", importPath, e.Name())
|
oldname := filepath.Join(tinygoroot, "src", importPath, e.Name())
|
||||||
err := os.Symlink(oldname, newname)
|
err := symlink(oldname, newname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -143,7 +149,7 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides
|
|||||||
}
|
}
|
||||||
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
|
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
|
||||||
oldname := filepath.Join(goroot, "src", importPath, e.Name())
|
oldname := filepath.Join(goroot, "src", importPath, e.Name())
|
||||||
err := os.Symlink(oldname, newname)
|
err := symlink(oldname, newname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -174,3 +180,46 @@ func pathsToOverride(needsSyscallPackage bool) map[string]bool {
|
|||||||
}
|
}
|
||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// symlink creates a symlink or something similar. On Unix-like systems, it
|
||||||
|
// always creates a symlink. On Windows, it tries to create a symlink and if
|
||||||
|
// that fails, creates a hardlink or directory junction instead.
|
||||||
|
//
|
||||||
|
// Note that while Windows 10 does support symlinks and allows them to be
|
||||||
|
// created using os.Symlink, it requires developer mode to be enabled.
|
||||||
|
// Therefore provide a fallback for when symlinking is not possible.
|
||||||
|
// Unfortunately this fallback only works when TinyGo is installed on the same
|
||||||
|
// filesystem as the TinyGo cache and the Go installation (which is usually the
|
||||||
|
// C drive).
|
||||||
|
func symlink(oldname, newname string) error {
|
||||||
|
symlinkErr := os.Symlink(oldname, newname)
|
||||||
|
if runtime.GOOS == "windows" && symlinkErr != nil {
|
||||||
|
// Fallback for when developer mode is disabled.
|
||||||
|
// Note that we return the symlink error even if something else fails
|
||||||
|
// later on. This is because symlinks are the easiest to support
|
||||||
|
// (they're also used on Linux and MacOS) and enabling them is easy:
|
||||||
|
// just enable developer mode.
|
||||||
|
st, err := os.Stat(oldname)
|
||||||
|
if err != nil {
|
||||||
|
return symlinkErr
|
||||||
|
}
|
||||||
|
if st.IsDir() {
|
||||||
|
// Make a directory junction. There may be a way to do this
|
||||||
|
// programmatically, but it involves a lot of magic. Use the mklink
|
||||||
|
// command built into cmd instead (mklink is a builtin, not an
|
||||||
|
// external command).
|
||||||
|
err := exec.Command("cmd", "/k", "mklink", "/J", newname, oldname).Run()
|
||||||
|
if err != nil {
|
||||||
|
return symlinkErr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Make a hard link.
|
||||||
|
err := os.Link(oldname, newname)
|
||||||
|
if err != nil {
|
||||||
|
return symlinkErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil // success
|
||||||
|
}
|
||||||
|
return symlinkErr
|
||||||
|
}
|
||||||
|
|||||||
+127
-213
@@ -3,10 +3,10 @@ package loader
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/build"
|
"go/build"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/scanner"
|
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"os"
|
"os"
|
||||||
@@ -16,13 +16,16 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/cgo"
|
"github.com/tinygo-org/tinygo/cgo"
|
||||||
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Program holds all packages and some metadata about the program as a whole.
|
// Program holds all packages and some metadata about the program as a whole.
|
||||||
type Program struct {
|
type Program struct {
|
||||||
mainPkg string
|
|
||||||
Build *build.Context
|
Build *build.Context
|
||||||
|
Tests bool
|
||||||
Packages map[string]*Package
|
Packages map[string]*Package
|
||||||
|
MainPkg *Package
|
||||||
sorted []*Package
|
sorted []*Package
|
||||||
fset *token.FileSet
|
fset *token.FileSet
|
||||||
TypeChecker types.Config
|
TypeChecker types.Config
|
||||||
@@ -35,85 +38,114 @@ type Program struct {
|
|||||||
// Package holds a loaded package, its imports, and its parsed files.
|
// Package holds a loaded package, its imports, and its parsed files.
|
||||||
type Package struct {
|
type Package struct {
|
||||||
*Program
|
*Program
|
||||||
*build.Package
|
*packages.Package
|
||||||
Imports map[string]*Package
|
Files []*ast.File
|
||||||
Importing bool
|
Pkg *types.Package
|
||||||
Files []*ast.File
|
|
||||||
Pkg *types.Package
|
|
||||||
types.Info
|
types.Info
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import loads the given package relative to srcDir (for the vendor directory).
|
// Load loads the given package with all dependencies (including the runtime
|
||||||
// It only loads the current package without recursion.
|
// package). Call .Parse() afterwards to parse all Go files (including CGo
|
||||||
func (p *Program) Import(path, srcDir string, pos token.Position) (*Package, error) {
|
// processing, if necessary).
|
||||||
|
func (p *Program) Load(importPath string) error {
|
||||||
if p.Packages == nil {
|
if p.Packages == nil {
|
||||||
p.Packages = make(map[string]*Package)
|
p.Packages = make(map[string]*Package)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load this package.
|
err := p.loadPackage(importPath)
|
||||||
ctx := p.Build
|
|
||||||
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, scanner.Error{
|
return err
|
||||||
Pos: pos,
|
|
||||||
Msg: err.Error(), // TODO: define a new error type that will wrap the inner error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if existingPkg, ok := p.Packages[buildPkg.ImportPath]; ok {
|
p.MainPkg = p.sorted[len(p.sorted)-1]
|
||||||
// Already imported, or at least started the import.
|
if _, ok := p.Packages["runtime"]; !ok {
|
||||||
return existingPkg, nil
|
// The runtime package wasn't loaded. Although `go list -deps` seems to
|
||||||
|
// return the full dependency list, there is no way to get those
|
||||||
|
// packages from the go/packages package. Therefore load the runtime
|
||||||
|
// manually and add it to the list of to-be-compiled packages
|
||||||
|
// (duplicates are already filtered).
|
||||||
|
return p.loadPackage("runtime")
|
||||||
}
|
}
|
||||||
p.sorted = nil // invalidate the sorted order of packages
|
return nil
|
||||||
pkg := p.newPackage(buildPkg)
|
|
||||||
p.Packages[buildPkg.ImportPath] = pkg
|
|
||||||
|
|
||||||
if p.mainPkg == "" {
|
|
||||||
p.mainPkg = buildPkg.ImportPath
|
|
||||||
}
|
|
||||||
|
|
||||||
return pkg, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImportFile loads and parses the import statements in the given path and
|
func (p *Program) loadPackage(importPath string) error {
|
||||||
// creates a pseudo-package out of it.
|
cgoEnabled := "0"
|
||||||
func (p *Program) ImportFile(path string) (*Package, error) {
|
if p.Build.CgoEnabled {
|
||||||
if p.Packages == nil {
|
cgoEnabled = "1"
|
||||||
p.Packages = make(map[string]*Package)
|
|
||||||
}
|
}
|
||||||
if _, ok := p.Packages[path]; ok {
|
pkgs, err := packages.Load(&packages.Config{
|
||||||
// unlikely
|
Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps,
|
||||||
return nil, errors.New("loader: cannot import file that is already imported as package: " + path)
|
Env: append(os.Environ(), "GOROOT="+p.Build.GOROOT, "GOOS="+p.Build.GOOS, "GOARCH="+p.Build.GOARCH, "CGO_ENABLED="+cgoEnabled),
|
||||||
}
|
BuildFlags: []string{"-tags", strings.Join(p.Build.BuildTags, " ")},
|
||||||
|
Tests: p.Tests,
|
||||||
file, err := p.parseFile(path, parser.ImportsOnly)
|
}, importPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
buildPkg := &build.Package{
|
var pkg *packages.Package
|
||||||
Dir: filepath.Dir(path),
|
if p.Tests {
|
||||||
ImportPath: path,
|
// We need the second package. Quoting from the docs:
|
||||||
GoFiles: []string{filepath.Base(path)},
|
// > For example, when using the go command, loading "fmt" with Tests=true
|
||||||
|
// > returns four packages, with IDs "fmt" (the standard package),
|
||||||
|
// > "fmt [fmt.test]" (the package as compiled for the test),
|
||||||
|
// > "fmt_test" (the test functions from source files in package fmt_test),
|
||||||
|
// > and "fmt.test" (the test binary).
|
||||||
|
pkg = pkgs[1]
|
||||||
|
} else {
|
||||||
|
if len(pkgs) != 1 {
|
||||||
|
return fmt.Errorf("expected exactly one package while importing %s, got %d", importPath, len(pkgs))
|
||||||
|
}
|
||||||
|
pkg = pkgs[0]
|
||||||
}
|
}
|
||||||
for _, importSpec := range file.Imports {
|
var importError *Errors
|
||||||
buildPkg.Imports = append(buildPkg.Imports, importSpec.Path.Value[1:len(importSpec.Path.Value)-1])
|
var addPackages func(pkg *packages.Package)
|
||||||
}
|
addPackages = func(pkg *packages.Package) {
|
||||||
p.sorted = nil // invalidate the sorted order of packages
|
if _, ok := p.Packages[pkg.PkgPath]; ok {
|
||||||
pkg := p.newPackage(buildPkg)
|
return
|
||||||
p.Packages[buildPkg.ImportPath] = pkg
|
}
|
||||||
|
pkg2 := p.newPackage(pkg)
|
||||||
|
p.Packages[pkg.PkgPath] = pkg2
|
||||||
|
if len(pkg.Errors) != 0 {
|
||||||
|
if importError != nil {
|
||||||
|
// There was another error reported already. Do not report
|
||||||
|
// errors from multiple packages at once.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
importError = &Errors{
|
||||||
|
Pkg: pkg2,
|
||||||
|
}
|
||||||
|
for _, err := range pkg.Errors {
|
||||||
|
importError.Errs = append(importError.Errs, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if p.mainPkg == "" {
|
// Get the list of imports (sorted alphabetically).
|
||||||
p.mainPkg = buildPkg.ImportPath
|
names := make([]string, 0, len(pkg.Imports))
|
||||||
}
|
for name := range pkg.Imports {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
return pkg, nil
|
// Add all the imports.
|
||||||
|
for _, name := range names {
|
||||||
|
addPackages(pkg.Imports[name])
|
||||||
|
}
|
||||||
|
|
||||||
|
p.sorted = append(p.sorted, pkg2)
|
||||||
|
}
|
||||||
|
addPackages(pkg)
|
||||||
|
if importError != nil {
|
||||||
|
return importError
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newPackage instantiates a new *Package object with initialized members.
|
// newPackage instantiates a new *Package object with initialized members.
|
||||||
func (p *Program) newPackage(pkg *build.Package) *Package {
|
func (p *Program) newPackage(pkg *packages.Package) *Package {
|
||||||
return &Package{
|
return &Package{
|
||||||
Program: p,
|
Program: p,
|
||||||
Package: pkg,
|
Package: pkg,
|
||||||
Imports: make(map[string]*Package, len(pkg.Imports)),
|
|
||||||
Info: types.Info{
|
Info: types.Info{
|
||||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||||
Defs: make(map[*ast.Ident]types.Object),
|
Defs: make(map[*ast.Ident]types.Object),
|
||||||
@@ -128,87 +160,25 @@ func (p *Program) newPackage(pkg *build.Package) *Package {
|
|||||||
// Sorted returns a list of all packages, sorted in a way that no packages come
|
// Sorted returns a list of all packages, sorted in a way that no packages come
|
||||||
// before the packages they depend upon.
|
// before the packages they depend upon.
|
||||||
func (p *Program) Sorted() []*Package {
|
func (p *Program) Sorted() []*Package {
|
||||||
if p.sorted == nil {
|
|
||||||
p.sort()
|
|
||||||
}
|
|
||||||
return p.sorted
|
return p.sorted
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Program) sort() {
|
// Parse parses all packages and typechecks them.
|
||||||
p.sorted = nil
|
|
||||||
packageList := make([]*Package, 0, len(p.Packages))
|
|
||||||
packageSet := make(map[string]struct{}, len(p.Packages))
|
|
||||||
worklist := make([]string, 0, len(p.Packages))
|
|
||||||
for path := range p.Packages {
|
|
||||||
worklist = append(worklist, path)
|
|
||||||
}
|
|
||||||
sort.Strings(worklist)
|
|
||||||
for len(worklist) != 0 {
|
|
||||||
pkgPath := worklist[0]
|
|
||||||
pkg := p.Packages[pkgPath]
|
|
||||||
|
|
||||||
if _, ok := packageSet[pkgPath]; ok {
|
|
||||||
// Package already in the final package list.
|
|
||||||
worklist = worklist[1:]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
unsatisfiedImports := make([]string, 0)
|
|
||||||
for _, pkg := range pkg.Imports {
|
|
||||||
if _, ok := packageSet[pkg.ImportPath]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
unsatisfiedImports = append(unsatisfiedImports, pkg.ImportPath)
|
|
||||||
}
|
|
||||||
sort.Strings(unsatisfiedImports)
|
|
||||||
if len(unsatisfiedImports) == 0 {
|
|
||||||
// All dependencies of this package are satisfied, so add this
|
|
||||||
// package to the list.
|
|
||||||
packageList = append(packageList, pkg)
|
|
||||||
packageSet[pkgPath] = struct{}{}
|
|
||||||
worklist = worklist[1:]
|
|
||||||
} else {
|
|
||||||
// Prepend all dependencies to the worklist and reconsider this
|
|
||||||
// package (by not removing it from the worklist). At that point, it
|
|
||||||
// must be possible to add it to packageList.
|
|
||||||
worklist = append(unsatisfiedImports, worklist...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.sorted = packageList
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse recursively imports all packages, parses them, and typechecks them.
|
|
||||||
//
|
//
|
||||||
// The returned error may be an Errors error, which contains a list of errors.
|
// The returned error may be an Errors error, which contains a list of errors.
|
||||||
//
|
//
|
||||||
// Idempotent.
|
// Idempotent.
|
||||||
func (p *Program) Parse(compileTestBinary bool) error {
|
func (p *Program) Parse() error {
|
||||||
includeTests := compileTestBinary
|
|
||||||
|
|
||||||
// Load all imports
|
|
||||||
for _, pkg := range p.Sorted() {
|
|
||||||
err := pkg.importRecursively(includeTests)
|
|
||||||
if err != nil {
|
|
||||||
if err, ok := err.(*ImportCycleError); ok {
|
|
||||||
if pkg.ImportPath != err.Packages[0] {
|
|
||||||
err.Packages = append([]string{pkg.ImportPath}, err.Packages...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse all packages.
|
// Parse all packages.
|
||||||
for _, pkg := range p.Sorted() {
|
for _, pkg := range p.Sorted() {
|
||||||
err := pkg.Parse(includeTests)
|
err := pkg.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if compileTestBinary {
|
if p.Tests {
|
||||||
err := p.SwapTestMain()
|
err := p.swapTestMain()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -225,7 +195,7 @@ func (p *Program) Parse(compileTestBinary bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Program) SwapTestMain() error {
|
func (p *Program) swapTestMain() error {
|
||||||
var tests []string
|
var tests []string
|
||||||
|
|
||||||
isTestFunc := func(f *ast.FuncDecl) bool {
|
isTestFunc := func(f *ast.FuncDecl) bool {
|
||||||
@@ -235,8 +205,7 @@ func (p *Program) SwapTestMain() error {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
mainPkg := p.Packages[p.mainPkg]
|
for _, f := range p.MainPkg.Files {
|
||||||
for _, f := range mainPkg.Files {
|
|
||||||
for i, d := range f.Decls {
|
for i, d := range f.Decls {
|
||||||
switch v := d.(type) {
|
switch v := d.(type) {
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
@@ -287,7 +256,7 @@ func main () {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
path := filepath.Join(p.mainPkg, "$testmain.go")
|
path := filepath.Join(p.MainPkg.Dir, "$testmain.go")
|
||||||
|
|
||||||
if p.fset == nil {
|
if p.fset == nil {
|
||||||
p.fset = token.NewFileSet()
|
p.fset = token.NewFileSet()
|
||||||
@@ -297,7 +266,7 @@ func main () {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
mainPkg.Files = append(mainPkg.Files, newMain)
|
p.MainPkg.Files = append(p.MainPkg.Files, newMain)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -313,34 +282,41 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rd.Close()
|
defer rd.Close()
|
||||||
relpath := path
|
diagnosticPath := path
|
||||||
if filepath.IsAbs(path) {
|
if strings.HasPrefix(path, p.Build.GOROOT+string(filepath.Separator)) {
|
||||||
rp, err := filepath.Rel(p.Dir, path)
|
// If this file is part of the synthetic GOROOT, try to infer the
|
||||||
if err == nil {
|
// original path.
|
||||||
relpath = rp
|
relpath := path[len(p.Build.GOROOT)+1:]
|
||||||
|
tinygoPath := filepath.Join(p.TINYGOROOT, relpath)
|
||||||
|
if _, err := os.Stat(tinygoPath); err == nil {
|
||||||
|
diagnosticPath = tinygoPath
|
||||||
|
}
|
||||||
|
realgorootPath := filepath.Join(goenv.Get("GOROOT"), relpath)
|
||||||
|
if _, err := os.Stat(realgorootPath); err == nil {
|
||||||
|
diagnosticPath = realgorootPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return parser.ParseFile(p.fset, relpath, rd, mode)
|
return parser.ParseFile(p.fset, diagnosticPath, rd, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses and typechecks this package.
|
// Parse parses and typechecks this package.
|
||||||
//
|
//
|
||||||
// Idempotent.
|
// Idempotent.
|
||||||
func (p *Package) Parse(includeTests bool) error {
|
func (p *Package) Parse() error {
|
||||||
if len(p.Files) != 0 {
|
if len(p.Files) != 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the AST.
|
// Load the AST.
|
||||||
// TODO: do this in parallel.
|
// TODO: do this in parallel.
|
||||||
if p.ImportPath == "unsafe" {
|
if p.PkgPath == "unsafe" {
|
||||||
// Special case for the unsafe package. Don't even bother loading
|
// Special case for the unsafe package. Don't even bother loading
|
||||||
// the files.
|
// the files.
|
||||||
p.Pkg = types.Unsafe
|
p.Pkg = types.Unsafe
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
files, err := p.parseFiles(includeTests)
|
files, err := p.parseFiles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -367,7 +343,7 @@ func (p *Package) Check() error {
|
|||||||
// Do typechecking of the package.
|
// Do typechecking of the package.
|
||||||
checker.Importer = p
|
checker.Importer = p
|
||||||
|
|
||||||
typesPkg, err := checker.Check(p.ImportPath, p.fset, p.Files, &p.Info)
|
typesPkg, err := checker.Check(p.PkgPath, p.fset, p.Files, &p.Info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err, ok := err.(Errors); ok {
|
if err, ok := err.(Errors); ok {
|
||||||
return err
|
return err
|
||||||
@@ -379,22 +355,14 @@ func (p *Package) Check() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseFiles parses the loaded list of files and returns this list.
|
// parseFiles parses the loaded list of files and returns this list.
|
||||||
func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
|
func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||||
// TODO: do this concurrently.
|
// TODO: do this concurrently.
|
||||||
var files []*ast.File
|
var files []*ast.File
|
||||||
var fileErrs []error
|
var fileErrs []error
|
||||||
|
|
||||||
var gofiles []string
|
var cgoFiles []*ast.File
|
||||||
if includeTests {
|
for _, file := range p.GoFiles {
|
||||||
gofiles = make([]string, 0, len(p.GoFiles)+len(p.TestGoFiles))
|
f, err := p.parseFile(file, parser.ParseComments)
|
||||||
gofiles = append(gofiles, p.GoFiles...)
|
|
||||||
gofiles = append(gofiles, p.TestGoFiles...)
|
|
||||||
} else {
|
|
||||||
gofiles = p.GoFiles
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range gofiles {
|
|
||||||
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fileErrs = append(fileErrs, err)
|
fileErrs = append(fileErrs, err)
|
||||||
continue
|
continue
|
||||||
@@ -403,19 +371,15 @@ func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
|
|||||||
fileErrs = append(fileErrs, err)
|
fileErrs = append(fileErrs, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
for _, importSpec := range f.Imports {
|
||||||
|
if importSpec.Path.Value == `"C"` {
|
||||||
|
cgoFiles = append(cgoFiles, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
files = append(files, f)
|
files = append(files, f)
|
||||||
}
|
}
|
||||||
for _, file := range p.CgoFiles {
|
if len(cgoFiles) != 0 {
|
||||||
path := filepath.Join(p.Package.Dir, file)
|
cflags := append(p.CFlags, "-I"+filepath.Dir(p.GoFiles[0]))
|
||||||
f, err := p.parseFile(path, parser.ParseComments)
|
|
||||||
if err != nil {
|
|
||||||
fileErrs = append(fileErrs, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
files = append(files, f)
|
|
||||||
}
|
|
||||||
if len(p.CgoFiles) != 0 {
|
|
||||||
cflags := append(p.CFlags, "-I"+p.Package.Dir)
|
|
||||||
if p.ClangHeaders != "" {
|
if p.ClangHeaders != "" {
|
||||||
cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.ClangHeaders)
|
cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.ClangHeaders)
|
||||||
}
|
}
|
||||||
@@ -439,58 +403,8 @@ func (p *Package) Import(to string) (*types.Package, error) {
|
|||||||
return types.Unsafe, nil
|
return types.Unsafe, nil
|
||||||
}
|
}
|
||||||
if _, ok := p.Imports[to]; ok {
|
if _, ok := p.Imports[to]; ok {
|
||||||
return p.Imports[to].Pkg, nil
|
return p.Packages[p.Imports[to].PkgPath].Pkg, nil
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("package not imported: " + to)
|
return nil, errors.New("package not imported: " + to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// importRecursively calls Program.Import() on all imported packages, and calls
|
|
||||||
// importRecursively() on the imported packages as well.
|
|
||||||
//
|
|
||||||
// Idempotent.
|
|
||||||
func (p *Package) importRecursively(includeTests bool) error {
|
|
||||||
p.Importing = true
|
|
||||||
|
|
||||||
imports := p.Package.Imports
|
|
||||||
if includeTests {
|
|
||||||
imports = append(imports, p.Package.TestImports...)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, to := range imports {
|
|
||||||
if to == "C" {
|
|
||||||
// Do CGo processing in a later stage.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := p.Imports[to]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Find error location.
|
|
||||||
var pos token.Position
|
|
||||||
if len(p.Package.ImportPos[to]) > 0 {
|
|
||||||
pos = p.Package.ImportPos[to][0]
|
|
||||||
} else {
|
|
||||||
pos = token.Position{Filename: p.Package.ImportPath}
|
|
||||||
}
|
|
||||||
importedPkg, err := p.Program.Import(to, p.Package.Dir, pos)
|
|
||||||
if err != nil {
|
|
||||||
if err, ok := err.(*ImportCycleError); ok {
|
|
||||||
err.Packages = append([]string{p.ImportPath}, err.Packages...)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if importedPkg.Importing {
|
|
||||||
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
|
|
||||||
}
|
|
||||||
err = importedPkg.importRecursively(false)
|
|
||||||
if err != nil {
|
|
||||||
if err, ok := err.(*ImportCycleError); ok {
|
|
||||||
err.Packages = append([]string{p.ImportPath}, err.Packages...)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.Imports[to] = importedPkg
|
|
||||||
}
|
|
||||||
p.Importing = false
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -714,7 +714,7 @@ func printCompilerError(logln func(...interface{}), err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case loader.Errors:
|
case loader.Errors:
|
||||||
logln("#", err.Pkg.ImportPath)
|
logln("#", err.Pkg.PkgPath)
|
||||||
for _, err := range err.Errs {
|
for _, err := range err.Errs {
|
||||||
logln(err)
|
logln(err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user