aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/cmd/root.go
blob: d8d0e33c25d77d75c98c99406d6a10689a395a34 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Package cmd holds the root cobra command for turbo
package cmd

import (
	"context"
	"fmt"
	"os"
	"runtime/pprof"
	"runtime/trace"

	"github.com/pkg/errors"
	"github.com/vercel/turbo/cli/internal/cmdutil"
	"github.com/vercel/turbo/cli/internal/daemon"
	"github.com/vercel/turbo/cli/internal/process"
	"github.com/vercel/turbo/cli/internal/prune"
	"github.com/vercel/turbo/cli/internal/run"
	"github.com/vercel/turbo/cli/internal/signals"
	"github.com/vercel/turbo/cli/internal/turbostate"
	"github.com/vercel/turbo/cli/internal/util"
)

func initializeOutputFiles(helper *cmdutil.Helper, parsedArgs *turbostate.ParsedArgsFromRust) error {
	if parsedArgs.Trace != "" {
		cleanup, err := createTraceFile(parsedArgs.Trace)
		if err != nil {
			return fmt.Errorf("failed to create trace file: %v", err)
		}
		helper.RegisterCleanup(cleanup)
	}
	if parsedArgs.Heap != "" {
		cleanup, err := createHeapFile(parsedArgs.Heap)
		if err != nil {
			return fmt.Errorf("failed to create heap file: %v", err)
		}
		helper.RegisterCleanup(cleanup)
	}
	if parsedArgs.CPUProfile != "" {
		cleanup, err := createCpuprofileFile(parsedArgs.CPUProfile)
		if err != nil {
			return fmt.Errorf("failed to create CPU profile file: %v", err)
		}
		helper.RegisterCleanup(cleanup)
	}

	return nil
}

// RunWithArgs runs turbo with the ParsedArgsFromRust that is passed from the Rust side.
func RunWithArgs(args *turbostate.ParsedArgsFromRust, turboVersion string) int {
	util.InitPrintf()
	// TODO: replace this with a context
	signalWatcher := signals.NewWatcher()
	helper := cmdutil.NewHelper(turboVersion, args)
	ctx := context.Background()

	err := initializeOutputFiles(helper, args)
	if err != nil {
		fmt.Printf("%v", err)
		return 1
	}
	defer helper.Cleanup(args)

	doneCh := make(chan struct{})
	var execErr error
	go func() {
		command := args.Command
		if command.Daemon != nil {
			execErr = daemon.ExecuteDaemon(ctx, helper, signalWatcher, args)
		} else if command.Prune != nil {
			execErr = prune.ExecutePrune(helper, args)
		} else if command.Run != nil {
			execErr = run.ExecuteRun(ctx, helper, signalWatcher, args)
		} else {
			execErr = fmt.Errorf("unknown command: %v", command)
		}

		close(doneCh)
	}()

	// Wait for either our command to finish, in which case we need to clean up,
	// or to receive a signal, in which case the signal handler above does the cleanup
	select {
	case <-doneCh:
		// We finished whatever task we were running
		signalWatcher.Close()
		exitErr := &process.ChildExit{}
		if errors.As(execErr, &exitErr) {
			return exitErr.ExitCode
		} else if execErr != nil {
			fmt.Printf("Turbo error: %v\n", execErr)
			return 1
		}
		return 0
	case <-signalWatcher.Done():
		// We caught a signal, which already called the close handlers
		return 1
	}
}

type profileCleanup func() error

// Close implements io.Close for profileCleanup
func (pc profileCleanup) Close() error {
	return pc()
}

// To view a CPU trace, use "go tool trace [file]". Note that the trace
// viewer doesn't work under Windows Subsystem for Linux for some reason.
func createTraceFile(traceFile string) (profileCleanup, error) {
	f, err := os.Create(traceFile)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to create trace file: %v", traceFile)
	}
	if err := trace.Start(f); err != nil {
		return nil, errors.Wrap(err, "failed to start tracing")
	}
	return func() error {
		trace.Stop()
		return f.Close()
	}, nil
}

// To view a heap trace, use "go tool pprof [file]" and type "top". You can
// also drop it into https://speedscope.app and use the "left heavy" or
// "sandwich" view modes.
func createHeapFile(heapFile string) (profileCleanup, error) {
	f, err := os.Create(heapFile)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to create heap file: %v", heapFile)
	}
	return func() error {
		if err := pprof.WriteHeapProfile(f); err != nil {
			// we don't care if we fail to close the file we just failed to write to
			_ = f.Close()
			return errors.Wrapf(err, "failed to write heap file: %v", heapFile)
		}
		return f.Close()
	}, nil
}

// To view a CPU profile, drop the file into https://speedscope.app.
// Note: Running the CPU profiler doesn't work under Windows subsystem for
// Linux. The profiler has to be built for native Windows and run using the
// command prompt instead.
func createCpuprofileFile(cpuprofileFile string) (profileCleanup, error) {
	f, err := os.Create(cpuprofileFile)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to create cpuprofile file: %v", cpuprofileFile)
	}
	if err := pprof.StartCPUProfile(f); err != nil {
		return nil, errors.Wrap(err, "failed to start CPU profiling")
	}
	return func() error {
		pprof.StopCPUProfile()
		return f.Close()
	}, nil
}