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 }