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