From c3c09e9d24e19c3d47896bebfc21a54ae4412078 Mon Sep 17 00:00:00 2001 From: ardnew Date: Sun, 15 Mar 2020 16:07:14 -0500 Subject: [PATCH 1/2] consider GOROOT/api as fallback to determine Go version --- builder/env.go | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/builder/env.go b/builder/env.go index b28173c44b..91475aa937 100644 --- a/builder/env.go +++ b/builder/env.go @@ -1,16 +1,123 @@ package builder import ( + "errors" + "fmt" + "io" "io/ioutil" "os" "os/exec" "path/filepath" + "regexp" "sort" "strings" "tinygo.org/x/go-llvm" ) +// parseGorootVersion returns the major and minor version for a given Go version +// string of the form `goX.Y.Z`. +// Returns (0, 0) if the version cannot be determined. +func parseGorootVersion(version string) (int, int, error) { + var ( + maj, min int + pch string + ) + n, err := fmt.Sscanf(version, "go%d.%d%s", &maj, &min, &pch) + if n == 2 && io.EOF == err { + // Means there were no trailing characters (i.e., not an alpha/beta) + err = nil + } + if nil != err { + return 0, 0, fmt.Errorf("failed to parse version: %s", err) + } + return maj, min, nil +} + +// getGorootVersion returns the major and minor version for a given GOROOT path. +// If the version cannot be determined, (0, 0) is returned. +func getGorootVersion(goroot string) (int, int, error) { + const errPrefix = "could not parse Go version" + s, err := GorootVersionString(goroot) + if err != nil { + return 0, 0, err + } + + if "" == s { + return 0, 0, fmt.Errorf("%s: version string is empty", errPrefix) + } + + if strings.HasPrefix(s, "devel") { + maj, min, err := getGorootApiVersion(goroot) + if nil != err { + return 0, 0, fmt.Errorf("%s: invalid GOROOT API version: %s", errPrefix, err) + } + return maj, min, nil + } + + if !strings.HasPrefix(s, "go") { + return 0, 0, fmt.Errorf("%s: version does not start with 'go' prefix", errPrefix) + } + + parts := strings.Split(s[2:], ".") + if len(parts) < 2 { + return 0, 0, fmt.Errorf("%s: version has less than two parts", errPrefix) + } + + return parseGorootVersion(s) +} + +// getGorootApiVersion returns the major and minor version of the Go API files +// defined for a given GOROOT path. +// If the version cannot be determined, (0, 0) is returned. +func getGorootApiVersion(goroot string) (int, int, error) { + info, err := ioutil.ReadDir(filepath.Join(goroot, "api")) + if nil != err { + return 0, 0, fmt.Errorf("could not read API feature directory: %s", err) + } + maj, min := -1, -1 + for _, f := range info { + if !strings.HasPrefix(f.Name(), "go") || f.IsDir() { + continue + } + vers := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name())) + part := strings.Split(vers[2:], ".") + if len(part) < 2 { + continue + } + vmaj, vmin, err := parseGorootVersion(vers) + if nil != err { + continue + } + if vmaj >= maj && vmin > min { + maj, min = vmaj, vmin + } + } + if maj < 0 || min < 0 { + return 0, 0, errors.New("no valid API feature files") + } + return maj, min, nil +} + +// 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. From ab4ba0150b0cfc86679176952e2458b5f37b021e Mon Sep 17 00:00:00 2001 From: ardnew Date: Fri, 15 Apr 2022 14:50:32 -0500 Subject: [PATCH 2/2] add GOROOT/api eval fallback logic to goenv --- builder/env.go | 2 +- goenv/version.go | 82 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/builder/env.go b/builder/env.go index 91475aa937..9d2c17b79c 100644 --- a/builder/env.go +++ b/builder/env.go @@ -56,7 +56,7 @@ func getGorootVersion(goroot string) (int, int, error) { } if !strings.HasPrefix(s, "go") { - return 0, 0, fmt.Errorf("%s: version does not start with 'go' prefix", errPrefix) + return 0, 0, fmt.Errorf("%s: [%s] version does not start with 'go' prefix", s, errPrefix) } parts := strings.Split(s[2:], ".") diff --git a/goenv/version.go b/goenv/version.go index 451fbe8821..e4b6dc7aa2 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -20,34 +20,88 @@ var ( GitSha1 string ) +// parseGorootVersion returns the major and minor version for a given Go version +// string of the form `goX.Y.Z`. +// Returns (0, 0) if the version cannot be determined. +func parseGorootVersion(version string) (int, int, error) { + var ( + maj, min int + pch string + ) + n, err := fmt.Sscanf(version, "go%d.%d%s", &maj, &min, &pch) + if n == 2 && io.EOF == err { + // Means there were no trailing characters (i.e., not an alpha/beta) + err = nil + } + if nil != err { + return 0, 0, fmt.Errorf("failed to parse version: %s", err) + } + return maj, min, 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) { +// If the version cannot be determined, (0, 0) is returned. +func GetGorootVersion(goroot string) (int, int, error) { + const errPrefix = "could not parse Go version" 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") + if "" == s { + return 0, 0, fmt.Errorf("%s: version string is empty", errPrefix) + } + + if strings.HasPrefix(s, "devel") { + maj, min, err := getGorootApiVersion(goroot) + if nil != err { + return 0, 0, fmt.Errorf("%s: invalid GOROOT API version: %s", errPrefix, err) + } + return maj, min, nil + } + + if !strings.HasPrefix(s, "go") { + return 0, 0, fmt.Errorf("%s: version does not start with 'go' prefix", errPrefix) } 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") + return 0, 0, fmt.Errorf("%s: version has less than two parts", errPrefix) } - // 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 + return parseGorootVersion(s) +} + +// getGorootApiVersion returns the major and minor version of the Go API files +// defined for a given GOROOT path. +// If the version cannot be determined, (0, 0) is returned. +func getGorootApiVersion(goroot string) (int, int, error) { + info, err := ioutil.ReadDir(filepath.Join(goroot, "api")) + if nil != err { + return 0, 0, fmt.Errorf("could not read API feature directory: %s", err) } - if err != nil { - return 0, 0, fmt.Errorf("failed to parse version: %s", err) + maj, min := -1, -1 + for _, f := range info { + if !strings.HasPrefix(f.Name(), "go") || f.IsDir() { + continue + } + vers := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name())) + part := strings.Split(vers[2:], ".") + if len(part) < 2 { + continue + } + vmaj, vmin, err := parseGorootVersion(vers) + if nil != err { + continue + } + if vmaj >= maj && vmin > min { + maj, min = vmaj, vmin + } + } + if maj < 0 || min < 0 { + return 0, 0, errors.New("no valid API feature files") } - return + return maj, min, nil } // GorootVersionString returns the version string as reported by the Go