From e7cf75030c6fe8a21ace88be690241f7b16f998d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 31 Oct 2019 15:39:01 +0100 Subject: [PATCH] main: move target specification into a separate package --- .circleci/config.yml | 2 +- Makefile | 2 +- target.go => compileopts/target.go | 107 +---------------- target_test.go => compileopts/target_test.go | 2 +- main.go | 13 ++- main_test.go | 3 +- paths.go | 114 +++++++++++++++++++ 7 files changed, 129 insertions(+), 114 deletions(-) rename target.go => compileopts/target.go (73%) rename target_test.go => compileopts/target_test.go (95%) create mode 100644 paths.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 10210446..1e1952a4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -65,7 +65,7 @@ commands: - go-cache-v2-{{ checksum "go.mod" }} - llvm-source-linux - run: go install . - - run: go test -v ./cgo ./interp ./transform . + - run: go test -v ./cgo ./compileopts ./interp ./transform . - run: make gen-device -j4 - run: make smoketest RISCV=0 - save_cache: diff --git a/Makefile b/Makefile index 3a869c39..2451600c 100644 --- a/Makefile +++ b/Makefile @@ -139,7 +139,7 @@ tinygo: CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm . test: - CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./interp ./transform . + CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./interp ./transform . tinygo-test: cd tests/tinygotest && tinygo test diff --git a/target.go b/compileopts/target.go similarity index 73% rename from target.go rename to compileopts/target.go index 8af138bc..a220e6b2 100644 --- a/target.go +++ b/compileopts/target.go @@ -1,13 +1,13 @@ -package main +package compileopts + +// This file loads a target specification from a JSON file. import ( "encoding/json" "errors" "fmt" "io" - "io/ioutil" "os" - "os/exec" "path/filepath" "regexp" "runtime" @@ -300,104 +300,3 @@ func (spec *TargetSpec) OpenOCDConfiguration() (args []string, err error) { args = append(args, "-f", "target/"+spec.OpenOCDTarget+".cfg") return args, nil } - -// 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 := getGorootVersionString(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 -} - -// getGorootVersionString 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 getGorootVersionString(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. -func getClangHeaderPath(TINYGOROOT string) string { - // Check whether we're running from the source directory. - path := filepath.Join(TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers") - if _, err := os.Stat(path); !os.IsNotExist(err) { - return path - } - - // Check whether we're running from the installation directory. - path = filepath.Join(TINYGOROOT, "lib", "clang", "include") - if _, err := os.Stat(path); !os.IsNotExist(err) { - return path - } - - // It looks like we are built with a system-installed LLVM. Do a last - // attempt: try to use Clang headers relative to the clang binary. - for _, cmdName := range commands["clang"] { - binpath, err := exec.LookPath(cmdName) - if err == nil { - // This should be the command that will also be used by - // execCommand. To avoid inconsistencies, make sure we use the - // headers relative to this command. - binpath, err = filepath.EvalSymlinks(binpath) - if err != nil { - // Unexpected. - return "" - } - // Example executable: - // /usr/lib/llvm-8/bin/clang - // Example include path: - // /usr/lib/llvm-8/lib/clang/8.0.1/include/ - llvmRoot := filepath.Dir(filepath.Dir(binpath)) - clangVersionRoot := filepath.Join(llvmRoot, "lib", "clang") - dirnames, err := ioutil.ReadDir(clangVersionRoot) - if err != nil || len(dirnames) != 1 { - // Unexpected. - return "" - } - return filepath.Join(clangVersionRoot, dirnames[0].Name(), "include") - } - } - - // Could not find it. - return "" -} diff --git a/target_test.go b/compileopts/target_test.go similarity index 95% rename from target_test.go rename to compileopts/target_test.go index 659dcd64..271c20eb 100644 --- a/target_test.go +++ b/compileopts/target_test.go @@ -1,4 +1,4 @@ -package main +package compileopts import "testing" diff --git a/main.go b/main.go index 9f03ff36..009fcc00 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "syscall" "time" + "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compiler" "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/interp" @@ -66,7 +67,7 @@ type BuildConfig struct { } // Helper function for Compiler object. -func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, action func(string) error) error { +func Compile(pkgName, outpath string, spec *compileopts.TargetSpec, config *BuildConfig, action func(string) error) error { if config.gc == "" && spec.GC != "" { config.gc = spec.GC } @@ -335,7 +336,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act } func Build(pkgName, outpath, target string, config *BuildConfig) error { - spec, err := LoadTarget(target) + spec, err := compileopts.LoadTarget(target) if err != nil { return err } @@ -369,7 +370,7 @@ func Build(pkgName, outpath, target string, config *BuildConfig) error { } func Test(pkgName, target string, config *BuildConfig) error { - spec, err := LoadTarget(target) + spec, err := compileopts.LoadTarget(target) if err != nil { return err } @@ -396,7 +397,7 @@ func Test(pkgName, target string, config *BuildConfig) error { } func Flash(pkgName, target, port string, config *BuildConfig) error { - spec, err := LoadTarget(target) + spec, err := compileopts.LoadTarget(target) if err != nil { return err } @@ -503,7 +504,7 @@ func Flash(pkgName, target, port string, config *BuildConfig) error { // Note: this command is expected to execute just before exiting, as it // modifies global state. func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig) error { - spec, err := LoadTarget(target) + spec, err := compileopts.LoadTarget(target) if err != nil { return err } @@ -594,7 +595,7 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig) // Compile and run the given program, directly or in an emulator. func Run(pkgName, target string, config *BuildConfig) error { - spec, err := LoadTarget(target) + spec, err := compileopts.LoadTarget(target) if err != nil { return err } diff --git a/main_test.go b/main_test.go index f0a574bf..02f18363 100644 --- a/main_test.go +++ b/main_test.go @@ -14,6 +14,7 @@ import ( "sort" "testing" + "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/loader" ) @@ -142,7 +143,7 @@ func runTest(path, tmpdir string, target string, t *testing.T) { if target == "" { cmd = exec.Command(binary) } else { - spec, err := LoadTarget(target) + spec, err := compileopts.LoadTarget(target) if err != nil { t.Fatal("failed to load target spec:", err) } diff --git a/paths.go b/paths.go new file mode 100644 index 00000000..70b06768 --- /dev/null +++ b/paths.go @@ -0,0 +1,114 @@ +package main + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "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 := getGorootVersionString(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 +} + +// getGorootVersionString 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 getGorootVersionString(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. +func getClangHeaderPath(TINYGOROOT string) string { + // Check whether we're running from the source directory. + path := filepath.Join(TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers") + if _, err := os.Stat(path); !os.IsNotExist(err) { + return path + } + + // Check whether we're running from the installation directory. + path = filepath.Join(TINYGOROOT, "lib", "clang", "include") + if _, err := os.Stat(path); !os.IsNotExist(err) { + return path + } + + // It looks like we are built with a system-installed LLVM. Do a last + // attempt: try to use Clang headers relative to the clang binary. + for _, cmdName := range commands["clang"] { + binpath, err := exec.LookPath(cmdName) + if err == nil { + // This should be the command that will also be used by + // execCommand. To avoid inconsistencies, make sure we use the + // headers relative to this command. + binpath, err = filepath.EvalSymlinks(binpath) + if err != nil { + // Unexpected. + return "" + } + // Example executable: + // /usr/lib/llvm-8/bin/clang + // Example include path: + // /usr/lib/llvm-8/lib/clang/8.0.1/include/ + llvmRoot := filepath.Dir(filepath.Dir(binpath)) + clangVersionRoot := filepath.Join(llvmRoot, "lib", "clang") + dirnames, err := ioutil.ReadDir(clangVersionRoot) + if err != nil || len(dirnames) != 1 { + // Unexpected. + return "" + } + return filepath.Join(clangVersionRoot, dirnames[0].Name(), "include") + } + } + + // Could not find it. + return "" +}