main: refactor environment variables into a separate package

This makes it possible to query these environment variables from
anywhere, which might be useful. More importantly, it puts them in a
central location from where they can be queried, useful for a `go env`
subcommand.
Этот коммит содержится в:
Ayke van Laethem 2019-10-14 15:51:03 +02:00 коммит произвёл Ron Evans
родитель 2a71aa90bc
коммит 2463153a8c
7 изменённых файлов: 214 добавлений и 173 удалений

Просмотреть файл

@ -5,16 +5,9 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
)
// Get the cache directory, usually ~/.cache/tinygo "github.com/tinygo-org/tinygo/goenv"
func cacheDir() string { )
dir, err := os.UserCacheDir()
if err != nil {
panic("could not find cache dir: " + err.Error())
}
return filepath.Join(dir, "tinygo")
}
// Return the newest timestamp of all the file paths passed in. Used to check // Return the newest timestamp of all the file paths passed in. Used to check
// for stale caches. // for stale caches.
@ -41,8 +34,7 @@ func cacheTimestamp(paths []string) (time.Time, error) {
// TODO: the configKey is currently ignored. It is supposed to be used as extra // TODO: the configKey is currently ignored. It is supposed to be used as extra
// data for the cache key, like the compiler version and arguments. // data for the cache key, like the compiler version and arguments.
func cacheLoad(name, configKey string, sourceFiles []string) (string, error) { func cacheLoad(name, configKey string, sourceFiles []string) (string, error) {
dir := cacheDir() cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
cachepath := filepath.Join(dir, name)
cacheStat, err := os.Stat(cachepath) cacheStat, err := os.Stat(cachepath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
return "", nil // does not exist return "", nil // does not exist
@ -76,7 +68,7 @@ func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string,
// TODO: check the config key // TODO: check the config key
dir := cacheDir() dir := goenv.Get("GOCACHE")
err := os.MkdirAll(dir, 0777) err := os.MkdirAll(dir, 0777)
if err != nil { if err != nil {
return "", err return "", err

Просмотреть файл

@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/blakesmith/ar" "github.com/blakesmith/ar"
"github.com/tinygo-org/tinygo/goenv"
) )
// These are the GENERIC_SOURCES according to CMakeList.txt. // These are the GENERIC_SOURCES according to CMakeList.txt.
@ -169,13 +170,13 @@ func builtinFiles(target string) []string {
// builtinsDir returns the directory where the sources for compiler-rt are kept. // builtinsDir returns the directory where the sources for compiler-rt are kept.
func builtinsDir() string { func builtinsDir() string {
return filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins") return filepath.Join(goenv.Get("TINYGOROOT"), "lib", "compiler-rt", "lib", "builtins")
} }
// Get the builtins archive, possibly generating it as needed. // Get the builtins archive, possibly generating it as needed.
func loadBuiltins(target string) (path string, err error) { func loadBuiltins(target string) (path string, err error) {
// Try to load a precompiled compiler-rt library. // Try to load a precompiled compiler-rt library.
precompiledPath := filepath.Join(sourceDir(), "pkg", target, "compiler-rt.a") precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, "compiler-rt.a")
if _, err := os.Stat(precompiledPath); err == nil { if _, err := os.Stat(precompiledPath); err == nil {
// Found a precompiled compiler-rt for this OS/architecture. Return the // Found a precompiled compiler-rt for this OS/architecture. Return the
// path directly. // path directly.

189
goenv/goenv.go Обычный файл
Просмотреть файл

@ -0,0 +1,189 @@
// Package goenv returns environment variables that are used in various parts of
// the compiler. You can query it manually with the `tinygo env` subcommand.
package goenv
import (
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
)
// Keys is a slice of all available environment variable keys.
var Keys = []string{
"GOOS",
"GOARCH",
"GOROOT",
"GOPATH",
"GOCACHE",
"TINYGOROOT",
}
// TINYGOROOT is the path to the final location for checking tinygo files. If
// unset (by a -X ldflag), then sourceDir() will fallback to the original build
// directory.
var TINYGOROOT string
// Get returns a single environment variable, possibly calculating it on-demand.
// The empty string is returned for unknown environment variables.
func Get(name string) string {
switch name {
case "GOOS":
if dir := os.Getenv("GOOS"); dir != "" {
return dir
}
return runtime.GOOS
case "GOARCH":
if dir := os.Getenv("GOARCH"); dir != "" {
return dir
}
return runtime.GOARCH
case "GOROOT":
return getGoroot()
case "GOPATH":
if dir := os.Getenv("GOPATH"); dir != "" {
return dir
}
// fallback
home := getHomeDir()
return filepath.Join(home, "go")
case "GOCACHE":
// Get the cache directory, usually ~/.cache/tinygo
dir, err := os.UserCacheDir()
if err != nil {
panic("could not find cache dir: " + err.Error())
}
return filepath.Join(dir, "tinygo")
case "TINYGOROOT":
return sourceDir()
default:
return ""
}
}
// Return the TINYGOROOT, or exit with an error.
func sourceDir() string {
// Use $TINYGOROOT as root, if available.
root := os.Getenv("TINYGOROOT")
if root != "" {
if !isSourceDir(root) {
fmt.Fprintln(os.Stderr, "error: $TINYGOROOT was not set to the correct root")
os.Exit(1)
}
return root
}
if TINYGOROOT != "" {
if !isSourceDir(TINYGOROOT) {
fmt.Fprintln(os.Stderr, "error: TINYGOROOT was not set to the correct root")
os.Exit(1)
}
return TINYGOROOT
}
// Find root from executable path.
path, err := os.Executable()
if err != nil {
// Very unlikely. Bail out if it happens.
panic("could not get executable path: " + err.Error())
}
root = filepath.Dir(filepath.Dir(path))
if isSourceDir(root) {
return root
}
// Fallback: use the original directory from where it was built
// https://stackoverflow.com/a/32163888/559350
_, path, _, _ = runtime.Caller(0)
root = filepath.Dir(filepath.Dir(path))
if isSourceDir(root) {
return root
}
fmt.Fprintln(os.Stderr, "error: could not autodetect root directory, set the TINYGOROOT environment variable to override")
os.Exit(1)
panic("unreachable")
}
// isSourceDir returns true if the directory looks like a TinyGo source directory.
func isSourceDir(root string) bool {
_, err := os.Stat(filepath.Join(root, "src/runtime/internal/sys/zversion.go"))
if err != nil {
return false
}
_, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go"))
return err == nil
}
func getHomeDir() string {
u, err := user.Current()
if err != nil {
panic("cannot get current user: " + err.Error())
}
if u.HomeDir == "" {
// This is very unlikely, so panic here.
// Not the nicest solution, however.
panic("could not find home directory")
}
return u.HomeDir
}
// getGoroot returns an appropriate GOROOT from various sources. If it can't be
// found, it returns an empty string.
func getGoroot() string {
goroot := os.Getenv("GOROOT")
if goroot != "" {
// An explicitly set GOROOT always has preference.
return goroot
}
// Check for the location of the 'go' binary and base GOROOT on that.
binpath, err := exec.LookPath("go")
if err == nil {
binpath, err = filepath.EvalSymlinks(binpath)
if err == nil {
goroot := filepath.Dir(filepath.Dir(binpath))
if isGoroot(goroot) {
return goroot
}
}
}
// Check what GOROOT was at compile time.
if isGoroot(runtime.GOROOT()) {
return runtime.GOROOT()
}
// Check for some standard locations, as a last resort.
var candidates []string
switch runtime.GOOS {
case "linux":
candidates = []string{
"/usr/local/go", // manually installed
"/usr/lib/go", // from the distribution
}
case "darwin":
candidates = []string{
"/usr/local/go", // manually installed
"/usr/local/opt/go/libexec", // from Homebrew
}
}
for _, candidate := range candidates {
if isGoroot(candidate) {
return candidate
}
}
// Can't find GOROOT...
return ""
}
// isGoroot checks whether the given path looks like a GOROOT.
func isGoroot(goroot string) bool {
_, err := os.Stat(filepath.Join(goroot, "src", "runtime", "internal", "sys", "zversion.go"))
return err == nil
}

Просмотреть файл

@ -9,6 +9,8 @@ import (
"os" "os"
"os/exec" "os/exec"
"unsafe" "unsafe"
"github.com/tinygo-org/tinygo/goenv"
) )
/* /*
@ -63,7 +65,7 @@ func Link(linker string, flags ...string) error {
cmd := exec.Command(linker, flags...) cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Dir = sourceDir() cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run() return cmd.Run()
} }
} }

Просмотреть файл

@ -8,6 +8,8 @@ package main
import ( import (
"os" "os"
"os/exec" "os/exec"
"github.com/tinygo-org/tinygo/goenv"
) )
// Link invokes a linker with the given name and arguments. // Link invokes a linker with the given name and arguments.
@ -20,6 +22,6 @@ func Link(linker string, flags ...string) error {
cmd := exec.Command(linker, flags...) cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Dir = sourceDir() cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run() return cmd.Run()
} }

14
main.go
Просмотреть файл

@ -18,6 +18,7 @@ import (
"time" "time"
"github.com/tinygo-org/tinygo/compiler" "github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp" "github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/loader" "github.com/tinygo-org/tinygo/loader"
@ -70,7 +71,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
config.gc = spec.GC config.gc = spec.GC
} }
root := sourceDir() root := goenv.Get("TINYGOROOT")
// Merge and adjust CFlags. // Merge and adjust CFlags.
cflags := append([]string{}, config.cFlags...) cflags := append([]string{}, config.cFlags...)
@ -84,7 +85,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1)) ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
} }
goroot := getGoroot() goroot := goenv.Get("GOROOT")
if goroot == "" { if goroot == "" {
return errors.New("cannot locate $GOROOT, please set it manually") return errors.New("cannot locate $GOROOT, please set it manually")
} }
@ -123,7 +124,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
VerifyIR: config.verifyIR, VerifyIR: config.verifyIR,
TINYGOROOT: root, TINYGOROOT: root,
GOROOT: goroot, GOROOT: goroot,
GOPATH: getGopath(), GOPATH: goenv.Get("GOPATH"),
BuildTags: tags, BuildTags: tags,
TestConfig: config.testConfig, TestConfig: config.testConfig,
} }
@ -454,7 +455,7 @@ func Flash(pkgName, target, port string, config *BuildConfig) error {
cmd := exec.Command("/bin/sh", "-c", flashCmd) cmd := exec.Command("/bin/sh", "-c", flashCmd)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Dir = sourceDir() cmd.Dir = goenv.Get("TINYGOROOT")
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return &commandError{"failed to flash", tmppath, err} return &commandError{"failed to flash", tmppath, err}
@ -719,7 +720,7 @@ func usage() {
fmt.Fprintln(os.Stderr, " test: test packages") fmt.Fprintln(os.Stderr, " test: test packages")
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, " clean: empty cache directory ("+cacheDir()+")") 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:")
flag.PrintDefaults() flag.PrintDefaults()
@ -890,8 +891,7 @@ func main() {
handleCompilerError(err) handleCompilerError(err)
case "clean": case "clean":
// remove cache directory // remove cache directory
dir := cacheDir() err := os.RemoveAll(goenv.Get("GOCACHE"))
err := os.RemoveAll(dir)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "cannot clean cache:", err) fmt.Fprintln(os.Stderr, "cannot clean cache:", err)
os.Exit(1) os.Exit(1)

155
target.go
Просмотреть файл

@ -8,17 +8,13 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"os/user"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"strings" "strings"
)
// TINYGOROOT is the path to the final location for checking tinygo files. If "github.com/tinygo-org/tinygo/goenv"
// unset (by a -X ldflag), then sourceDir() will fallback to the original build )
// directory.
var TINYGOROOT string
// Target specification for a given target. Used for bare metal targets. // Target specification for a given target. Used for bare metal targets.
// //
@ -147,7 +143,7 @@ func (spec *TargetSpec) loadFromGivenStr(str string) error {
if strings.HasSuffix(str, ".json") { if strings.HasSuffix(str, ".json") {
path, _ = filepath.Abs(str) path, _ = filepath.Abs(str)
} else { } else {
path = filepath.Join(sourceDir(), "targets", strings.ToLower(str)+".json") path = filepath.Join(goenv.Get("TINYGOROOT"), "targets", strings.ToLower(str)+".json")
} }
fp, err := os.Open(path) fp, err := os.Open(path)
if err != nil { if err != nil {
@ -186,14 +182,8 @@ func LoadTarget(target string) (*TargetSpec, error) {
if target == "" { if target == "" {
// Configure based on GOOS/GOARCH environment variables (falling back to // Configure based on GOOS/GOARCH environment variables (falling back to
// runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it. // runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it.
goos := os.Getenv("GOOS") goos := goenv.Get("GOOS")
if goos == "" { goarch := goenv.Get("GOARCH")
goos = runtime.GOOS
}
goarch := os.Getenv("GOARCH")
if goarch == "" {
goarch = runtime.GOARCH
}
llvmos := goos llvmos := goos
llvmarch := map[string]string{ llvmarch := map[string]string{
"386": "i386", "386": "i386",
@ -315,141 +305,6 @@ func (spec *TargetSpec) OpenOCDConfiguration() (args []string, err error) {
return args, nil return args, nil
} }
// Return the TINYGOROOT, or exit with an error.
func sourceDir() string {
// Use $TINYGOROOT as root, if available.
root := os.Getenv("TINYGOROOT")
if root != "" {
if !isSourceDir(root) {
fmt.Fprintln(os.Stderr, "error: $TINYGOROOT was not set to the correct root")
os.Exit(1)
}
return root
}
if TINYGOROOT != "" {
if !isSourceDir(TINYGOROOT) {
fmt.Fprintln(os.Stderr, "error: TINYGOROOT was not set to the correct root")
os.Exit(1)
}
return TINYGOROOT
}
// Find root from executable path.
path, err := os.Executable()
if err != nil {
// Very unlikely. Bail out if it happens.
panic("could not get executable path: " + err.Error())
}
root = filepath.Dir(filepath.Dir(path))
if isSourceDir(root) {
return root
}
// Fallback: use the original directory from where it was built
// https://stackoverflow.com/a/32163888/559350
_, path, _, _ = runtime.Caller(0)
root = filepath.Dir(path)
if isSourceDir(root) {
return root
}
fmt.Fprintln(os.Stderr, "error: could not autodetect root directory, set the TINYGOROOT environment variable to override")
os.Exit(1)
panic("unreachable")
}
// isSourceDir returns true if the directory looks like a TinyGo source directory.
func isSourceDir(root string) bool {
_, err := os.Stat(filepath.Join(root, "src/runtime/internal/sys/zversion.go"))
if err != nil {
return false
}
_, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go"))
return err == nil
}
func getGopath() string {
gopath := os.Getenv("GOPATH")
if gopath != "" {
return gopath
}
// fallback
home := getHomeDir()
return filepath.Join(home, "go")
}
func getHomeDir() string {
u, err := user.Current()
if err != nil {
panic("cannot get current user: " + err.Error())
}
if u.HomeDir == "" {
// This is very unlikely, so panic here.
// Not the nicest solution, however.
panic("could not find home directory")
}
return u.HomeDir
}
// getGoroot returns an appropriate GOROOT from various sources. If it can't be
// found, it returns an empty string.
func getGoroot() string {
goroot := os.Getenv("GOROOT")
if goroot != "" {
// An explicitly set GOROOT always has preference.
return goroot
}
// Check for the location of the 'go' binary and base GOROOT on that.
binpath, err := exec.LookPath("go")
if err == nil {
binpath, err = filepath.EvalSymlinks(binpath)
if err == nil {
goroot := filepath.Dir(filepath.Dir(binpath))
if isGoroot(goroot) {
return goroot
}
}
}
// Check what GOROOT was at compile time.
if isGoroot(runtime.GOROOT()) {
return runtime.GOROOT()
}
// Check for some standard locations, as a last resort.
var candidates []string
switch runtime.GOOS {
case "linux":
candidates = []string{
"/usr/local/go", // manually installed
"/usr/lib/go", // from the distribution
}
case "darwin":
candidates = []string{
"/usr/local/go", // manually installed
"/usr/local/opt/go/libexec", // from Homebrew
}
}
for _, candidate := range candidates {
if isGoroot(candidate) {
return candidate
}
}
// Can't find GOROOT...
return ""
}
// isGoroot checks whether the given path looks like a GOROOT.
func isGoroot(goroot string) bool {
_, err := os.Stat(filepath.Join(goroot, "src", "runtime", "internal", "sys", "zversion.go"))
return err == nil
}
// getGorootVersion returns the major and minor version for a given GOROOT path. // getGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned. // If the goroot cannot be determined, (0, 0) is returned.
func getGorootVersion(goroot string) (major, minor int, err error) { func getGorootVersion(goroot string) (major, minor int, err error) {