github.com/aykevl/tinygo@v0.5.0/target.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"os/user"
    10  	"path/filepath"
    11  	"runtime"
    12  	"strings"
    13  )
    14  
    15  // Target specification for a given target. Used for bare metal targets.
    16  //
    17  // The target specification is mostly inspired by Rust:
    18  // https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.TargetOptions.html
    19  // https://github.com/shepmaster/rust-arduino-blink-led-no-core-with-cargo/blob/master/blink/arduino.json
    20  type TargetSpec struct {
    21  	Inherits   []string `json:"inherits"`
    22  	Triple     string   `json:"llvm-target"`
    23  	CPU        string   `json:"cpu"`
    24  	GOOS       string   `json:"goos"`
    25  	GOARCH     string   `json:"goarch"`
    26  	BuildTags  []string `json:"build-tags"`
    27  	GC         string   `json:"gc"`
    28  	Compiler   string   `json:"compiler"`
    29  	Linker     string   `json:"linker"`
    30  	RTLib      string   `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
    31  	CFlags     []string `json:"cflags"`
    32  	LDFlags    []string `json:"ldflags"`
    33  	ExtraFiles []string `json:"extra-files"`
    34  	Emulator   []string `json:"emulator"`
    35  	Flasher    string   `json:"flash"`
    36  	OCDDaemon  []string `json:"ocd-daemon"`
    37  	GDB        string   `json:"gdb"`
    38  	GDBCmds    []string `json:"gdb-initial-cmds"`
    39  }
    40  
    41  // copyProperties copies all properties that are set in spec2 into itself.
    42  func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
    43  	// TODO: simplify this using reflection? Inherits and BuildTags are special
    44  	// cases, but the rest can simply be copied if set.
    45  	spec.Inherits = append(spec.Inherits, spec2.Inherits...)
    46  	if spec2.Triple != "" {
    47  		spec.Triple = spec2.Triple
    48  	}
    49  	if spec2.CPU != "" {
    50  		spec.CPU = spec2.CPU
    51  	}
    52  	if spec2.GOOS != "" {
    53  		spec.GOOS = spec2.GOOS
    54  	}
    55  	if spec2.GOARCH != "" {
    56  		spec.GOARCH = spec2.GOARCH
    57  	}
    58  	spec.BuildTags = append(spec.BuildTags, spec2.BuildTags...)
    59  	if spec2.GC != "" {
    60  		spec.GC = spec2.GC
    61  	}
    62  	if spec2.Compiler != "" {
    63  		spec.Compiler = spec2.Compiler
    64  	}
    65  	if spec2.Linker != "" {
    66  		spec.Linker = spec2.Linker
    67  	}
    68  	if spec2.RTLib != "" {
    69  		spec.RTLib = spec2.RTLib
    70  	}
    71  	spec.CFlags = append(spec.CFlags, spec2.CFlags...)
    72  	spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...)
    73  	spec.ExtraFiles = append(spec.ExtraFiles, spec2.ExtraFiles...)
    74  	if len(spec2.Emulator) != 0 {
    75  		spec.Emulator = spec2.Emulator
    76  	}
    77  	if spec2.Flasher != "" {
    78  		spec.Flasher = spec2.Flasher
    79  	}
    80  	if len(spec2.OCDDaemon) != 0 {
    81  		spec.OCDDaemon = spec2.OCDDaemon
    82  	}
    83  	if spec2.GDB != "" {
    84  		spec.GDB = spec2.GDB
    85  	}
    86  	if len(spec2.GDBCmds) != 0 {
    87  		spec.GDBCmds = spec2.GDBCmds
    88  	}
    89  }
    90  
    91  // load reads a target specification from the JSON in the given io.Reader. It
    92  // may load more targets specified using the "inherits" property.
    93  func (spec *TargetSpec) load(r io.Reader) error {
    94  	err := json.NewDecoder(r).Decode(spec)
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  // loadFromName loads the given target from the targets/ directory inside the
   103  // compiler sources.
   104  func (spec *TargetSpec) loadFromName(name string) error {
   105  	path := filepath.Join(sourceDir(), "targets", strings.ToLower(name)+".json")
   106  	fp, err := os.Open(path)
   107  	if err != nil {
   108  		return err
   109  	}
   110  	defer fp.Close()
   111  	return spec.load(fp)
   112  }
   113  
   114  // resolveInherits loads inherited targets, recursively.
   115  func (spec *TargetSpec) resolveInherits() error {
   116  	// First create a new spec with all the inherited properties.
   117  	newSpec := &TargetSpec{}
   118  	for _, name := range spec.Inherits {
   119  		subtarget := &TargetSpec{}
   120  		err := subtarget.loadFromName(name)
   121  		if err != nil {
   122  			return err
   123  		}
   124  		err = subtarget.resolveInherits()
   125  		if err != nil {
   126  			return err
   127  		}
   128  		newSpec.copyProperties(subtarget)
   129  	}
   130  
   131  	// When all properties are loaded, make sure they are properly inherited.
   132  	newSpec.copyProperties(spec)
   133  	*spec = *newSpec
   134  
   135  	return nil
   136  }
   137  
   138  // Load a target specification.
   139  func LoadTarget(target string) (*TargetSpec, error) {
   140  	if target == "" {
   141  		// Configure based on GOOS/GOARCH environment variables (falling back to
   142  		// runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it.
   143  		goos := os.Getenv("GOOS")
   144  		if goos == "" {
   145  			goos = runtime.GOOS
   146  		}
   147  		goarch := os.Getenv("GOARCH")
   148  		if goarch == "" {
   149  			goarch = runtime.GOARCH
   150  		}
   151  		llvmos := goos
   152  		llvmarch := map[string]string{
   153  			"386":   "i386",
   154  			"amd64": "x86_64",
   155  			"arm64": "aarch64",
   156  		}[goarch]
   157  		if llvmarch == "" {
   158  			llvmarch = goarch
   159  		}
   160  		target = llvmarch + "--" + llvmos
   161  		if goarch == "arm" {
   162  			target += "-gnueabihf"
   163  		}
   164  		return defaultTarget(goos, goarch, target)
   165  	}
   166  
   167  	// See whether there is a target specification for this target (e.g.
   168  	// Arduino).
   169  	spec := &TargetSpec{}
   170  	err := spec.loadFromName(target)
   171  	if err == nil {
   172  		// Successfully loaded this target from a built-in .json file. Make sure
   173  		// it includes all parents as specified in the "inherits" key.
   174  		err = spec.resolveInherits()
   175  		if err != nil {
   176  			return nil, err
   177  		}
   178  		return spec, nil
   179  	} else if !os.IsNotExist(err) {
   180  		// Expected a 'file not found' error, got something else. Report it as
   181  		// an error.
   182  		return nil, err
   183  	} else {
   184  		// Load target from given triple, ignore GOOS/GOARCH environment
   185  		// variables.
   186  		tripleSplit := strings.Split(target, "-")
   187  		if len(tripleSplit) == 1 {
   188  			return nil, errors.New("expected a full LLVM target or a custom target in -target flag")
   189  		}
   190  		goos := tripleSplit[2]
   191  		if strings.HasPrefix(goos, "darwin") {
   192  			goos = "darwin"
   193  		}
   194  		goarch := map[string]string{ // map from LLVM arch to Go arch
   195  			"i386":    "386",
   196  			"x86_64":  "amd64",
   197  			"aarch64": "arm64",
   198  		}[tripleSplit[0]]
   199  		if goarch == "" {
   200  			goarch = tripleSplit[0]
   201  		}
   202  		return defaultTarget(goos, goarch, target)
   203  	}
   204  }
   205  
   206  func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
   207  	// No target spec available. Use the default one, useful on most systems
   208  	// with a regular OS.
   209  	spec := TargetSpec{
   210  		Triple:    triple,
   211  		GOOS:      goos,
   212  		GOARCH:    goarch,
   213  		BuildTags: []string{goos, goarch},
   214  		Compiler:  commands["clang"],
   215  		Linker:    "cc",
   216  		GDB:       "gdb",
   217  		GDBCmds:   []string{"run"},
   218  	}
   219  	if goos == "darwin" {
   220  		spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
   221  	} else {
   222  		spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
   223  	}
   224  	if goarch != runtime.GOARCH {
   225  		// Some educated guesses as to how to invoke helper programs.
   226  		if goarch == "arm" && goos == "linux" {
   227  			spec.Linker = "arm-linux-gnueabihf-gcc"
   228  			spec.GDB = "arm-linux-gnueabihf-gdb"
   229  			spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabihf"}
   230  		}
   231  		if goarch == "arm64" && goos == "linux" {
   232  			spec.Linker = "aarch64-linux-gnu-gcc"
   233  			spec.GDB = "aarch64-linux-gnu-gdb"
   234  			spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"}
   235  		}
   236  		if goarch == "386" {
   237  			spec.CFlags = []string{"-m32"}
   238  			spec.LDFlags = []string{"-m32"}
   239  		}
   240  	}
   241  	return &spec, nil
   242  }
   243  
   244  // Return the TINYGOROOT, or exit with an error.
   245  func sourceDir() string {
   246  	// Use $TINYGOROOT as root, if available.
   247  	root := os.Getenv("TINYGOROOT")
   248  	if root != "" {
   249  		if !isSourceDir(root) {
   250  			fmt.Fprintln(os.Stderr, "error: $TINYGOROOT was not set to the correct root")
   251  			os.Exit(1)
   252  		}
   253  		return root
   254  	}
   255  
   256  	// Find root from executable path.
   257  	path, err := os.Executable()
   258  	if err != nil {
   259  		// Very unlikely. Bail out if it happens.
   260  		panic("could not get executable path: " + err.Error())
   261  	}
   262  	root = filepath.Dir(filepath.Dir(path))
   263  	if isSourceDir(root) {
   264  		return root
   265  	}
   266  
   267  	// Fallback: use the original directory from where it was built
   268  	// https://stackoverflow.com/a/32163888/559350
   269  	_, path, _, _ = runtime.Caller(0)
   270  	root = filepath.Dir(path)
   271  	if isSourceDir(root) {
   272  		return root
   273  	}
   274  
   275  	fmt.Fprintln(os.Stderr, "error: could not autodetect root directory, set the TINYGOROOT environment variable to override")
   276  	os.Exit(1)
   277  	panic("unreachable")
   278  }
   279  
   280  // isSourceDir returns true if the directory looks like a TinyGo source directory.
   281  func isSourceDir(root string) bool {
   282  	_, err := os.Stat(filepath.Join(root, "src/runtime/internal/sys/zversion.go"))
   283  	if err != nil {
   284  		return false
   285  	}
   286  	_, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go"))
   287  	return err == nil
   288  }
   289  
   290  func getGopath() string {
   291  	gopath := os.Getenv("GOPATH")
   292  	if gopath != "" {
   293  		return gopath
   294  	}
   295  
   296  	// fallback
   297  	home := getHomeDir()
   298  	return filepath.Join(home, "go")
   299  }
   300  
   301  func getHomeDir() string {
   302  	u, err := user.Current()
   303  	if err != nil {
   304  		panic("cannot get current user: " + err.Error())
   305  	}
   306  	if u.HomeDir == "" {
   307  		// This is very unlikely, so panic here.
   308  		// Not the nicest solution, however.
   309  		panic("could not find home directory")
   310  	}
   311  	return u.HomeDir
   312  }