loader: handle go list errors inside TinyGo

Instead of exiting with an error, handle these errors internally.
This will enable a few improvements in the future.
This commit is contained in:
Ayke van Laethem
2024-07-09 18:08:51 +02:00
committed by Ayke
parent b04b690842
commit d7773d3e86
9 changed files with 95 additions and 9 deletions
+17 -1
View File
@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"testing" "testing"
"time" "time"
@@ -16,6 +17,10 @@ import (
func TestErrors(t *testing.T) { func TestErrors(t *testing.T) {
for _, name := range []string{ for _, name := range []string{
"cgo", "cgo",
"loader-importcycle",
"loader-invaliddep",
"loader-invalidpackage",
"loader-nopackage",
"syntax", "syntax",
"types", "types",
} { } {
@@ -57,11 +62,22 @@ func testErrorMessages(t *testing.T, filename string) {
actual := strings.TrimRight(buf.String(), "\n") actual := strings.TrimRight(buf.String(), "\n")
// Check whether the error is as expected. // Check whether the error is as expected.
if actual != expected { if canonicalizeErrors(actual) != canonicalizeErrors(expected) {
t.Errorf("expected error:\n%s\ngot:\n%s", indentText(expected, "> "), indentText(actual, "> ")) t.Errorf("expected error:\n%s\ngot:\n%s", indentText(expected, "> "), indentText(actual, "> "))
} }
} }
func canonicalizeErrors(text string) string {
// Fix for Windows: replace all backslashes with forward slashes so that
// paths will be the same as on POSIX systems.
// (It may also change some other backslashes, but since this is only for
// comparing text it should be fine).
if runtime.GOOS == "windows" {
text = strings.ReplaceAll(text, "\\", "/")
}
return text
}
// Indent the given text with a given indentation string. // Indent the given text with a given indentation string.
func indentText(text, indent string) string { func indentText(text, indent string) string {
return indent + strings.ReplaceAll(text, "\n", "\n"+indent) return indent + strings.ReplaceAll(text, "\n", "\n"+indent)
+18 -3
View File
@@ -128,7 +128,7 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config)
} }
// List the dependencies of this package, in raw JSON format. // List the dependencies of this package, in raw JSON format.
extraArgs := []string{"-json", "-deps"} extraArgs := []string{"-json", "-deps", "-e"}
if config.TestConfig.CompileTestBinary { if config.TestConfig.CompileTestBinary {
extraArgs = append(extraArgs, "-test") extraArgs = append(extraArgs, "-test")
} }
@@ -149,6 +149,7 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config)
// Parse the returned json from `go list`. // Parse the returned json from `go list`.
decoder := json.NewDecoder(buf) decoder := json.NewDecoder(buf)
var pkgErrors []error
for { for {
pkg := &Package{ pkg := &Package{
program: p, program: p,
@@ -188,6 +189,12 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config)
pos.Filename = strings.Join(fields[:len(fields)-1], ":") pos.Filename = strings.Join(fields[:len(fields)-1], ":")
pos.Line, _ = strconv.Atoi(fields[len(fields)-1]) pos.Line, _ = strconv.Atoi(fields[len(fields)-1])
} }
if abs, err := filepath.Abs(pos.Filename); err == nil {
// Make the path absolute, so that error messages will be
// prettier (it will be turned back into a relative path
// when printing the error).
pos.Filename = abs
}
pos.Filename = p.getOriginalPath(pos.Filename) pos.Filename = p.getOriginalPath(pos.Filename)
} }
err := scanner.Error{ err := scanner.Error{
@@ -195,10 +202,11 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config)
Msg: pkg.Error.Err, Msg: pkg.Error.Err,
} }
if len(pkg.Error.ImportStack) != 0 { if len(pkg.Error.ImportStack) != 0 {
return nil, Error{ pkgErrors = append(pkgErrors, Error{
ImportStack: pkg.Error.ImportStack, ImportStack: pkg.Error.ImportStack,
Err: err, Err: err,
} })
continue
} }
return nil, err return nil, err
} }
@@ -241,6 +249,13 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config)
p.Packages[pkg.ImportPath] = pkg p.Packages[pkg.ImportPath] = pkg
} }
if len(pkgErrors) != 0 {
// TODO: use errors.Join in Go 1.20.
return nil, Errors{
Errs: pkgErrors,
}
}
if config.TestConfig.CompileTestBinary && !strings.HasSuffix(p.sorted[len(p.sorted)-1].ImportPath, ".test") { if config.TestConfig.CompileTestBinary && !strings.HasSuffix(p.sorted[len(p.sorted)-1].ImportPath, ".test") {
// Trying to compile a test binary but there are no test files in this // Trying to compile a test binary but there are no test files in this
// package. // package.
+21 -5
View File
@@ -1340,15 +1340,31 @@ func printCompilerError(err error, logln func(...interface{}), wd string) {
} }
} }
case loader.Errors: case loader.Errors:
logln("#", err.Pkg.ImportPath) // Parser errors, typechecking errors, or `go list` errors.
// err.Pkg is nil for `go list` errors.
if err.Pkg != nil {
logln("#", err.Pkg.ImportPath)
}
for _, err := range err.Errs { for _, err := range err.Errs {
printCompilerError(err, logln, wd) printCompilerError(err, logln, wd)
} }
case loader.Error: case loader.Error:
logln(err.Err.Error()) if err.Err.Pos.Filename != "" {
logln("package", err.ImportStack[0]) // Probably a syntax error in a dependency.
for _, pkgPath := range err.ImportStack[1:] { printCompilerError(err.Err, logln, wd)
logln("\timports", pkgPath) } else {
// Probably an "import cycle not allowed" error.
logln("package", err.ImportStack[0])
for i := 1; i < len(err.ImportStack); i++ {
pkgPath := err.ImportStack[i]
if i == len(err.ImportStack)-1 {
// last package
logln("\timports", pkgPath+": "+err.Err.Error())
} else {
// not the last pacakge
logln("\timports", pkgPath)
}
}
} }
case *builder.MultiError: case *builder.MultiError:
for _, err := range err.Errs { for _, err := range err.Errs {
+3
View File
@@ -0,0 +1,3 @@
package importcycle
import _ "github.com/tinygo-org/tinygo/testdata/errors/importcycle"
+1
View File
@@ -0,0 +1 @@
ppackage // syntax error
+10
View File
@@ -0,0 +1,10 @@
package main
import _ "github.com/tinygo-org/tinygo/testdata/errors/importcycle"
func main() {
}
// ERROR: package command-line-arguments
// ERROR: imports github.com/tinygo-org/tinygo/testdata/errors/importcycle
// ERROR: imports github.com/tinygo-org/tinygo/testdata/errors/importcycle: import cycle not allowed
+8
View File
@@ -0,0 +1,8 @@
package main
import _ "github.com/tinygo-org/tinygo/testdata/errors/invaliddep"
func main() {
}
// ERROR: invaliddep/invaliddep.go:1:1: expected 'package', found ppackage
+3
View File
@@ -0,0 +1,3 @@
ppackage // syntax error
// ERROR: loader-invalidpackage.go:1:1: expected 'package', found ppackage
+14
View File
@@ -0,0 +1,14 @@
package main
import (
_ "github.com/tinygo-org/tinygo/testdata/errors/non-existing-package"
_ "github.com/tinygo-org/tinygo/testdata/errors/non-existing-package-2"
)
func main() {
}
// ERROR: loader-nopackage.go:4:2: no required module provides package github.com/tinygo-org/tinygo/testdata/errors/non-existing-package; to add it:
// ERROR: go get github.com/tinygo-org/tinygo/testdata/errors/non-existing-package
// ERROR: loader-nopackage.go:5:2: no required module provides package github.com/tinygo-org/tinygo/testdata/errors/non-existing-package-2; to add it:
// ERROR: go get github.com/tinygo-org/tinygo/testdata/errors/non-existing-package-2