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