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

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"flag"
     6  	"fmt"
     7  	"go/types"
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/exec"
    12  	"os/signal"
    13  	"path/filepath"
    14  	"runtime"
    15  	"strconv"
    16  	"strings"
    17  	"syscall"
    18  
    19  	"github.com/tinygo-org/tinygo/compiler"
    20  	"github.com/tinygo-org/tinygo/interp"
    21  	"github.com/tinygo-org/tinygo/loader"
    22  )
    23  
    24  // commandError is an error type to wrap os/exec.Command errors. This provides
    25  // some more information regarding what went wrong while running a command.
    26  type commandError struct {
    27  	Msg  string
    28  	File string
    29  	Err  error
    30  }
    31  
    32  func (e *commandError) Error() string {
    33  	return e.Msg + " " + e.File + ": " + e.Err.Error()
    34  }
    35  
    36  type BuildConfig struct {
    37  	opt        string
    38  	gc         string
    39  	printIR    bool
    40  	dumpSSA    bool
    41  	debug      bool
    42  	printSizes string
    43  	cFlags     []string
    44  	ldFlags    []string
    45  	wasmAbi    string
    46  }
    47  
    48  // Helper function for Compiler object.
    49  func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, action func(string) error) error {
    50  	if config.gc == "" && spec.GC != "" {
    51  		config.gc = spec.GC
    52  	}
    53  
    54  	// Append command line passed CFlags and LDFlags
    55  	spec.CFlags = append(spec.CFlags, config.cFlags...)
    56  	spec.LDFlags = append(spec.LDFlags, config.ldFlags...)
    57  
    58  	compilerConfig := compiler.Config{
    59  		Triple:    spec.Triple,
    60  		CPU:       spec.CPU,
    61  		GOOS:      spec.GOOS,
    62  		GOARCH:    spec.GOARCH,
    63  		GC:        config.gc,
    64  		CFlags:    spec.CFlags,
    65  		LDFlags:   spec.LDFlags,
    66  		Debug:     config.debug,
    67  		DumpSSA:   config.dumpSSA,
    68  		RootDir:   sourceDir(),
    69  		GOPATH:    getGopath(),
    70  		BuildTags: spec.BuildTags,
    71  	}
    72  	c, err := compiler.NewCompiler(pkgName, compilerConfig)
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	// Compile Go code to IR.
    78  	err = c.Compile(pkgName)
    79  	if err != nil {
    80  		return err
    81  	}
    82  	if config.printIR {
    83  		fmt.Println("Generated LLVM IR:")
    84  		fmt.Println(c.IR())
    85  	}
    86  	if err := c.Verify(); err != nil {
    87  		return errors.New("verification error after IR construction")
    88  	}
    89  
    90  	err = interp.Run(c.Module(), c.TargetData(), config.dumpSSA)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	if err := c.Verify(); err != nil {
    95  		return errors.New("verification error after interpreting runtime.initAll")
    96  	}
    97  
    98  	if spec.GOOS != "darwin" {
    99  		c.ApplyFunctionSections() // -ffunction-sections
   100  	}
   101  	if err := c.Verify(); err != nil {
   102  		return errors.New("verification error after applying function sections")
   103  	}
   104  
   105  	// Browsers cannot handle external functions that have type i64 because it
   106  	// cannot be represented exactly in JavaScript (JS only has doubles). To
   107  	// keep functions interoperable, pass int64 types as pointers to
   108  	// stack-allocated values.
   109  	// Use -wasm-abi=generic to disable this behaviour.
   110  	if config.wasmAbi == "js" && strings.HasPrefix(spec.Triple, "wasm") {
   111  		err := c.ExternalInt64AsPtr()
   112  		if err != nil {
   113  			return err
   114  		}
   115  		if err := c.Verify(); err != nil {
   116  			return errors.New("verification error after running the wasm i64 hack")
   117  		}
   118  	}
   119  
   120  	// Optimization levels here are roughly the same as Clang, but probably not
   121  	// exactly.
   122  	switch config.opt {
   123  	case "none:", "0":
   124  		err = c.Optimize(0, 0, 0) // -O0
   125  	case "1":
   126  		err = c.Optimize(1, 0, 0) // -O1
   127  	case "2":
   128  		err = c.Optimize(2, 0, 225) // -O2
   129  	case "s":
   130  		err = c.Optimize(2, 1, 225) // -Os
   131  	case "z":
   132  		err = c.Optimize(2, 2, 5) // -Oz, default
   133  	default:
   134  		err = errors.New("unknown optimization level: -opt=" + config.opt)
   135  	}
   136  	if err != nil {
   137  		return err
   138  	}
   139  	if err := c.Verify(); err != nil {
   140  		return errors.New("verification failure after LLVM optimization passes")
   141  	}
   142  
   143  	// On the AVR, pointers can point either to flash or to RAM, but we don't
   144  	// know. As a temporary fix, load all global variables in RAM.
   145  	// In the future, there should be a compiler pass that determines which
   146  	// pointers are flash and which are in RAM so that pointers can have a
   147  	// correct address space parameter (address space 1 is for flash).
   148  	if strings.HasPrefix(spec.Triple, "avr") {
   149  		c.NonConstGlobals()
   150  		if err := c.Verify(); err != nil {
   151  			return errors.New("verification error after making all globals non-constant on AVR")
   152  		}
   153  	}
   154  
   155  	// Generate output.
   156  	outext := filepath.Ext(outpath)
   157  	switch outext {
   158  	case ".o":
   159  		return c.EmitObject(outpath)
   160  	case ".bc":
   161  		return c.EmitBitcode(outpath)
   162  	case ".ll":
   163  		return c.EmitText(outpath)
   164  	default:
   165  		// Act as a compiler driver.
   166  
   167  		// Create a temporary directory for intermediary files.
   168  		dir, err := ioutil.TempDir("", "tinygo")
   169  		if err != nil {
   170  			return err
   171  		}
   172  		defer os.RemoveAll(dir)
   173  
   174  		// Write the object file.
   175  		objfile := filepath.Join(dir, "main.o")
   176  		err = c.EmitObject(objfile)
   177  		if err != nil {
   178  			return err
   179  		}
   180  
   181  		// Load builtins library from the cache, possibly compiling it on the
   182  		// fly.
   183  		var librt string
   184  		if spec.RTLib == "compiler-rt" {
   185  			librt, err = loadBuiltins(spec.Triple)
   186  			if err != nil {
   187  				return err
   188  			}
   189  		}
   190  
   191  		// Prepare link command.
   192  		executable := filepath.Join(dir, "main")
   193  		tmppath := executable // final file
   194  		ldflags := append(spec.LDFlags, "-o", executable, objfile, "-L", sourceDir())
   195  		if spec.RTLib == "compiler-rt" {
   196  			ldflags = append(ldflags, librt)
   197  		}
   198  
   199  		// Compile extra files.
   200  		for i, path := range spec.ExtraFiles {
   201  			outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
   202  			cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...)
   203  			cmd.Stdout = os.Stdout
   204  			cmd.Stderr = os.Stderr
   205  			cmd.Dir = sourceDir()
   206  			err := cmd.Run()
   207  			if err != nil {
   208  				return &commandError{"failed to build", path, err}
   209  			}
   210  			ldflags = append(ldflags, outpath)
   211  		}
   212  
   213  		// Compile C files in packages.
   214  		for i, pkg := range c.Packages() {
   215  			for _, file := range pkg.CFiles {
   216  				path := filepath.Join(pkg.Package.Dir, file)
   217  				outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
   218  				cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...)
   219  				cmd.Stdout = os.Stdout
   220  				cmd.Stderr = os.Stderr
   221  				cmd.Dir = sourceDir()
   222  				err := cmd.Run()
   223  				if err != nil {
   224  					return &commandError{"failed to build", path, err}
   225  				}
   226  				ldflags = append(ldflags, outpath)
   227  			}
   228  		}
   229  
   230  		// Link the object files together.
   231  		if linker, ok := commands[spec.Linker]; ok {
   232  			err = Link(linker, ldflags...)
   233  		} else {
   234  			err = Link(spec.Linker, ldflags...)
   235  		}
   236  		if err != nil {
   237  			return &commandError{"failed to link", executable, err}
   238  		}
   239  
   240  		if config.printSizes == "short" || config.printSizes == "full" {
   241  			sizes, err := Sizes(executable)
   242  			if err != nil {
   243  				return err
   244  			}
   245  			if config.printSizes == "short" {
   246  				fmt.Printf("   code    data     bss |   flash     ram\n")
   247  				fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
   248  			} else {
   249  				fmt.Printf("   code  rodata    data     bss |   flash     ram | package\n")
   250  				for _, name := range sizes.SortedPackageNames() {
   251  					pkgSize := sizes.Packages[name]
   252  					fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name)
   253  				}
   254  				fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM())
   255  				fmt.Printf("%7d       - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
   256  			}
   257  		}
   258  
   259  		// Get an Intel .hex file or .bin file from the .elf file.
   260  		if outext == ".hex" || outext == ".bin" {
   261  			tmppath = filepath.Join(dir, "main"+outext)
   262  			err := Objcopy(executable, tmppath)
   263  			if err != nil {
   264  				return err
   265  			}
   266  		} else if outext == ".uf2" {
   267  			// Get UF2 from the .elf file.
   268  			tmppath = filepath.Join(dir, "main"+outext)
   269  			err := ConvertELFFileToUF2File(executable, tmppath)
   270  			if err != nil {
   271  				return err
   272  			}
   273  		}
   274  		return action(tmppath)
   275  	}
   276  }
   277  
   278  func Build(pkgName, outpath, target string, config *BuildConfig) error {
   279  	spec, err := LoadTarget(target)
   280  	if err != nil {
   281  		return err
   282  	}
   283  
   284  	return Compile(pkgName, outpath, spec, config, func(tmppath string) error {
   285  		if err := os.Rename(tmppath, outpath); err != nil {
   286  			// Moving failed. Do a file copy.
   287  			inf, err := os.Open(tmppath)
   288  			if err != nil {
   289  				return err
   290  			}
   291  			defer inf.Close()
   292  			outf, err := os.OpenFile(outpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
   293  			if err != nil {
   294  				return err
   295  			}
   296  
   297  			// Copy data to output file.
   298  			_, err = io.Copy(outf, inf)
   299  			if err != nil {
   300  				return err
   301  			}
   302  
   303  			// Check whether file writing was successful.
   304  			return outf.Close()
   305  		} else {
   306  			// Move was successful.
   307  			return nil
   308  		}
   309  	})
   310  }
   311  
   312  func Flash(pkgName, target, port string, config *BuildConfig) error {
   313  	spec, err := LoadTarget(target)
   314  	if err != nil {
   315  		return err
   316  	}
   317  
   318  	// determine the type of file to compile
   319  	var fileExt string
   320  
   321  	switch {
   322  	case strings.Contains(spec.Flasher, "{hex}"):
   323  		fileExt = ".hex"
   324  	case strings.Contains(spec.Flasher, "{elf}"):
   325  		fileExt = ".elf"
   326  	case strings.Contains(spec.Flasher, "{bin}"):
   327  		fileExt = ".bin"
   328  	case strings.Contains(spec.Flasher, "{uf2}"):
   329  		fileExt = ".uf2"
   330  	default:
   331  		return errors.New("invalid target file - did you forget the {hex} token in the 'flash' section?")
   332  	}
   333  
   334  	return Compile(pkgName, fileExt, spec, config, func(tmppath string) error {
   335  		if spec.Flasher == "" {
   336  			return errors.New("no flash command specified - did you miss a -target flag?")
   337  		}
   338  
   339  		// Create the command.
   340  		flashCmd := spec.Flasher
   341  		fileToken := "{" + fileExt[1:] + "}"
   342  		flashCmd = strings.Replace(flashCmd, fileToken, tmppath, -1)
   343  		flashCmd = strings.Replace(flashCmd, "{port}", port, -1)
   344  
   345  		// Execute the command.
   346  		cmd := exec.Command("/bin/sh", "-c", flashCmd)
   347  		cmd.Stdout = os.Stdout
   348  		cmd.Stderr = os.Stderr
   349  		cmd.Dir = sourceDir()
   350  		err := cmd.Run()
   351  		if err != nil {
   352  			return &commandError{"failed to flash", tmppath, err}
   353  		}
   354  		return nil
   355  	})
   356  }
   357  
   358  // Flash a program on a microcontroller and drop into a GDB shell.
   359  //
   360  // Note: this command is expected to execute just before exiting, as it
   361  // modifies global state.
   362  func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig) error {
   363  	spec, err := LoadTarget(target)
   364  	if err != nil {
   365  		return err
   366  	}
   367  
   368  	if spec.GDB == "" {
   369  		return errors.New("gdb not configured in the target specification")
   370  	}
   371  
   372  	return Compile(pkgName, "", spec, config, func(tmppath string) error {
   373  		if len(spec.OCDDaemon) != 0 {
   374  			// We need a separate debugging daemon for on-chip debugging.
   375  			daemon := exec.Command(spec.OCDDaemon[0], spec.OCDDaemon[1:]...)
   376  			if ocdOutput {
   377  				// Make it clear which output is from the daemon.
   378  				w := &ColorWriter{
   379  					Out:    os.Stderr,
   380  					Prefix: spec.OCDDaemon[0] + ": ",
   381  					Color:  TermColorYellow,
   382  				}
   383  				daemon.Stdout = w
   384  				daemon.Stderr = w
   385  			}
   386  			// Make sure the daemon doesn't receive Ctrl-C that is intended for
   387  			// GDB (to break the currently executing program).
   388  			// https://stackoverflow.com/a/35435038/559350
   389  			daemon.SysProcAttr = &syscall.SysProcAttr{
   390  				Setpgid: true,
   391  				Pgid:    0,
   392  			}
   393  			// Start now, and kill it on exit.
   394  			daemon.Start()
   395  			defer func() {
   396  				daemon.Process.Signal(os.Interrupt)
   397  				// Maybe we should send a .Kill() after x seconds?
   398  				daemon.Wait()
   399  			}()
   400  		}
   401  
   402  		// Ignore Ctrl-C, it must be passed on to GDB.
   403  		c := make(chan os.Signal, 1)
   404  		signal.Notify(c, os.Interrupt)
   405  		go func() {
   406  			for range c {
   407  			}
   408  		}()
   409  
   410  		// Construct and execute a gdb command.
   411  		// By default: gdb -ex run <binary>
   412  		// Exit GDB with Ctrl-D.
   413  		params := []string{tmppath}
   414  		for _, cmd := range spec.GDBCmds {
   415  			params = append(params, "-ex", cmd)
   416  		}
   417  		cmd := exec.Command(spec.GDB, params...)
   418  		cmd.Stdin = os.Stdin
   419  		cmd.Stdout = os.Stdout
   420  		cmd.Stderr = os.Stderr
   421  		err := cmd.Run()
   422  		if err != nil {
   423  			return &commandError{"failed to run gdb with", tmppath, err}
   424  		}
   425  		return nil
   426  	})
   427  }
   428  
   429  // Compile and run the given program, directly or in an emulator.
   430  func Run(pkgName, target string, config *BuildConfig) error {
   431  	spec, err := LoadTarget(target)
   432  	if err != nil {
   433  		return err
   434  	}
   435  
   436  	return Compile(pkgName, ".elf", spec, config, func(tmppath string) error {
   437  		if len(spec.Emulator) == 0 {
   438  			// Run directly.
   439  			cmd := exec.Command(tmppath)
   440  			cmd.Stdout = os.Stdout
   441  			cmd.Stderr = os.Stderr
   442  			err := cmd.Run()
   443  			if err != nil {
   444  				if err, ok := err.(*exec.ExitError); ok && err.Exited() {
   445  					// Workaround for QEMU which always exits with an error.
   446  					return nil
   447  				}
   448  				return &commandError{"failed to run compiled binary", tmppath, err}
   449  			}
   450  			return nil
   451  		} else {
   452  			// Run in an emulator.
   453  			args := append(spec.Emulator[1:], tmppath)
   454  			cmd := exec.Command(spec.Emulator[0], args...)
   455  			cmd.Stdout = os.Stdout
   456  			cmd.Stderr = os.Stderr
   457  			err := cmd.Run()
   458  			if err != nil {
   459  				if err, ok := err.(*exec.ExitError); ok && err.Exited() {
   460  					// Workaround for QEMU which always exits with an error.
   461  					return nil
   462  				}
   463  				return &commandError{"failed to run emulator with", tmppath, err}
   464  			}
   465  			return nil
   466  		}
   467  	})
   468  }
   469  
   470  func usage() {
   471  	fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
   472  	fmt.Fprintln(os.Stderr, "version:", version)
   473  	fmt.Fprintf(os.Stderr, "usage: %s command [-printir] [-target=<target>] -o <output> <input>\n", os.Args[0])
   474  	fmt.Fprintln(os.Stderr, "\ncommands:")
   475  	fmt.Fprintln(os.Stderr, "  build: compile packages and dependencies")
   476  	fmt.Fprintln(os.Stderr, "  run:   compile and run immediately")
   477  	fmt.Fprintln(os.Stderr, "  flash: compile and flash to the device")
   478  	fmt.Fprintln(os.Stderr, "  gdb:   run/flash and immediately enter GDB")
   479  	fmt.Fprintln(os.Stderr, "  clean: empty cache directory ("+cacheDir()+")")
   480  	fmt.Fprintln(os.Stderr, "  help:  print this help text")
   481  	fmt.Fprintln(os.Stderr, "\nflags:")
   482  	flag.PrintDefaults()
   483  }
   484  
   485  func handleCompilerError(err error) {
   486  	if err != nil {
   487  		if errUnsupported, ok := err.(*interp.Unsupported); ok {
   488  			// hit an unknown/unsupported instruction
   489  			fmt.Fprintln(os.Stderr, "unsupported instruction during init evaluation:")
   490  			errUnsupported.Inst.Dump()
   491  			fmt.Fprintln(os.Stderr)
   492  		} else if errCompiler, ok := err.(types.Error); ok {
   493  			fmt.Fprintln(os.Stderr, errCompiler)
   494  		} else if errLoader, ok := err.(loader.Errors); ok {
   495  			fmt.Fprintln(os.Stderr, "#", errLoader.Pkg.ImportPath)
   496  			for _, err := range errLoader.Errs {
   497  				fmt.Fprintln(os.Stderr, err)
   498  			}
   499  		} else {
   500  			fmt.Fprintln(os.Stderr, "error:", err)
   501  		}
   502  		os.Exit(1)
   503  	}
   504  }
   505  
   506  func main() {
   507  	outpath := flag.String("o", "", "output filename")
   508  	opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
   509  	gc := flag.String("gc", "", "garbage collector to use (none, dumb, marksweep)")
   510  	printIR := flag.Bool("printir", false, "print LLVM IR")
   511  	dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
   512  	target := flag.String("target", "", "LLVM target")
   513  	printSize := flag.String("size", "", "print sizes (none, short, full)")
   514  	nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
   515  	ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
   516  	port := flag.String("port", "/dev/ttyACM0", "flash port")
   517  	cFlags := flag.String("cflags", "", "additional cflags for compiler")
   518  	ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
   519  	wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
   520  
   521  	if len(os.Args) < 2 {
   522  		fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
   523  		usage()
   524  		os.Exit(1)
   525  	}
   526  	command := os.Args[1]
   527  
   528  	flag.CommandLine.Parse(os.Args[2:])
   529  	config := &BuildConfig{
   530  		opt:        *opt,
   531  		gc:         *gc,
   532  		printIR:    *printIR,
   533  		dumpSSA:    *dumpSSA,
   534  		debug:      !*nodebug,
   535  		printSizes: *printSize,
   536  		wasmAbi:    *wasmAbi,
   537  	}
   538  
   539  	if *cFlags != "" {
   540  		config.cFlags = strings.Split(*cFlags, " ")
   541  	}
   542  
   543  	if *ldFlags != "" {
   544  		config.ldFlags = strings.Split(*ldFlags, " ")
   545  	}
   546  
   547  	os.Setenv("CC", "clang -target="+*target)
   548  
   549  	switch command {
   550  	case "build":
   551  		if *outpath == "" {
   552  			fmt.Fprintln(os.Stderr, "No output filename supplied (-o).")
   553  			usage()
   554  			os.Exit(1)
   555  		}
   556  		if flag.NArg() != 1 {
   557  			fmt.Fprintln(os.Stderr, "No package specified.")
   558  			usage()
   559  			os.Exit(1)
   560  		}
   561  		target := *target
   562  		if target == "" && filepath.Ext(*outpath) == ".wasm" {
   563  			target = "wasm"
   564  		}
   565  		err := Build(flag.Arg(0), *outpath, target, config)
   566  		handleCompilerError(err)
   567  	case "build-builtins":
   568  		// Note: this command is only meant to be used while making a release!
   569  		if *outpath == "" {
   570  			fmt.Fprintln(os.Stderr, "No output filename supplied (-o).")
   571  			usage()
   572  			os.Exit(1)
   573  		}
   574  		if *target == "" {
   575  			fmt.Fprintln(os.Stderr, "No target (-target).")
   576  		}
   577  		err := compileBuiltins(*target, func(path string) error {
   578  			return moveFile(path, *outpath)
   579  		})
   580  		handleCompilerError(err)
   581  	case "flash", "gdb":
   582  		if *outpath != "" {
   583  			fmt.Fprintln(os.Stderr, "Output cannot be specified with the flash command.")
   584  			usage()
   585  			os.Exit(1)
   586  		}
   587  		if command == "flash" {
   588  			err := Flash(flag.Arg(0), *target, *port, config)
   589  			handleCompilerError(err)
   590  		} else {
   591  			if !config.debug {
   592  				fmt.Fprintln(os.Stderr, "Debug disabled while running gdb?")
   593  				usage()
   594  				os.Exit(1)
   595  			}
   596  			err := FlashGDB(flag.Arg(0), *target, *port, *ocdOutput, config)
   597  			handleCompilerError(err)
   598  		}
   599  	case "run":
   600  		if flag.NArg() != 1 {
   601  			fmt.Fprintln(os.Stderr, "No package specified.")
   602  			usage()
   603  			os.Exit(1)
   604  		}
   605  		err := Run(flag.Arg(0), *target, config)
   606  		handleCompilerError(err)
   607  	case "clean":
   608  		// remove cache directory
   609  		dir := cacheDir()
   610  		err := os.RemoveAll(dir)
   611  		if err != nil {
   612  			fmt.Fprintln(os.Stderr, "cannot clean cache:", err)
   613  			os.Exit(1)
   614  		}
   615  	case "help":
   616  		usage()
   617  	case "version":
   618  		fmt.Printf("tinygo version %s %s/%s\n", version, runtime.GOOS, runtime.GOARCH)
   619  	default:
   620  		fmt.Fprintln(os.Stderr, "Unknown command:", command)
   621  		usage()
   622  		os.Exit(1)
   623  	}
   624  }