main.go raw

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