@@ -3,123 +3,150 @@ package run
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "io"
6
7
"os/exec"
7
- "strings"
8
8
9
9
"github.com/rjeczalik/notify"
10
-
11
10
"github.com/sourcegraph/sourcegraph/dev/sg/internal/secrets"
11
+ "github.com/sourcegraph/sourcegraph/dev/sg/internal/std"
12
+ "github.com/sourcegraph/sourcegraph/lib/errors"
13
+ "github.com/sourcegraph/sourcegraph/lib/output"
14
+ "github.com/sourcegraph/sourcegraph/lib/process"
12
15
)
13
16
14
17
// A BazelCommand is a command definition for sg run/start that uses
15
18
// bazel under the hood. It will handle restarting itself autonomously,
16
19
// as long as iBazel is running and watch that specific target.
17
20
type BazelCommand struct {
18
- Name string
19
- Description string `yaml:"description"`
20
- Target string `yaml:"target"`
21
- Args string `yaml:"args"`
22
- PreCmd string `yaml:"precmd"`
23
- Env map [string ]string `yaml:"env"`
24
- IgnoreStdout bool `yaml:"ignoreStdout"`
25
- IgnoreStderr bool `yaml:"ignoreStderr"`
26
- ContinueWatchOnExit bool `yaml:"continueWatchOnExit"`
27
- // Preamble is a short and visible message, displayed when the command is launched.
28
- Preamble string `yaml:"preamble"`
21
+ Name string
22
+ Description string `yaml:"description"`
23
+ Target string `yaml:"target"`
24
+ Args string `yaml:"args"`
25
+ PreCmd string `yaml:"precmd"`
26
+ Env map [string ]string `yaml:"env"`
27
+ IgnoreStdout bool `yaml:"ignoreStdout"`
28
+ IgnoreStderr bool `yaml:"ignoreStderr"`
29
29
ExternalSecrets map [string ]secrets.ExternalSecret `yaml:"external_secrets"`
30
-
31
- // RunTarget specifies a target that should be run via `bazel run $RunTarget` instead of directly executing the binary.
32
- RunTarget string `yaml:"runTarget"`
33
30
}
34
31
35
- func (bc BazelCommand ) GetName () string {
36
- return bc .Name
32
+ func (bc * BazelCommand ) BinLocation () ( string , error ) {
33
+ return binLocation ( bc .Target )
37
34
}
38
35
39
- func (bc BazelCommand ) GetContinueWatchOnExit () bool {
40
- return bc .ContinueWatchOnExit
41
- }
36
+ func (bc * BazelCommand ) watch (ctx context.Context ) (<- chan struct {}, error ) {
37
+ // Grab the location of the binary in bazel-out.
38
+ binLocation , err := bc .BinLocation ()
39
+ if err != nil {
40
+ return nil , err
41
+ }
42
42
43
- func (bc BazelCommand ) GetEnv () map [string ]string {
44
- return bc .Env
45
- }
43
+ // Set up the watcher.
44
+ restart := make (chan struct {})
45
+ events := make (chan notify.EventInfo , 1 )
46
+ if err := notify .Watch (binLocation , events , notify .All ); err != nil {
47
+ return nil , err
48
+ }
46
49
47
- func (bc BazelCommand ) GetIgnoreStdout () bool {
48
- return bc .IgnoreStdout
49
- }
50
+ // Start watching for a freshly compiled version of the binary.
51
+ go func () {
52
+ defer close (events )
53
+ defer notify .Stop (events )
54
+
55
+ for {
56
+ select {
57
+ case <- ctx .Done ():
58
+ return
59
+ case e := <- events :
60
+ if e .Event () != notify .Remove {
61
+ restart <- struct {}{}
62
+ }
63
+ }
50
64
51
- func (bc BazelCommand ) GetIgnoreStderr () bool {
52
- return bc .IgnoreStderr
53
- }
65
+ }
66
+ }()
54
67
55
- func (bc BazelCommand ) GetPreamble () string {
56
- return bc .Preamble
68
+ return restart , nil
57
69
}
58
70
59
- func (bc BazelCommand ) GetBinaryLocation () (string , error ) {
60
- baseOutput , err := outputPath ()
71
+ func (bc * BazelCommand ) Start (ctx context.Context , dir string , parentEnv map [string ]string ) error {
72
+ std .Out .WriteLine (output .Styledf (output .StylePending , "Running %s..." , bc .Name ))
73
+
74
+ // Run the binary for the first time.
75
+ cancel , err := bc .start (ctx , dir , parentEnv )
61
76
if err != nil {
62
- return " " , err
77
+ return errors . Wrapf ( err , "failed to start Bazel command %q " , bc . Name )
63
78
}
64
- // Trim "bazel-out" because the next bazel query will include it.
65
- outputPath := strings .TrimSuffix (strings .TrimSpace (string (baseOutput )), "bazel-out" )
66
79
67
- // Get the binary from the specific target.
68
- cmd := exec .Command ("bazel" , "cquery" , bc .Target , "--output=files" )
69
- baseOutput , err = cmd .Output ()
80
+ // Restart when the binary change.
81
+ wantRestart , err := bc .watch (ctx )
70
82
if err != nil {
71
- return "" , err
83
+ return err
72
84
}
73
- binPath := strings .TrimSpace (string (baseOutput ))
74
85
75
- return fmt .Sprintf ("%s%s" , outputPath , binPath ), nil
86
+ // Wait forever until we're asked to stop or that restarting returns an error.
87
+ for {
88
+ select {
89
+ case <- ctx .Done ():
90
+ return ctx .Err ()
91
+ case <- wantRestart :
92
+ std .Out .WriteLine (output .Styledf (output .StylePending , "Restarting %s..." , bc .Name ))
93
+ cancel ()
94
+ cancel , err = bc .start (ctx , dir , parentEnv )
95
+ if err != nil {
96
+ return err
97
+ }
98
+ }
99
+ }
76
100
}
77
101
78
- func (bc BazelCommand ) GetExternalSecrets () map [string ]secrets.ExternalSecret {
79
- return bc .ExternalSecrets
80
- }
102
+ func (bc * BazelCommand ) start (ctx context.Context , dir string , parentEnv map [string ]string ) (func (), error ) {
103
+ binLocation , err := bc .BinLocation ()
104
+ if err != nil {
105
+ return nil , err
106
+ }
81
107
82
- func (bc BazelCommand ) watchPaths () ([]string , error ) {
83
- // If no target is defined, there is nothing to be built and watched
84
- if bc .Target == "" {
85
- return nil , nil
108
+ sc := & startedCmd {
109
+ stdoutBuf : & prefixSuffixSaver {N : 32 << 10 },
110
+ stderrBuf : & prefixSuffixSaver {N : 32 << 10 },
86
111
}
87
- // Grab the location of the binary in bazel-out.
88
- binLocation , err := bc .GetBinaryLocation ()
112
+
113
+ commandCtx , cancel := context .WithCancel (ctx )
114
+ sc .cancel = cancel
115
+ sc .Cmd = exec .CommandContext (commandCtx , "bash" , "-c" , fmt .Sprintf ("%s\n %s" , bc .PreCmd , binLocation ))
116
+ sc .Cmd .Dir = dir
117
+
118
+ secretsEnv , err := getSecrets (ctx , bc .Name , bc .ExternalSecrets )
89
119
if err != nil {
90
- return nil , err
120
+ std .Out .WriteLine (output .Styledf (output .StyleWarning , "[%s] %s %s" ,
121
+ bc .Name , output .EmojiFailure , err .Error ()))
91
122
}
92
- return []string {binLocation }, nil
93
123
94
- }
124
+ sc . Cmd . Env = makeEnv ( parentEnv , secretsEnv , bc . Env )
95
125
96
- func (bc BazelCommand ) StartWatch (ctx context.Context ) (<- chan struct {}, error ) {
97
- if watchPaths , err := bc .watchPaths (); err != nil {
98
- return nil , err
126
+ var stdoutWriter , stderrWriter io.Writer
127
+ logger := newCmdLogger (commandCtx , bc .Name , std .Out .Output )
128
+ if bc .IgnoreStdout {
129
+ std .Out .WriteLine (output .Styledf (output .StyleSuggestion , "Ignoring stdout of %s" , bc .Name ))
130
+ stdoutWriter = sc .stdoutBuf
99
131
} else {
100
- // skip remove events as we don't care about files being removed, we only
101
- // want to know when the binary has been rebuilt
102
- return WatchPaths (ctx , watchPaths , notify .Remove )
132
+ stdoutWriter = io .MultiWriter (logger , sc .stdoutBuf )
103
133
}
104
- }
105
-
106
- func (bc BazelCommand ) GetExecCmd (ctx context.Context ) (* exec.Cmd , error ) {
107
- var cmd string
108
- var err error
109
- if bc .RunTarget != "" {
110
- cmd = "bazel run " + bc .RunTarget
134
+ if bc .IgnoreStderr {
135
+ std .Out .WriteLine (output .Styledf (output .StyleSuggestion , "Ignoring stderr of %s" , bc .Name ))
136
+ stderrWriter = sc .stderrBuf
111
137
} else {
112
- if cmd , err = bc .GetBinaryLocation (); err != nil {
113
- return nil , err
114
- }
138
+ stderrWriter = io .MultiWriter (logger , sc .stderrBuf )
115
139
}
116
140
117
- return exec .CommandContext (ctx , "bash" , "-c" , fmt .Sprintf ("%s\n %s" , bc .PreCmd , cmd )), nil
118
- }
141
+ eg , err := process .PipeOutputUnbuffered (ctx , sc .Cmd , stdoutWriter , stderrWriter )
142
+ if err != nil {
143
+ return nil , err
144
+ }
145
+ sc .outEg = eg
146
+
147
+ if err := sc .Start (); err != nil {
148
+ return nil , err
149
+ }
119
150
120
- func outputPath () ([]byte , error ) {
121
- // Get the output directory from Bazel, which varies depending on which OS
122
- // we're running against.
123
- cmd := exec .Command ("bazel" , "info" , "output_path" )
124
- return cmd .Output ()
151
+ return cancel , nil
125
152
}
0 commit comments