main.go raw

   1  package main
   2  
   3  import (
   4  	"bytes"
   5  	"context"
   6  	"encoding/json"
   7  	"errors"
   8  	"flag"
   9  	"fmt"
  10  	"io"
  11  	"os"
  12  	"os/exec"
  13  	"path/filepath"
  14  	"regexp"
  15  	"runtime"
  16  	"runtime/pprof"
  17  	"sort"
  18  	"strconv"
  19  	"strings"
  20  	"time"
  21  
  22  	"github.com/google/shlex"
  23  	"moxie/builder"
  24  	"moxie/cache"
  25  	"moxie/compileopts"
  26  	"moxie/diagnostics"
  27  	"moxie/goenv"
  28  	"moxie/loader"
  29  	"golang.org/x/tools/go/buildutil"
  30  )
  31  
  32  type commandError struct {
  33  	Msg  string
  34  	File string
  35  	Err  error
  36  }
  37  
  38  func (e *commandError) Error() string {
  39  	return e.Msg + " " + e.File + ": " + e.Err.Error()
  40  }
  41  
  42  func moveFile(src, dst string) error {
  43  	err := os.Rename(src, dst)
  44  	if err == nil {
  45  		return nil
  46  	}
  47  	inf, err := os.Open(src)
  48  	if err != nil {
  49  		return err
  50  	}
  51  	defer inf.Close()
  52  	outf, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
  53  	if err != nil {
  54  		return err
  55  	}
  56  	_, err = io.Copy(outf, inf)
  57  	if err != nil {
  58  		return err
  59  	}
  60  	err = outf.Close()
  61  	if err != nil {
  62  		return err
  63  	}
  64  	return os.Remove(src)
  65  }
  66  
  67  func printCommand(cmd string, args ...string) {
  68  	command := append([]string{cmd}, args...)
  69  	for i, arg := range command {
  70  		const specialChars = "~`#$&*()\\|[]{};'\"<>?! "
  71  		if strings.ContainsAny(arg, specialChars) {
  72  			arg = "'" + strings.ReplaceAll(arg, `'`, `'\''`) + "'"
  73  			command[i] = arg
  74  		}
  75  	}
  76  	fmt.Fprintln(os.Stderr, strings.Join(command, " "))
  77  }
  78  
  79  // BuildRuntime compiles the runtime packages into a standalone .bc file
  80  // with external linkage on all symbols.
  81  func BuildRuntime(outpath string, config *compileopts.Config) error {
  82  	if outpath == "" {
  83  		root := goenv.Get("MOXIEROOT")
  84  		if root == "" {
  85  			root = "."
  86  		}
  87  		outpath = filepath.Join(root, "sysroot", "runtime_go.bc")
  88  	}
  89  	if !strings.HasSuffix(outpath, ".bc") {
  90  		outpath = outpath + ".bc"
  91  	}
  92  
  93  	tmpdir, err := os.MkdirTemp("", "moxie-rt-build")
  94  	if err != nil {
  95  		return err
  96  	}
  97  	if !config.Options.Work {
  98  		defer os.RemoveAll(tmpdir)
  99  	}
 100  
 101  	// Compile runtime as the "main" package - the loader resolves all
 102  	// runtime dependencies. SkipMainNameCheck + StandaloneRuntime flags
 103  	// bypass the main-package requirement and skip internalization.
 104  	_, err = builder.Build("runtime", outpath, tmpdir, config)
 105  	if err != nil {
 106  		return err
 107  	}
 108  
 109  	fmt.Fprintf(os.Stderr, "  -> %s\n", outpath)
 110  	return nil
 111  }
 112  
 113  // Build compiles and links the given package and writes it to outpath.
 114  func Build(pkgName, outpath string, config *compileopts.Config) error {
 115  	tmpdir, err := os.MkdirTemp("", "moxie")
 116  	if err != nil {
 117  		return err
 118  	}
 119  	if !config.Options.Work {
 120  		defer os.RemoveAll(tmpdir)
 121  	}
 122  
 123  	result, err := builder.Build(pkgName, outpath, tmpdir, config)
 124  	if err != nil {
 125  		return err
 126  	}
 127  
 128  	if result.Binary != "" {
 129  		if outpath == "" {
 130  			if strings.HasSuffix(pkgName, ".mx") {
 131  				outpath = filepath.Base(pkgName[:len(pkgName)-3]) + config.DefaultBinaryExtension()
 132  			} else {
 133  				outpath = filepath.Base(result.MainDir) + config.DefaultBinaryExtension()
 134  			}
 135  		}
 136  		if err := moveFile(result.Binary, outpath); err != nil {
 137  			return err
 138  		}
 139  	}
 140  	return nil
 141  }
 142  
 143  // Test runs the tests in the given package.
 144  func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options, outpath string) (bool, error) {
 145  	options.TestConfig.CompileTestBinary = true
 146  	config, err := builder.NewConfig(options)
 147  	if err != nil {
 148  		return false, err
 149  	}
 150  
 151  	testConfig := &options.TestConfig
 152  
 153  	var flags []string
 154  	if testConfig.Verbose {
 155  		flags = append(flags, "-test.v")
 156  	}
 157  	if testConfig.Short {
 158  		flags = append(flags, "-test.short")
 159  	}
 160  	if testConfig.RunRegexp != "" {
 161  		flags = append(flags, "-test.run="+testConfig.RunRegexp)
 162  	}
 163  	if testConfig.SkipRegexp != "" {
 164  		flags = append(flags, "-test.skip="+testConfig.SkipRegexp)
 165  	}
 166  	if testConfig.BenchRegexp != "" {
 167  		flags = append(flags, "-test.bench="+testConfig.BenchRegexp)
 168  	}
 169  	if testConfig.BenchTime != "" {
 170  		flags = append(flags, "-test.benchtime="+testConfig.BenchTime)
 171  	}
 172  	if testConfig.BenchMem {
 173  		flags = append(flags, "-test.benchmem")
 174  	}
 175  	if testConfig.Count != nil && *testConfig.Count != 1 {
 176  		flags = append(flags, "-test.count="+strconv.Itoa(*testConfig.Count))
 177  	}
 178  	if testConfig.Shuffle != "" {
 179  		flags = append(flags, "-test.shuffle="+testConfig.Shuffle)
 180  	}
 181  
 182  	logToStdout := testConfig.Verbose || testConfig.BenchRegexp != ""
 183  	var buf bytes.Buffer
 184  	var output io.Writer = &buf
 185  	if logToStdout {
 186  		output = os.Stdout
 187  	}
 188  
 189  	passed := false
 190  	var duration time.Duration
 191  	result, err := buildAndRun(pkgName, config, output, flags, nil, 0, func(cmd *exec.Cmd, result builder.BuildResult) error {
 192  		if testConfig.CompileOnly || outpath != "" {
 193  			if outpath == "" {
 194  				outpath = filepath.Base(result.MainDir) + ".test"
 195  			}
 196  			return moveFile(result.Binary, outpath)
 197  		}
 198  		if testConfig.CompileOnly {
 199  			passed = true
 200  			return nil
 201  		}
 202  		cmd.Dir = result.MainDir
 203  		start := time.Now()
 204  		err = cmd.Run()
 205  		duration = time.Since(start)
 206  		passed = err == nil
 207  		if !passed && !logToStdout {
 208  			buf.WriteTo(stdout)
 209  		}
 210  		if _, ok := err.(*exec.ExitError); ok {
 211  			return nil
 212  		}
 213  		return err
 214  	})
 215  
 216  	if testConfig.CompileOnly {
 217  		return true, err
 218  	}
 219  
 220  	importPath := strings.TrimSuffix(result.ImportPath, ".test")
 221  	var w io.Writer = stdout
 222  	if logToStdout {
 223  		w = os.Stdout
 224  	}
 225  	if err, ok := err.(loader.NoTestFilesError); ok {
 226  		fmt.Fprintf(w, "?   \t%s\t[no test files]\n", err.ImportPath)
 227  		return true, nil
 228  	} else if passed {
 229  		fmt.Fprintf(w, "ok  \t%s\t%.3fs\n", importPath, duration.Seconds())
 230  	} else {
 231  		fmt.Fprintf(w, "FAIL\t%s\t%.3fs\n", importPath, duration.Seconds())
 232  	}
 233  	return passed, err
 234  }
 235  
 236  // Run compiles and runs the given program.
 237  func Run(pkgName string, options *compileopts.Options, cmdArgs []string) error {
 238  	config, err := builder.NewConfig(options)
 239  	if err != nil {
 240  		return err
 241  	}
 242  	_, err = buildAndRun(pkgName, config, os.Stdout, cmdArgs, nil, 0, func(cmd *exec.Cmd, result builder.BuildResult) error {
 243  		return cmd.Run()
 244  	})
 245  	return err
 246  }
 247  
 248  func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, cmdArgs, environmentVars []string, timeout time.Duration, run func(cmd *exec.Cmd, result builder.BuildResult) error) (builder.BuildResult, error) {
 249  	needsEnvInVars := config.GOOS() == "js"
 250  	var args, env []string
 251  	if needsEnvInVars {
 252  		runtimeGlobals := make(map[string]string)
 253  		if len(cmdArgs) != 0 {
 254  			runtimeGlobals["osArgs"] = strings.Join(cmdArgs, "\x00")
 255  		}
 256  		if len(environmentVars) != 0 {
 257  			runtimeGlobals["osEnv"] = strings.Join(environmentVars, "\x00")
 258  		}
 259  		if len(runtimeGlobals) != 0 {
 260  			config.Options.GlobalValues = map[string]map[string]string{
 261  				"runtime": runtimeGlobals,
 262  			}
 263  		}
 264  	} else {
 265  		args = cmdArgs
 266  		env = environmentVars
 267  	}
 268  
 269  	tmpdir, err := os.MkdirTemp("", "moxie")
 270  	if err != nil {
 271  		return builder.BuildResult{}, err
 272  	}
 273  	if !config.Options.Work {
 274  		defer os.RemoveAll(tmpdir)
 275  	}
 276  
 277  	result, err := builder.Build(pkgName, "", tmpdir, config)
 278  	if err != nil {
 279  		return result, err
 280  	}
 281  
 282  	var ctx context.Context
 283  	if timeout != 0 {
 284  		var cancel context.CancelFunc
 285  		ctx, cancel = context.WithTimeout(context.Background(), timeout)
 286  		defer cancel()
 287  	}
 288  
 289  	name := result.Binary
 290  	if config.Target.Emulator != "" {
 291  		parts := strings.Fields(config.Target.Emulator)
 292  		for i, p := range parts {
 293  			parts[i] = strings.ReplaceAll(p, "{}", result.Binary)
 294  		}
 295  		name = parts[0]
 296  		args = append(parts[1:], args...)
 297  	}
 298  
 299  	var cmd *exec.Cmd
 300  	if ctx != nil {
 301  		cmd = exec.CommandContext(ctx, name, args...)
 302  	} else {
 303  		cmd = exec.Command(name, args...)
 304  	}
 305  	cmd.Env = append(cmd.Env, env...)
 306  	cmd.Stdout = stdout
 307  	cmd.Stderr = os.Stderr
 308  
 309  	config.Options.Semaphore <- struct{}{}
 310  	defer func() {
 311  		<-config.Options.Semaphore
 312  	}()
 313  
 314  	if config.Options.PrintCommands != nil {
 315  		config.Options.PrintCommands(cmd.Path, cmd.Args[1:]...)
 316  	}
 317  	err = run(cmd, result)
 318  	if err != nil {
 319  		if ctx != nil && ctx.Err() == context.DeadlineExceeded {
 320  			fmt.Fprintf(stdout, "--- timeout of %s exceeded, terminating...\n", timeout)
 321  			err = ctx.Err()
 322  		}
 323  		return result, &commandError{"failed to run compiled binary", result.Binary, err}
 324  	}
 325  	return result, nil
 326  }
 327  
 328  type globalValuesFlag map[string]map[string]string
 329  
 330  func (m globalValuesFlag) String() string { return "pkgpath.Var=value" }
 331  
 332  func (m globalValuesFlag) Set(value string) error {
 333  	equalsIndex := strings.IndexByte(value, '=')
 334  	if equalsIndex < 0 {
 335  		return errors.New("expected format pkgpath.Var=value")
 336  	}
 337  	pathAndName := value[:equalsIndex]
 338  	pointIndex := strings.LastIndexByte(pathAndName, '.')
 339  	if pointIndex < 0 {
 340  		return errors.New("expected format pkgpath.Var=value")
 341  	}
 342  	path := pathAndName[:pointIndex]
 343  	name := pathAndName[pointIndex+1:]
 344  	stringValue := value[equalsIndex+1:]
 345  	if m[path] == nil {
 346  		m[path] = make(map[string]string)
 347  	}
 348  	m[path][name] = stringValue
 349  	return nil
 350  }
 351  
 352  func parseGoLinkFlag(flagsString string) (map[string]map[string]string, string, error) {
 353  	set := flag.NewFlagSet("link", flag.ExitOnError)
 354  	globalVarValues := make(globalValuesFlag)
 355  	set.Var(globalVarValues, "X", "Set the value of the string variable to the given value.")
 356  	extLDFlags := set.String("extldflags", "", "additional flags to pass to external linker")
 357  	flags, err := shlex.Split(flagsString)
 358  	if err != nil {
 359  		return nil, "", err
 360  	}
 361  	err = set.Parse(flags)
 362  	if err != nil {
 363  		return nil, "", err
 364  	}
 365  	return map[string]map[string]string(globalVarValues), *extLDFlags, nil
 366  }
 367  
 368  func getListOfPackages(pkgs []string, options *compileopts.Options) ([]string, error) {
 369  	config, err := builder.NewConfig(options)
 370  	if err != nil {
 371  		return nil, err
 372  	}
 373  	return loader.MxListImportPaths(config, pkgs)
 374  }
 375  
 376  func handleCompilerError(err error) {
 377  	if err != nil {
 378  		wd, getwdErr := os.Getwd()
 379  		if getwdErr != nil {
 380  			wd = ""
 381  		}
 382  		diagnostics.CreateDiagnostics(err).WriteTo(os.Stderr, wd)
 383  		os.Exit(1)
 384  	}
 385  }
 386  
 387  func printBuildOutput(err error, jsonDiagnostics bool) {
 388  	if err == nil {
 389  		return
 390  	}
 391  	if jsonDiagnostics {
 392  		wd, _ := os.Getwd()
 393  		type jsonDiag struct {
 394  			ImportPath string
 395  			Action     string
 396  			Output     string `json:",omitempty"`
 397  		}
 398  		for _, diags := range diagnostics.CreateDiagnostics(err) {
 399  			if diags.ImportPath != "" {
 400  				output, _ := json.Marshal(jsonDiag{diags.ImportPath, "build-output", "# " + diags.ImportPath + "\n"})
 401  				os.Stdout.Write(append(output, '\n'))
 402  			}
 403  			for _, diag := range diags.Diagnostics {
 404  				w := &bytes.Buffer{}
 405  				diag.WriteTo(w, wd)
 406  				output, _ := json.Marshal(jsonDiag{diags.ImportPath, "build-output", w.String()})
 407  				os.Stdout.Write(append(output, '\n'))
 408  			}
 409  			output, _ := json.Marshal(jsonDiag{diags.ImportPath, "build-fail", ""})
 410  			os.Stdout.Write(append(output, '\n'))
 411  		}
 412  		os.Exit(1)
 413  	}
 414  	handleCompilerError(err)
 415  }
 416  
 417  const usageText = `moxie version %s
 418  
 419  usage: %s <command> [arguments]
 420  
 421  commands:
 422    build:   compile packages and dependencies
 423    run:     compile and run immediately
 424    test:    test packages
 425    clean:   empty the cache directory (%s)
 426    targets: list all supported targets
 427    version: print version information
 428    env:     print environment information
 429    vendor:  copy dependencies into vendor/
 430    header:  extract codec types from a package into a .mxh protocol header
 431    install: fetch, compile, and cache an external package binary + .mxh
 432    fetch:   fetch and cache all dependencies declared in moxie.mod
 433    cache:   manage the install cache (subcommands: list, clean)
 434    help:    print this help text
 435  `
 436  
 437  func usage(command string) {
 438  	fmt.Fprintf(os.Stderr, usageText, goenv.Version(), os.Args[0], goenv.Get("GOCACHE"))
 439  	if flag.Parsed() {
 440  		fmt.Fprintln(os.Stderr, "\nflags:")
 441  		flag.PrintDefaults()
 442  	}
 443  }
 444  
 445  func handleChdirFlag() {
 446  	used := 2
 447  	if used >= len(os.Args) {
 448  		return
 449  	}
 450  	var dir string
 451  	switch a := os.Args[used]; {
 452  	default:
 453  		return
 454  	case a == "-C", a == "--C":
 455  		if used+1 >= len(os.Args) {
 456  			return
 457  		}
 458  		dir = os.Args[used+1]
 459  		copy(os.Args[used:], os.Args[used+2:])
 460  		os.Args = os.Args[:len(os.Args)-2]
 461  	case strings.HasPrefix(a, "-C="), strings.HasPrefix(a, "--C="):
 462  		_, dir, _ = strings.Cut(a, "=")
 463  		copy(os.Args[used:], os.Args[used+1:])
 464  		os.Args = os.Args[:len(os.Args)-1]
 465  	}
 466  	if err := os.Chdir(dir); err != nil {
 467  		fmt.Fprintln(os.Stderr, "cannot chdir:", err)
 468  		os.Exit(1)
 469  	}
 470  }
 471  
 472  func main() {
 473  	if len(os.Args) < 2 {
 474  		fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
 475  		usage("")
 476  		os.Exit(1)
 477  	}
 478  	command := os.Args[1]
 479  
 480  	opt := flag.String("opt", "0", "optimization level: 0, 1, 2, s, z (Moxie default: 0 — LLVM optimization is forbidden; Moxie's source-to-codegen mapping is 1:1, the optimizer assumes semantics Moxie does not have)")
 481  	panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
 482  	scheduler := flag.String("scheduler", "", "which scheduler to use (none, tasks)")
 483  	work := flag.Bool("work", false, "print the name of the temporary build directory and do not delete this directory on exit")
 484  	interpTimeout := flag.Duration("interp-timeout", 180*time.Second, "interp optimization pass timeout")
 485  	var tags buildutil.TagsFlag
 486  	flag.Var(&tags, "tags", "a space-separated list of extra build tags")
 487  	target := flag.String("target", "", "target specification")
 488  	buildMode := flag.String("buildmode", "", "build mode to use (default)")
 489  	stackSize := flag.Uint64("stack-size", 0, "goroutine stack size")
 490  	printSize := flag.String("size", "", "print sizes (none, short, full)")
 491  	printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines")
 492  	printAllocsString := flag.String("print-allocs", "", "regular expression of functions for which heap allocations should be printed")
 493  	printCommands := flag.Bool("x", false, "Print commands")
 494  	flagJSON := flag.Bool("json", false, "print output in JSON format")
 495  	parallelism := flag.Int("p", runtime.GOMAXPROCS(0), "the number of build jobs that can run in parallel")
 496  	nodebug := flag.Bool("no-debug", false, "strip debug information")
 497  	nobounds := flag.Bool("nobounds", false, "do not emit bounds checks")
 498  	ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags")
 499  	llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable")
 500  	cpuprofile := flag.String("cpuprofile", "", "cpuprofile output")
 501  	memprofile := flag.String("memprofile", "", "heap profile output")
 502  
 503  	// Internal flags.
 504  	printIR := flag.Bool("internal-printir", false, "print LLVM IR")
 505  	dumpSSA := flag.Bool("internal-dumpssa", false, "dump internal Moxie SSA")
 506  	verifyIR := flag.Bool("internal-verifyir", false, "run extra verification steps on LLVM IR")
 507  	skipDwarf := flag.Bool("internal-nodwarf", false, "internal flag, use -no-debug instead")
 508  	standaloneRuntime := flag.Bool("standalone-runtime", false, "keep runtime symbols external (for build-runtime)")
 509  
 510  	var flagDeps, flagTest bool
 511  	if command == "help" || command == "list" {
 512  		flag.BoolVar(&flagDeps, "deps", false, "supply -deps flag to moxie list")
 513  		flag.BoolVar(&flagTest, "test", false, "supply -test flag to moxie list")
 514  	}
 515  	var outpath string
 516  	if command == "help" || command == "build" || command == "test" || command == "header" {
 517  		flag.StringVar(&outpath, "o", "", "output filename")
 518  	}
 519  
 520  	var testConfig compileopts.TestConfig
 521  	if command == "help" || command == "test" {
 522  		flag.BoolVar(&testConfig.CompileOnly, "c", false, "compile the test binary but do not run it")
 523  		flag.BoolVar(&testConfig.Verbose, "v", false, "verbose: print additional output")
 524  		flag.BoolVar(&testConfig.Short, "short", false, "short: run smaller test suite to save time")
 525  		flag.StringVar(&testConfig.RunRegexp, "run", "", "run: regexp of tests to run")
 526  		flag.StringVar(&testConfig.SkipRegexp, "skip", "", "skip: regexp of tests to skip")
 527  		testConfig.Count = flag.Int("count", 1, "count: number of times to run tests/benchmarks")
 528  		flag.StringVar(&testConfig.BenchRegexp, "bench", "", "bench: regexp of benchmarks to run")
 529  		flag.StringVar(&testConfig.BenchTime, "benchtime", "", "run each benchmark for duration d")
 530  		flag.BoolVar(&testConfig.BenchMem, "benchmem", false, "show memory stats for benchmarks")
 531  		flag.StringVar(&testConfig.Shuffle, "shuffle", "", "shuffle the order the tests and benchmarks run")
 532  	}
 533  
 534  	handleChdirFlag()
 535  	switch command {
 536  	case "clang", "ld.lld", "wasm-ld":
 537  		err := builder.RunTool(command, os.Args[2:]...)
 538  		if err != nil {
 539  			os.Exit(1)
 540  		}
 541  		os.Exit(0)
 542  	}
 543  
 544  	flag.CommandLine.Parse(os.Args[2:])
 545  	globalVarValues, extLDFlags, err := parseGoLinkFlag(*ldflags)
 546  	if err != nil {
 547  		fmt.Fprintln(os.Stderr, err)
 548  		os.Exit(1)
 549  	}
 550  
 551  	var printAllocs *regexp.Regexp
 552  	if *printAllocsString != "" {
 553  		printAllocs, err = regexp.Compile(*printAllocsString)
 554  		if err != nil {
 555  			fmt.Fprintln(os.Stderr, err)
 556  			os.Exit(1)
 557  		}
 558  	}
 559  
 560  	options := &compileopts.Options{
 561  		GOOS:          goenv.Get("GOOS"),
 562  		GOARCH:        goenv.Get("GOARCH"),
 563  		Target:        *target,
 564  		BuildMode:     *buildMode,
 565  		StackSize:     *stackSize,
 566  		Opt:           *opt,
 567  		PanicStrategy: *panicStrategy,
 568  		Scheduler:     *scheduler,
 569  		Work:          *work,
 570  		InterpTimeout: *interpTimeout,
 571  		PrintIR:       *printIR,
 572  		DumpSSA:       *dumpSSA,
 573  		VerifyIR:      *verifyIR,
 574  		SkipDWARF:     *skipDwarf,
 575  		Semaphore:     make(chan struct{}, *parallelism),
 576  		Debug:         !*nodebug,
 577  		Nobounds:      *nobounds,
 578  		PrintSizes:    *printSize,
 579  		PrintStacks:   *printStacks,
 580  		PrintAllocs:   printAllocs,
 581  		Tags:          []string(tags),
 582  		TestConfig:    testConfig,
 583  		GlobalValues:  globalVarValues,
 584  		LLVMFeatures:       *llvmFeatures,
 585  		StandaloneRuntime:  *standaloneRuntime,
 586  	}
 587  	if *printCommands {
 588  		options.PrintCommands = printCommand
 589  	}
 590  
 591  	if extLDFlags != "" {
 592  		options.ExtLDFlags, err = shlex.Split(extLDFlags)
 593  		if err != nil {
 594  			fmt.Fprintln(os.Stderr, "could not parse -extldflags:", err)
 595  			os.Exit(1)
 596  		}
 597  	}
 598  
 599  	err = options.Verify()
 600  	if err != nil {
 601  		fmt.Fprintln(os.Stderr, err.Error())
 602  		usage(command)
 603  		os.Exit(1)
 604  	}
 605  
 606  	if *cpuprofile != "" {
 607  		f, err := os.Create(*cpuprofile)
 608  		if err != nil {
 609  			fmt.Fprintln(os.Stderr, "could not create CPU profile: ", err)
 610  			os.Exit(1)
 611  		}
 612  		defer f.Close()
 613  		if err := pprof.StartCPUProfile(f); err != nil {
 614  			fmt.Fprintln(os.Stderr, "could not start CPU profile: ", err)
 615  			os.Exit(1)
 616  		}
 617  		defer pprof.StopCPUProfile()
 618  	}
 619  
 620  	if *memprofile != "" {
 621  		defer func() {
 622  			runtime.GC()
 623  			f, err := os.Create(*memprofile)
 624  			if err != nil {
 625  				fmt.Fprintln(os.Stderr, "could not create memory profile: ", err)
 626  				return
 627  			}
 628  			defer f.Close()
 629  			if err := pprof.WriteHeapProfile(f); err != nil {
 630  				fmt.Fprintln(os.Stderr, "could not write memory profile: ", err)
 631  			}
 632  		}()
 633  	}
 634  
 635  	switch command {
 636  	case "build":
 637  		pkgName := "."
 638  		if flag.NArg() == 1 {
 639  			pkgName = filepath.ToSlash(flag.Arg(0))
 640  		} else if flag.NArg() > 1 {
 641  			fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name")
 642  			usage(command)
 643  			os.Exit(1)
 644  		}
 645  		config, err := builder.NewConfig(options)
 646  		handleCompilerError(err)
 647  		err = Build(pkgName, outpath, config)
 648  		printBuildOutput(err, *flagJSON)
 649  	case "run":
 650  		if flag.NArg() < 1 {
 651  			fmt.Fprintln(os.Stderr, "No package specified.")
 652  			usage(command)
 653  			os.Exit(1)
 654  		}
 655  		pkgName := filepath.ToSlash(flag.Arg(0))
 656  		err := Run(pkgName, options, flag.Args()[1:])
 657  		printBuildOutput(err, *flagJSON)
 658  	case "test":
 659  		var pkgNames []string
 660  		for i := 0; i < flag.NArg(); i++ {
 661  			pkgNames = append(pkgNames, filepath.ToSlash(flag.Arg(i)))
 662  		}
 663  		if len(pkgNames) == 0 {
 664  			pkgNames = []string{"."}
 665  		}
 666  		explicitPkgNames, err := getListOfPackages(pkgNames, options)
 667  		if err != nil {
 668  			fmt.Printf("cannot resolve packages: %v\n", err)
 669  			os.Exit(1)
 670  		}
 671  		if outpath != "" && len(explicitPkgNames) > 1 {
 672  			fmt.Println("cannot use -o flag with multiple packages")
 673  			os.Exit(1)
 674  		}
 675  
 676  		fail := make(chan struct{}, 1)
 677  		bufs := make([]testOutputBuf, len(explicitPkgNames))
 678  		for i := range bufs {
 679  			bufs[i].entries = make(chan outputEntry, 64)
 680  		}
 681  		flusherDone := make(chan struct{})
 682  		go func() {
 683  			defer close(flusherDone)
 684  			for i := range bufs {
 685  				bufs[i].flush(os.Stdout, os.Stderr)
 686  			}
 687  		}()
 688  		testSema := make(chan struct{}, cap(options.Semaphore))
 689  		testsDone := make(chan struct{}, len(explicitPkgNames))
 690  		for i, pkgName := range explicitPkgNames {
 691  			pkgName := pkgName
 692  			buf := &bufs[i]
 693  			testSema <- struct{}{}
 694  			go func() {
 695  				defer func() { <-testSema }()
 696  				defer func() { close(buf.entries); testsDone <- struct{}{} }()
 697  				stdout := (*testStdout)(buf)
 698  				stderr := (*testStderr)(buf)
 699  				passed, err := Test(pkgName, stdout, stderr, options, outpath)
 700  				if err != nil {
 701  					wd, _ := os.Getwd()
 702  					diagnostics.CreateDiagnostics(err).WriteTo(os.Stderr, wd)
 703  					os.Exit(1)
 704  				}
 705  				if !passed {
 706  					select {
 707  					case fail <- struct{}{}:
 708  					default:
 709  					}
 710  				}
 711  			}()
 712  		}
 713  		for range explicitPkgNames {
 714  			<-testsDone
 715  		}
 716  		<-flusherDone
 717  		close(fail)
 718  		if _, fail := <-fail; fail {
 719  			os.Exit(1)
 720  		}
 721  	case "targets":
 722  		specs := compileopts.GetTargetSpecs()
 723  		names := make([]string, 0, len(specs))
 724  		for key := range specs {
 725  			names = append(names, key)
 726  		}
 727  		sort.Strings(names)
 728  		for _, name := range names {
 729  			fmt.Println(name)
 730  		}
 731  	case "info":
 732  		config, err := builder.NewConfig(options)
 733  		if err != nil {
 734  			fmt.Fprintln(os.Stderr, err)
 735  			os.Exit(1)
 736  		}
 737  		config.GoMinorVersion = 0
 738  		if *flagJSON {
 739  			data, _ := json.MarshalIndent(struct {
 740  				Target     *compileopts.TargetSpec `json:"target"`
 741  				GOOS       string                  `json:"goos"`
 742  				GOARCH     string                  `json:"goarch"`
 743  				BuildTags  []string                `json:"build_tags"`
 744  				Scheduler  string                  `json:"scheduler"`
 745  				LLVMTriple string                  `json:"llvm_triple"`
 746  			}{
 747  				Target:     config.Target,
 748  				GOOS:       config.GOOS(),
 749  				GOARCH:     config.GOARCH(),
 750  				BuildTags:  config.BuildTags(),
 751  				Scheduler:  config.Scheduler(),
 752  				LLVMTriple: config.Triple(),
 753  			}, "", "  ")
 754  			fmt.Println(string(data))
 755  		} else {
 756  			fmt.Printf("LLVM triple:       %s\n", config.Triple())
 757  			fmt.Printf("GOOS:              %s\n", config.GOOS())
 758  			fmt.Printf("GOARCH:            %s\n", config.GOARCH())
 759  			fmt.Printf("build tags:        %s\n", strings.Join(config.BuildTags(), " "))
 760  			fmt.Printf("scheduler:         %s\n", config.Scheduler())
 761  		}
 762  	case "list":
 763  		config, err := builder.NewConfig(options)
 764  		if err != nil {
 765  			fmt.Fprintln(os.Stderr, err)
 766  			os.Exit(1)
 767  		}
 768  		var extraArgs []string
 769  		if *flagJSON {
 770  			extraArgs = append(extraArgs, "-json")
 771  		}
 772  		if flagDeps {
 773  			extraArgs = append(extraArgs, "-deps")
 774  		}
 775  		if flagTest {
 776  			extraArgs = append(extraArgs, "-test")
 777  		}
 778  		cmd, err := loader.List(config, extraArgs, flag.Args())
 779  		if err != nil {
 780  			fmt.Fprintln(os.Stderr, "failed to run `moxie list`:", err)
 781  			os.Exit(1)
 782  		}
 783  		cmd.Stdout = os.Stdout
 784  		cmd.Stderr = os.Stderr
 785  		err = cmd.Run()
 786  		if err != nil {
 787  			if exitErr, ok := err.(*exec.ExitError); ok {
 788  				os.Exit(exitErr.ExitCode())
 789  			}
 790  			fmt.Fprintln(os.Stderr, "failed to run `moxie list`:", err)
 791  			os.Exit(1)
 792  		}
 793  	case "vendor":
 794  		wd, err := os.Getwd()
 795  		if err != nil {
 796  			fmt.Fprintln(os.Stderr, err)
 797  			os.Exit(1)
 798  		}
 799  		if err := loader.Vendor(wd); err != nil {
 800  			fmt.Fprintln(os.Stderr, err)
 801  			os.Exit(1)
 802  		}
 803  	case "clean":
 804  		err := os.RemoveAll(goenv.Get("GOCACHE"))
 805  		if err != nil {
 806  			fmt.Fprintln(os.Stderr, "cannot clean cache:", err)
 807  			os.Exit(1)
 808  		}
 809  	case "header":
 810  		pkgName := "."
 811  		if flag.NArg() >= 1 {
 812  			pkgName = filepath.ToSlash(flag.Arg(0))
 813  		}
 814  		if outpath == "" {
 815  			outpath = "."
 816  		}
 817  		// Scan remaining args for --prev <old.mxh>.
 818  		prevPath := ""
 819  		args := flag.Args()
 820  		for i := 1; i < len(args)-1; i++ {
 821  			if args[i] == "--prev" || args[i] == "-prev" {
 822  				prevPath = args[i+1]
 823  			}
 824  		}
 825  		err := Header(pkgName, outpath, prevPath, options)
 826  		if err != nil {
 827  			fmt.Fprintln(os.Stderr, err)
 828  			os.Exit(1)
 829  		}
 830  	case "install":
 831  		if flag.NArg() < 1 {
 832  			fmt.Fprintln(os.Stderr, "install requires a package path")
 833  			usage(command)
 834  			os.Exit(1)
 835  		}
 836  		pkgName := filepath.ToSlash(flag.Arg(0))
 837  		err := Install(pkgName, options)
 838  		if err != nil {
 839  			fmt.Fprintln(os.Stderr, err)
 840  			os.Exit(1)
 841  		}
 842  	case "fetch":
 843  		err := Fetch(options)
 844  		if err != nil {
 845  			fmt.Fprintln(os.Stderr, err)
 846  			os.Exit(1)
 847  		}
 848  	case "cache":
 849  		subcmd := ""
 850  		if flag.NArg() >= 1 {
 851  			subcmd = flag.Arg(0)
 852  		}
 853  		switch subcmd {
 854  		case "clean":
 855  			if err := cache.Clean(); err != nil {
 856  				fmt.Fprintln(os.Stderr, "cannot clean install cache:", err)
 857  				os.Exit(1)
 858  			}
 859  		case "list":
 860  			paths, err := cache.List()
 861  			if err != nil {
 862  				fmt.Fprintln(os.Stderr, err)
 863  				os.Exit(1)
 864  			}
 865  			for _, p := range paths {
 866  				fmt.Println(p)
 867  			}
 868  		default:
 869  			fmt.Fprintln(os.Stderr, "cache subcommand must be 'list' or 'clean'")
 870  			os.Exit(1)
 871  		}
 872  	case "build-runtime":
 873  		options.StandaloneRuntime = true
 874  		config, err := builder.NewConfig(options)
 875  		handleCompilerError(err)
 876  		err = BuildRuntime(outpath, config)
 877  		printBuildOutput(err, *flagJSON)
 878  	case "help":
 879  		usage("")
 880  	case "version":
 881  		goversion := "<unknown>"
 882  		if s, err := goenv.GorootVersionString(); err == nil {
 883  			goversion = s
 884  		}
 885  		fmt.Printf("moxie version %s %s/%s (using moxie version %s)\n", goenv.Version(), runtime.GOOS, runtime.GOARCH, goversion)
 886  	case "env":
 887  		if flag.NArg() == 0 {
 888  			for _, key := range goenv.Keys {
 889  				fmt.Printf("%s=%#v\n", key, goenv.Get(key))
 890  			}
 891  		} else {
 892  			for i := 0; i < flag.NArg(); i++ {
 893  				fmt.Println(goenv.Get(flag.Arg(i)))
 894  			}
 895  		}
 896  	default:
 897  		fmt.Fprintln(os.Stderr, "Unknown command:", command)
 898  		usage("")
 899  		os.Exit(1)
 900  	}
 901  }
 902  
 903  // testOutputBuf buffers the output of concurrent tests.
 904  // Output entries are sent through a channel; the flusher goroutine is the
 905  // sole consumer, so no mutex is needed.
 906  type testOutputBuf struct {
 907  	entries chan outputEntry
 908  }
 909  
 910  func (b *testOutputBuf) flush(stdout, stderr io.Writer) {
 911  	for e := range b.entries {
 912  		if e.stderr {
 913  			stderr.Write(e.data)
 914  		} else {
 915  			stdout.Write(e.data)
 916  		}
 917  	}
 918  }
 919  
 920  type testStdout testOutputBuf
 921  
 922  func (out *testStdout) Write(data []byte) (int, error) {
 923  	cp := make([]byte, len(data))
 924  	copy(cp, data)
 925  	out.entries <- outputEntry{stderr: false, data: cp}
 926  	return len(data), nil
 927  }
 928  
 929  type testStderr testOutputBuf
 930  
 931  func (out *testStderr) Write(data []byte) (int, error) {
 932  	cp := make([]byte, len(data))
 933  	copy(cp, data)
 934  	out.entries <- outputEntry{stderr: true, data: cp}
 935  	return len(data), nil
 936  }
 937  
 938  type outputEntry struct {
 939  	stderr bool
 940  	data   []byte
 941  }
 942