Skip to content

Commit

Permalink
main: refactor environment variables into a separate package
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
aykevl authored and deadprogram committed Oct 14, 2019
1 parent 2a71aa9 commit 2463153
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 173 deletions.
16 changes: 4 additions & 12 deletions buildcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,9 @@ import (
"os"
"path/filepath"
"time"
)

// Get the cache directory, usually ~/.cache/tinygo
func cacheDir() string {
dir, err := os.UserCacheDir()
if err != nil {
panic("could not find cache dir: " + err.Error())
}
return filepath.Join(dir, "tinygo")
}
"github.com/tinygo-org/tinygo/goenv"
)

// Return the newest timestamp of all the file paths passed in. Used to check
// for stale caches.
Expand All @@ -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
// data for the cache key, like the compiler version and arguments.
func cacheLoad(name, configKey string, sourceFiles []string) (string, error) {
dir := cacheDir()
cachepath := filepath.Join(dir, name)
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
cacheStat, err := os.Stat(cachepath)
if os.IsNotExist(err) {
return "", nil // does not exist
Expand Down Expand Up @@ -76,7 +68,7 @@ func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string,

// TODO: check the config key

dir := cacheDir()
dir := goenv.Get("GOCACHE")
err := os.MkdirAll(dir, 0777)
if err != nil {
return "", err
Expand Down
5 changes: 3 additions & 2 deletions builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

"github.com/blakesmith/ar"
"github.com/tinygo-org/tinygo/goenv"
)

// These are the GENERIC_SOURCES according to CMakeList.txt.
Expand Down Expand Up @@ -169,13 +170,13 @@ func builtinFiles(target string) []string {

// builtinsDir returns the directory where the sources for compiler-rt are kept.
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.
func loadBuiltins(target string) (path string, err error) {
// 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 {
// Found a precompiled compiler-rt for this OS/architecture. Return the
// path directly.
Expand Down
189 changes: 189 additions & 0 deletions goenv/goenv.go
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 3 additions & 1 deletion linker-builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"os"
"os/exec"
"unsafe"

"github.com/tinygo-org/tinygo/goenv"
)

/*
Expand Down Expand Up @@ -63,7 +65,7 @@ func Link(linker string, flags ...string) error {
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run()
}
}
4 changes: 3 additions & 1 deletion linker-external.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ package main
import (
"os"
"os/exec"

"github.com/tinygo-org/tinygo/goenv"
)

// Link invokes a linker with the given name and arguments.
Expand All @@ -20,6 +22,6 @@ func Link(linker string, flags ...string) error {
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run()
}
14 changes: 7 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"time"

"github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/loader"

Expand Down Expand Up @@ -70,7 +71,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
config.gc = spec.GC
}

root := sourceDir()
root := goenv.Get("TINYGOROOT")

// Merge and adjust CFlags.
cflags := append([]string{}, config.cFlags...)
Expand All @@ -84,7 +85,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
}

goroot := getGoroot()
goroot := goenv.Get("GOROOT")
if goroot == "" {
return errors.New("cannot locate $GOROOT, please set it manually")
}
Expand Down Expand Up @@ -123,7 +124,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
VerifyIR: config.verifyIR,
TINYGOROOT: root,
GOROOT: goroot,
GOPATH: getGopath(),
GOPATH: goenv.Get("GOPATH"),
BuildTags: tags,
TestConfig: config.testConfig,
}
Expand Down Expand Up @@ -454,7 +455,7 @@ func Flash(pkgName, target, port string, config *BuildConfig) error {
cmd := exec.Command("/bin/sh", "-c", flashCmd)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
cmd.Dir = goenv.Get("TINYGOROOT")
err := cmd.Run()
if err != nil {
return &commandError{"failed to flash", tmppath, err}
Expand Down Expand Up @@ -719,7 +720,7 @@ func usage() {
fmt.Fprintln(os.Stderr, " test: test packages")
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, " 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, "\nflags:")
flag.PrintDefaults()
Expand Down Expand Up @@ -890,8 +891,7 @@ func main() {
handleCompilerError(err)
case "clean":
// remove cache directory
dir := cacheDir()
err := os.RemoveAll(dir)
err := os.RemoveAll(goenv.Get("GOCACHE"))
if err != nil {
fmt.Fprintln(os.Stderr, "cannot clean cache:", err)
os.Exit(1)
Expand Down
Loading

0 comments on commit 2463153

Please sign in to comment.