Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go module support #941

Merged
merged 5 commits into from
May 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -354,4 +354,4 @@ deb: build/release
@mkdir -p build/release-deb/usr/local/lib
cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo
ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo
fpm -f -s dir -t deb -n tinygo -v $(shell grep "version = " version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb
fpm -f -s dir -t deb -n tinygo -v $(shell grep "const Version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb
2 changes: 1 addition & 1 deletion builder/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if goroot == "" {
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 {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
Expand Down
58 changes: 0 additions & 58 deletions builder/env.go
Original file line number Diff line number Diff line change
@@ -1,71 +1,13 @@
package builder

import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"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
// multiple locations, which should make it find the directory when installed in
// various ways.
Expand Down
79 changes: 14 additions & 65 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,64 +137,26 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
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()
if err != nil {
return c.mod, nil, nil, []error{err}
}
goroot, err := loader.GetCachedGoroot(c.Config)
if err != nil {
return c.mod, nil, nil, []error{err}
}
lprogram := &loader.Program{
Build: &build.Context{
GOARCH: c.GOARCH(),
GOOS: c.GOOS(),
GOROOT: goenv.Get("GOROOT"),
GOROOT: goroot,
GOPATH: goenv.Get("GOPATH"),
CgoEnabled: c.CgoEnabled(),
UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers
BuildTags: c.BuildTags(),
},
OverlayBuild: &build.Context{
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/bytealg", "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 ""
},
Tests: c.TestConfig.CompileTestBinary,
TypeChecker: types.Config{
Sizes: &stdSizes{
IntSize: int64(c.targetData.TypeAllocSize(c.intType)),
Expand All @@ -208,33 +170,17 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
ClangHeaders: c.ClangHeaders,
}

if strings.HasSuffix(pkgName, ".go") {
_, err = lprogram.ImportFile(pkgName)
if err != nil {
return c.mod, nil, nil, []error{err}
}
} else {
_, err = lprogram.Import(pkgName, wd, token.Position{
Filename: "build command-line-arguments",
})
if err != nil {
return c.mod, nil, nil, []error{err}
}
}

_, err = lprogram.Import("runtime", "", token.Position{
Filename: "build default import",
})
err = lprogram.Load(pkgName)
if err != nil {
return c.mod, nil, nil, []error{err}
}

err = lprogram.Parse(c.TestConfig.CompileTestBinary)
err = lprogram.Parse()
if err != nil {
return c.mod, nil, nil, []error{err}
}

c.ir = ir.NewProgram(lprogram, pkgName)
c.ir = ir.NewProgram(lprogram)

// Run a simple dead code elimination pass.
err = c.ir.SimpleDCE()
Expand Down Expand Up @@ -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.
var extraFiles []string
for _, pkg := range c.ir.LoaderProgram.Sorted() {
for _, file := range pkg.CFiles {
extraFiles = append(extraFiles, filepath.Join(pkg.Package.Dir, file))
for _, file := range pkg.OtherFiles {
switch strings.ToLower(filepath.Ext(file)) {
case ".c":
extraFiles = append(extraFiles, file)
}
}
}

Expand Down
68 changes: 68 additions & 0 deletions goenv/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package goenv

import (
"errors"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
)

// Version of TinyGo.
// Update this value before release of new version of software.
const Version = "0.14.0-dev"

// 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
}
}
67 changes: 4 additions & 63 deletions ir/ir.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,82 +63,23 @@ const (
)

// 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.Build()

// Find the main package, which is a bit difficult when running a .go file
// directly.
mainPkg := program.ImportedPackage(mainPath)
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
}
}
}
mainPkg := program.ImportedPackage(lprogram.MainPkg.PkgPath)
if mainPkg == nil {
panic("could not find main package")
}

// Make a list of packages in import order.
packageList := []*ssa.Package{}
packageSet := map[string]struct{}{}
worklist := []string{"runtime", mainPath}
for len(worklist) != 0 {
pkgPath := worklist[0]
var pkg *ssa.Package
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 {
// Package already in the final package list.
worklist = worklist[1:]
continue
}

unsatisfiedImports := make([]string, 0)
imports := pkg.Pkg.Imports()
for _, pkg := range imports {
if _, ok := packageSet[pkg.Path()]; ok {
continue
}
unsatisfiedImports = append(unsatisfiedImports, pkg.Path())
}
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 := &Program{
Program: program,
LoaderProgram: lprogram,
mainPkg: mainPkg,
functionMap: make(map[*ssa.Function]*Function),
}

for _, pkg := range packageList {
p.AddPackage(pkg)
for _, pkg := range lprogram.Sorted() {
p.AddPackage(program.ImportedPackage(pkg.PkgPath))
}

return p
Expand Down
Loading