mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-01 09:37:48 +00:00
Compare commits
5 Commits
file-embed
...
vscode
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e07fc569c | |||
| 25e13d887f | |||
| 51daa2a044 | |||
| f70dc70247 | |||
| f24bb1ceda |
+1
-1
@@ -21,7 +21,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
|||||||
if goroot == "" {
|
if goroot == "" {
|
||||||
return nil, errors.New("cannot locate $GOROOT, please set it manually")
|
return nil, errors.New("cannot locate $GOROOT, please set it manually")
|
||||||
}
|
}
|
||||||
major, minor, err := getGorootVersion(goroot)
|
major, minor, err := goenv.GetGorootVersion(goroot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,71 +1,13 @@
|
|||||||
package builder
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// getGorootVersion returns the major and minor version for a given GOROOT path.
|
|
||||||
// If the goroot cannot be determined, (0, 0) is returned.
|
|
||||||
func getGorootVersion(goroot string) (major, minor int, err error) {
|
|
||||||
s, err := GorootVersionString(goroot)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s == "" || s[:2] != "go" {
|
|
||||||
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.Split(s[2:], ".")
|
|
||||||
if len(parts) < 2 {
|
|
||||||
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore the errors, we don't really handle errors here anyway.
|
|
||||||
var trailing string
|
|
||||||
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
|
|
||||||
if n == 2 && err == io.EOF {
|
|
||||||
// Means there were no trailing characters (i.e., not an alpha/beta)
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GorootVersionString returns the version string as reported by the Go
|
|
||||||
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
|
|
||||||
// can have some variations (for beta releases, for example).
|
|
||||||
func GorootVersionString(goroot string) (string, error) {
|
|
||||||
if data, err := ioutil.ReadFile(filepath.Join(
|
|
||||||
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
|
|
||||||
|
|
||||||
r := regexp.MustCompile("const TheVersion = `(.*)`")
|
|
||||||
matches := r.FindSubmatch(data)
|
|
||||||
if len(matches) != 2 {
|
|
||||||
return "", errors.New("Invalid go version output:\n" + string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(matches[1]), nil
|
|
||||||
|
|
||||||
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
|
|
||||||
return string(data), nil
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
|
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
|
||||||
// multiple locations, which should make it find the directory when installed in
|
// multiple locations, which should make it find the directory when installed in
|
||||||
// various ways.
|
// various ways.
|
||||||
|
|||||||
+14
-65
@@ -137,64 +137,26 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
|
|||||||
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
|
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
|
||||||
dummyFunc.EraseFromParentAsFunction()
|
dummyFunc.EraseFromParentAsFunction()
|
||||||
|
|
||||||
// Prefix the GOPATH with the system GOROOT, as GOROOT is already set to
|
|
||||||
// the TinyGo root.
|
|
||||||
overlayGopath := goenv.Get("GOPATH")
|
|
||||||
if overlayGopath == "" {
|
|
||||||
overlayGopath = goenv.Get("GOROOT")
|
|
||||||
} else {
|
|
||||||
overlayGopath = goenv.Get("GOROOT") + string(filepath.ListSeparator) + overlayGopath
|
|
||||||
}
|
|
||||||
|
|
||||||
wd, err := os.Getwd()
|
wd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.mod, nil, []error{err}
|
return c.mod, nil, []error{err}
|
||||||
}
|
}
|
||||||
|
goroot, err := loader.GetCachedGoroot(c.Config)
|
||||||
|
if err != nil {
|
||||||
|
return c.mod, nil, []error{err}
|
||||||
|
}
|
||||||
lprogram := &loader.Program{
|
lprogram := &loader.Program{
|
||||||
Build: &build.Context{
|
Build: &build.Context{
|
||||||
GOARCH: c.GOARCH(),
|
GOARCH: c.GOARCH(),
|
||||||
GOOS: c.GOOS(),
|
GOOS: c.GOOS(),
|
||||||
GOROOT: goenv.Get("GOROOT"),
|
GOROOT: goroot,
|
||||||
GOPATH: goenv.Get("GOPATH"),
|
GOPATH: goenv.Get("GOPATH"),
|
||||||
CgoEnabled: c.CgoEnabled(),
|
CgoEnabled: c.CgoEnabled(),
|
||||||
UseAllFiles: false,
|
UseAllFiles: false,
|
||||||
Compiler: "gc", // must be one of the recognized compilers
|
Compiler: "gc", // must be one of the recognized compilers
|
||||||
BuildTags: c.BuildTags(),
|
BuildTags: c.BuildTags(),
|
||||||
},
|
},
|
||||||
OverlayBuild: &build.Context{
|
Tests: c.TestConfig.CompileTestBinary,
|
||||||
GOARCH: c.GOARCH(),
|
|
||||||
GOOS: c.GOOS(),
|
|
||||||
GOROOT: goenv.Get("TINYGOROOT"),
|
|
||||||
GOPATH: overlayGopath,
|
|
||||||
CgoEnabled: c.CgoEnabled(),
|
|
||||||
UseAllFiles: false,
|
|
||||||
Compiler: "gc", // must be one of the recognized compilers
|
|
||||||
BuildTags: c.BuildTags(),
|
|
||||||
},
|
|
||||||
OverlayPath: func(path string) string {
|
|
||||||
// Return the (overlay) import path when it should be overlaid, and
|
|
||||||
// "" if it should not.
|
|
||||||
if strings.HasPrefix(path, tinygoPath+"/src/") {
|
|
||||||
// Avoid issues with packages that are imported twice, one from
|
|
||||||
// GOPATH and one from TINYGOPATH.
|
|
||||||
path = path[len(tinygoPath+"/src/"):]
|
|
||||||
}
|
|
||||||
switch path {
|
|
||||||
case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "sync", "testing", "internal/reflectlite", "internal/task":
|
|
||||||
return path
|
|
||||||
default:
|
|
||||||
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
|
|
||||||
return path
|
|
||||||
} else if path == "syscall" {
|
|
||||||
for _, tag := range c.BuildTags() {
|
|
||||||
if tag == "baremetal" || tag == "darwin" {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
},
|
|
||||||
TypeChecker: types.Config{
|
TypeChecker: types.Config{
|
||||||
Sizes: &stdSizes{
|
Sizes: &stdSizes{
|
||||||
IntSize: int64(c.targetData.TypeAllocSize(c.intType)),
|
IntSize: int64(c.targetData.TypeAllocSize(c.intType)),
|
||||||
@@ -208,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()
|
||||||
@@ -378,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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package goenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetGorootVersion returns the major and minor version for a given GOROOT path.
|
||||||
|
// If the goroot cannot be determined, (0, 0) is returned.
|
||||||
|
func GetGorootVersion(goroot string) (major, minor int, err error) {
|
||||||
|
s, err := GorootVersionString(goroot)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == "" || s[:2] != "go" {
|
||||||
|
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(s[2:], ".")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore the errors, we don't really handle errors here anyway.
|
||||||
|
var trailing string
|
||||||
|
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
|
||||||
|
if n == 2 && err == io.EOF {
|
||||||
|
// Means there were no trailing characters (i.e., not an alpha/beta)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GorootVersionString returns the version string as reported by the Go
|
||||||
|
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
|
||||||
|
// can have some variations (for beta releases, for example).
|
||||||
|
func GorootVersionString(goroot string) (string, error) {
|
||||||
|
if data, err := ioutil.ReadFile(filepath.Join(
|
||||||
|
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
|
||||||
|
|
||||||
|
r := regexp.MustCompile("const TheVersion = `(.*)`")
|
||||||
|
matches := r.FindSubmatch(data)
|
||||||
|
if len(matches) != 2 {
|
||||||
|
return "", errors.New("Invalid go version output:\n" + string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(matches[1]), nil
|
||||||
|
|
||||||
|
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
|
||||||
|
return string(data), nil
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
package loader
|
||||||
|
|
||||||
|
// This file constructs a new temporary GOROOT directory by merging both the
|
||||||
|
// standard Go GOROOT and the GOROOT from TinyGo using symlinks.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetCachedGoroot creates a new GOROOT by merging both the standard GOROOT and
|
||||||
|
// the GOROOT from TinyGo using lots of symbolic links.
|
||||||
|
func GetCachedGoroot(config *compileopts.Config) (string, error) {
|
||||||
|
goroot := goenv.Get("GOROOT")
|
||||||
|
if goroot == "" {
|
||||||
|
return "", errors.New("could not determine GOROOT")
|
||||||
|
}
|
||||||
|
tinygoroot := goenv.Get("TINYGOROOT")
|
||||||
|
if tinygoroot == "" {
|
||||||
|
return "", errors.New("could not determine TINYGOROOT")
|
||||||
|
}
|
||||||
|
|
||||||
|
needsSyscallPackage := false
|
||||||
|
for _, tag := range config.BuildTags() {
|
||||||
|
if tag == "baremetal" || tag == "darwin" {
|
||||||
|
needsSyscallPackage = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the location of the cached GOROOT.
|
||||||
|
version, err := goenv.GorootVersionString(goroot)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
gorootsHash := sha512.Sum512_256([]byte(goroot + "\x00" + tinygoroot))
|
||||||
|
gorootsHashHex := hex.EncodeToString(gorootsHash[:])
|
||||||
|
cachedgoroot := filepath.Join(goenv.Get("GOCACHE"), "goroot-"+version+"-"+gorootsHashHex)
|
||||||
|
if needsSyscallPackage {
|
||||||
|
cachedgoroot += "-syscall"
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(cachedgoroot); err == nil {
|
||||||
|
return cachedgoroot, nil
|
||||||
|
}
|
||||||
|
tmpgoroot := cachedgoroot + ".tmp" + strconv.Itoa(rand.Int())
|
||||||
|
err = os.MkdirAll(tmpgoroot, 0777)
|
||||||
|
if err != nil {
|
||||||
|
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"} {
|
||||||
|
err = symlink(filepath.Join(goroot, name), filepath.Join(tmpgoroot, name))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = mergeDirectory(goroot, tinygoroot, tmpgoroot, "", pathsToOverride(needsSyscallPackage))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
err = os.Rename(tmpgoroot, cachedgoroot)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsExist(err) {
|
||||||
|
// Another invocation of TinyGo also seems to have created a GOROOT.
|
||||||
|
// Use that one instead. Our new GOROOT will be automatically
|
||||||
|
// deleted by the defer above.
|
||||||
|
return cachedgoroot, nil
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cachedgoroot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeDirectory merges two roots recursively. The tmpgoroot is the directory
|
||||||
|
// that will be created by this call by either symlinking the directory from
|
||||||
|
// goroot or tinygoroot, or by creating the directory and merging the contents.
|
||||||
|
func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides map[string]bool) error {
|
||||||
|
if mergeSubdirs, ok := overrides[importPath+"/"]; ok {
|
||||||
|
if !mergeSubdirs {
|
||||||
|
// This directory and all subdirectories should come from the TinyGo
|
||||||
|
// root, so simply make a symlink.
|
||||||
|
newname := filepath.Join(tmpgoroot, "src", importPath)
|
||||||
|
oldname := filepath.Join(tinygoroot, "src", importPath)
|
||||||
|
return symlink(oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge subdirectories. Start by making the directory to merge.
|
||||||
|
err := os.Mkdir(filepath.Join(tmpgoroot, "src", importPath), 0777)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symlink all files from TinyGo, and symlink directories from TinyGo
|
||||||
|
// that need to be overridden.
|
||||||
|
tinygoEntries, err := ioutil.ReadDir(filepath.Join(tinygoroot, "src", importPath))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, e := range tinygoEntries {
|
||||||
|
if e.IsDir() {
|
||||||
|
// A directory, so merge this thing.
|
||||||
|
err := mergeDirectory(goroot, tinygoroot, tmpgoroot, path.Join(importPath, e.Name()), overrides)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// A file, so symlink this.
|
||||||
|
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
|
||||||
|
oldname := filepath.Join(tinygoroot, "src", importPath, e.Name())
|
||||||
|
err := symlink(oldname, newname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symlink all directories from $GOROOT that are not part of the TinyGo
|
||||||
|
// overrides.
|
||||||
|
gorootEntries, err := ioutil.ReadDir(filepath.Join(goroot, "src", importPath))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, e := range gorootEntries {
|
||||||
|
if !e.IsDir() {
|
||||||
|
// Don't merge in files from Go. Otherwise we'd end up with a
|
||||||
|
// weird syscall package with files from both roots.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := overrides[path.Join(importPath, e.Name())+"/"]; ok {
|
||||||
|
// Already included above, so don't bother trying to create this
|
||||||
|
// symlink.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
|
||||||
|
oldname := filepath.Join(goroot, "src", importPath, e.Name())
|
||||||
|
err := symlink(oldname, newname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The boolean indicates whether to merge the subdirs. True means merge, false
|
||||||
|
// means use the TinyGo version.
|
||||||
|
func pathsToOverride(needsSyscallPackage bool) map[string]bool {
|
||||||
|
paths := map[string]bool{
|
||||||
|
"/": true,
|
||||||
|
"device/": false,
|
||||||
|
"examples/": false,
|
||||||
|
"internal/": true,
|
||||||
|
"internal/reflectlite/": false,
|
||||||
|
"internal/task/": false,
|
||||||
|
"machine/": false,
|
||||||
|
"os/": true,
|
||||||
|
"reflect/": false,
|
||||||
|
"runtime/": false,
|
||||||
|
"sync/": true,
|
||||||
|
"testing/": false,
|
||||||
|
}
|
||||||
|
if needsSyscallPackage {
|
||||||
|
paths["syscall/"] = true // include syscall/js
|
||||||
|
}
|
||||||
|
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
-219
@@ -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,15 +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
|
||||||
OverlayBuild *build.Context
|
Tests bool
|
||||||
OverlayPath func(path string) string
|
|
||||||
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
|
||||||
@@ -37,89 +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
|
|
||||||
if newPath := p.OverlayPath(path); newPath != "" {
|
|
||||||
ctx = p.OverlayBuild
|
|
||||||
path = newPath
|
|
||||||
}
|
|
||||||
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),
|
||||||
@@ -134,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
|
||||||
}
|
}
|
||||||
@@ -231,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 {
|
||||||
@@ -241,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:
|
||||||
@@ -293,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()
|
||||||
@@ -303,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
|
||||||
}
|
}
|
||||||
@@ -319,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
|
||||||
}
|
}
|
||||||
@@ -373,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
|
||||||
@@ -385,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
|
||||||
@@ -409,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)
|
||||||
}
|
}
|
||||||
@@ -445,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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -124,6 +125,7 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
|
|||||||
|
|
||||||
// Test runs the tests in the given package.
|
// Test runs the tests in the given package.
|
||||||
func Test(pkgName string, options *compileopts.Options) error {
|
func Test(pkgName string, options *compileopts.Options) error {
|
||||||
|
options.TestConfig.CompileTestBinary = true
|
||||||
config, err := builder.NewConfig(options)
|
config, err := builder.NewConfig(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -135,7 +137,6 @@ func Test(pkgName string, options *compileopts.Options) error {
|
|||||||
// For details: https://github.com/golang/go/issues/21360
|
// For details: https://github.com/golang/go/issues/21360
|
||||||
config.Target.BuildTags = append(config.Target.BuildTags, "test")
|
config.Target.BuildTags = append(config.Target.BuildTags, "test")
|
||||||
|
|
||||||
options.TestConfig.CompileTestBinary = true
|
|
||||||
return builder.Build(pkgName, ".elf", config, func(tmppath string) error {
|
return builder.Build(pkgName, ".elf", config, func(tmppath string) error {
|
||||||
cmd := exec.Command(tmppath)
|
cmd := exec.Command(tmppath)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
@@ -641,6 +642,36 @@ func getDefaultPort() (port string, err error) {
|
|||||||
return d[0], nil
|
return d[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runGoList runs the `go list` command but using the configuration used for
|
||||||
|
// TinyGo.
|
||||||
|
func runGoList(config *compileopts.Config, flagJSON, flagDeps bool, pkgs []string) error {
|
||||||
|
goroot, err := loader.GetCachedGoroot(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
args := []string{"list"}
|
||||||
|
if flagJSON {
|
||||||
|
args = append(args, "-json")
|
||||||
|
}
|
||||||
|
if flagDeps {
|
||||||
|
args = append(args, "-deps")
|
||||||
|
}
|
||||||
|
if len(config.BuildTags()) != 0 {
|
||||||
|
args = append(args, "-tags", strings.Join(config.BuildTags(), " "))
|
||||||
|
}
|
||||||
|
args = append(args, pkgs...)
|
||||||
|
cgoEnabled := "0"
|
||||||
|
if config.CgoEnabled() {
|
||||||
|
cgoEnabled = "1"
|
||||||
|
}
|
||||||
|
cmd := exec.Command("go", args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = append(os.Environ(), "GOROOT="+goroot, "GOOS="+config.GOOS(), "GOARCH="+config.GOARCH(), "CGO_ENABLED="+cgoEnabled)
|
||||||
|
cmd.Run()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
|
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
|
||||||
fmt.Fprintln(os.Stderr, "version:", version)
|
fmt.Fprintln(os.Stderr, "version:", version)
|
||||||
@@ -652,6 +683,7 @@ func usage() {
|
|||||||
fmt.Fprintln(os.Stderr, " flash: compile and flash to the device")
|
fmt.Fprintln(os.Stderr, " flash: compile and flash to the device")
|
||||||
fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB")
|
fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB")
|
||||||
fmt.Fprintln(os.Stderr, " env: list environment variables used during build")
|
fmt.Fprintln(os.Stderr, " env: list environment variables used during build")
|
||||||
|
fmt.Fprintln(os.Stderr, " list: run go list using the TinyGo root")
|
||||||
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+goenv.Get("GOCACHE")+")")
|
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+goenv.Get("GOCACHE")+")")
|
||||||
fmt.Fprintln(os.Stderr, " help: print this help text")
|
fmt.Fprintln(os.Stderr, " help: print this help text")
|
||||||
fmt.Fprintln(os.Stderr, "\nflags:")
|
fmt.Fprintln(os.Stderr, "\nflags:")
|
||||||
@@ -683,7 +715,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)
|
||||||
}
|
}
|
||||||
@@ -706,6 +738,13 @@ func handleCompilerError(err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
|
||||||
|
usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
command := os.Args[1]
|
||||||
|
|
||||||
outpath := flag.String("o", "", "output filename")
|
outpath := flag.String("o", "", "output filename")
|
||||||
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
||||||
gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)")
|
gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)")
|
||||||
@@ -726,12 +765,13 @@ func main() {
|
|||||||
wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
|
wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
|
||||||
heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
|
heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
|
||||||
|
|
||||||
if len(os.Args) < 2 {
|
var flagJSON, flagDeps *bool
|
||||||
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
|
if command == "list" || command == "env" {
|
||||||
usage()
|
flagJSON = flag.Bool("json", false, "print data in JSON format")
|
||||||
os.Exit(1)
|
}
|
||||||
|
if command == "list" {
|
||||||
|
flagDeps = flag.Bool("deps", false, "")
|
||||||
}
|
}
|
||||||
command := os.Args[1]
|
|
||||||
|
|
||||||
// Early command processing, before commands are interpreted by the Go flag
|
// Early command processing, before commands are interpreted by the Go flag
|
||||||
// library.
|
// library.
|
||||||
@@ -895,6 +935,18 @@ func main() {
|
|||||||
fmt.Printf("build tags: %s\n", strings.Join(config.BuildTags(), " "))
|
fmt.Printf("build tags: %s\n", strings.Join(config.BuildTags(), " "))
|
||||||
fmt.Printf("garbage collector: %s\n", config.GC())
|
fmt.Printf("garbage collector: %s\n", config.GC())
|
||||||
fmt.Printf("scheduler: %s\n", config.Scheduler())
|
fmt.Printf("scheduler: %s\n", config.Scheduler())
|
||||||
|
case "list":
|
||||||
|
config, err := builder.NewConfig(options)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err = runGoList(config, *flagJSON, *flagDeps, flag.Args())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "failed to run `go list`:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
case "clean":
|
case "clean":
|
||||||
// remove cache directory
|
// remove cache directory
|
||||||
err := os.RemoveAll(goenv.Get("GOCACHE"))
|
err := os.RemoveAll(goenv.Get("GOCACHE"))
|
||||||
@@ -906,20 +958,36 @@ func main() {
|
|||||||
usage()
|
usage()
|
||||||
case "version":
|
case "version":
|
||||||
goversion := "<unknown>"
|
goversion := "<unknown>"
|
||||||
if s, err := builder.GorootVersionString(goenv.Get("GOROOT")); err == nil {
|
if s, err := goenv.GorootVersionString(goenv.Get("GOROOT")); err == nil {
|
||||||
goversion = s
|
goversion = s
|
||||||
}
|
}
|
||||||
fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion, llvm.Version)
|
fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion, llvm.Version)
|
||||||
case "env":
|
case "env":
|
||||||
if flag.NArg() == 0 {
|
if *flagJSON {
|
||||||
// Show all environment variables.
|
keys := goenv.Keys
|
||||||
for _, key := range goenv.Keys {
|
if flag.NArg() != 0 {
|
||||||
fmt.Printf("%s=%#v\n", key, goenv.Get(key))
|
// Show only one (or a few) environment variables.
|
||||||
|
keys = flag.Args()
|
||||||
}
|
}
|
||||||
|
// Show environment variables in JSON format.
|
||||||
|
env := make(map[string]string)
|
||||||
|
for _, key := range keys {
|
||||||
|
env[key] = goenv.Get(key)
|
||||||
|
}
|
||||||
|
encoder := json.NewEncoder(os.Stdout)
|
||||||
|
encoder.SetIndent("", "\t")
|
||||||
|
encoder.Encode(env)
|
||||||
} else {
|
} else {
|
||||||
// Show only one (or a few) environment variables.
|
if flag.NArg() == 0 {
|
||||||
for i := 0; i < flag.NArg(); i++ {
|
// Show all environment variables.
|
||||||
fmt.Println(goenv.Get(flag.Arg(i)))
|
for _, key := range goenv.Keys {
|
||||||
|
fmt.Printf("%s=%#v\n", key, goenv.Get(key))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Show only one (or a few) environment variables.
|
||||||
|
for i := 0; i < flag.NArg(); i++ {
|
||||||
|
fmt.Println(goenv.Get(flag.Arg(i)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
+1
-1
@@ -73,7 +73,7 @@ func TestCompiler(t *testing.T) {
|
|||||||
t.Run("ARM64Linux", func(t *testing.T) {
|
t.Run("ARM64Linux", func(t *testing.T) {
|
||||||
runPlatTests("aarch64--linux-gnu", matches, t)
|
runPlatTests("aarch64--linux-gnu", matches, t)
|
||||||
})
|
})
|
||||||
goVersion, err := builder.GorootVersionString(goenv.Get("GOROOT"))
|
goVersion, err := goenv.GorootVersionString(goenv.Get("GOROOT"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("could not get Go version:", err)
|
t.Error("could not get Go version:", err)
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user