Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | package com.google.devtools.build.lib.runtime; |
| 15 | |
| 16 | import com.google.devtools.build.lib.events.Event; |
| 17 | import com.google.devtools.build.lib.events.EventHandler; |
| 18 | import com.google.devtools.build.lib.events.EventKind; |
| 19 | import com.google.devtools.build.lib.events.Location; |
| 20 | import com.google.devtools.build.lib.util.io.OutErr; |
| 21 | import com.google.devtools.common.options.EnumConverter; |
| 22 | import com.google.devtools.common.options.Option; |
| 23 | import com.google.devtools.common.options.OptionsBase; |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 24 | import com.google.devtools.common.options.OptionsParser.OptionUsageRestrictions; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 25 | import java.io.IOException; |
| 26 | import java.io.OutputStream; |
| 27 | import java.io.PrintStream; |
| 28 | import java.util.EnumSet; |
| 29 | import java.util.Set; |
Michajlo Matijkiw | 440fbc7 | 2015-08-04 21:03:25 +0000 | [diff] [blame] | 30 | import java.util.logging.Level; |
| 31 | import java.util.logging.Logger; |
Klaus Aehlig | 0436ce0 | 2016-10-07 11:31:52 +0000 | [diff] [blame] | 32 | import org.joda.time.format.DateTimeFormat; |
| 33 | import org.joda.time.format.DateTimeFormatter; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 34 | |
| 35 | /** |
| 36 | * BlazeCommandEventHandler: an event handler established for the duration of a |
| 37 | * single Blaze command. |
| 38 | */ |
| 39 | public class BlazeCommandEventHandler implements EventHandler { |
| 40 | |
Michajlo Matijkiw | 440fbc7 | 2015-08-04 21:03:25 +0000 | [diff] [blame] | 41 | private static final Logger LOG = Logger.getLogger(BlazeCommandEventHandler.class.getName()); |
| 42 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 43 | public enum UseColor { YES, NO, AUTO } |
| 44 | public enum UseCurses { YES, NO, AUTO } |
| 45 | |
| 46 | public static class UseColorConverter extends EnumConverter<UseColor> { |
| 47 | public UseColorConverter() { |
| 48 | super(UseColor.class, "--color setting"); |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | public static class UseCursesConverter extends EnumConverter<UseCurses> { |
| 53 | public UseCursesConverter() { |
| 54 | super(UseCurses.class, "--curses setting"); |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | public static class Options extends OptionsBase { |
| 59 | |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 60 | @Option( |
| 61 | name = "show_progress", |
| 62 | defaultValue = "true", |
| 63 | category = "verbosity", |
| 64 | help = "Display progress messages during a build." |
| 65 | ) |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 66 | public boolean showProgress; |
| 67 | |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 68 | @Option( |
| 69 | name = "show_task_finish", |
| 70 | defaultValue = "false", |
| 71 | category = "verbosity", |
| 72 | help = "Display progress messages when tasks complete, not just when they start." |
| 73 | ) |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 74 | public boolean showTaskFinish; |
| 75 | |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 76 | @Option( |
| 77 | name = "show_progress_rate_limit", |
| 78 | defaultValue = "0.03", // A nice middle ground; snappy but not too spammy in logs. |
| 79 | category = "verbosity", |
| 80 | help = "Minimum number of seconds between progress messages in the output." |
| 81 | ) |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 82 | public double showProgressRateLimit; |
| 83 | |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 84 | @Option( |
| 85 | name = "color", |
| 86 | defaultValue = "auto", |
| 87 | converter = UseColorConverter.class, |
| 88 | category = "verbosity", |
| 89 | help = "Use terminal controls to colorize output." |
| 90 | ) |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 91 | public UseColor useColorEnum; |
| 92 | |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 93 | @Option( |
| 94 | name = "curses", |
| 95 | defaultValue = "auto", |
| 96 | converter = UseCursesConverter.class, |
| 97 | category = "verbosity", |
| 98 | help = "Use terminal cursor controls to minimize scrolling output" |
| 99 | ) |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 100 | public UseCurses useCursesEnum; |
| 101 | |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 102 | @Option( |
| 103 | name = "terminal_columns", |
| 104 | defaultValue = "80", |
| 105 | optionUsageRestrictions = OptionUsageRestrictions.HIDDEN, |
| 106 | help = "A system-generated parameter which specifies the terminal width in columns." |
| 107 | ) |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 108 | public int terminalColumns; |
| 109 | |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 110 | @Option( |
| 111 | name = "isatty", |
| 112 | defaultValue = "false", |
| 113 | optionUsageRestrictions = OptionUsageRestrictions.HIDDEN, |
| 114 | help = |
| 115 | "A system-generated parameter which is used to notify the " |
| 116 | + "server whether this client is running in a terminal. " |
| 117 | + "If this is set to false, then '--color=auto' will be treated as '--color=no'. " |
| 118 | + "If this is set to true, then '--color=auto' will be treated as '--color=yes'." |
| 119 | ) |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 120 | public boolean isATty; |
| 121 | |
| 122 | // This lives here (as opposed to the more logical BuildRequest.Options) |
| 123 | // because the client passes it to the server *always*. We don't want the |
| 124 | // client to have to figure out when it should or shouldn't to send it. |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 125 | @Option( |
| 126 | name = "emacs", |
| 127 | defaultValue = "false", |
| 128 | optionUsageRestrictions = OptionUsageRestrictions.UNDOCUMENTED, |
| 129 | help = |
| 130 | "A system-generated parameter which is true iff EMACS=t or INSIDE_EMACS is set " |
| 131 | + "in the environment of the client. This option controls certain display " |
| 132 | + "features." |
| 133 | ) |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 134 | public boolean runningInEmacs; |
| 135 | |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 136 | @Option( |
| 137 | name = "show_timestamps", |
| 138 | defaultValue = "false", |
| 139 | category = "verbosity", |
| 140 | help = "Include timestamps in messages" |
| 141 | ) |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 142 | public boolean showTimestamp; |
| 143 | |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 144 | @Option( |
| 145 | name = "progress_in_terminal_title", |
| 146 | defaultValue = "false", |
| 147 | category = "verbosity", |
| 148 | help = |
| 149 | "Show the command progress in the terminal title. " |
| 150 | + "Useful to see what blaze is doing when having multiple terminal tabs." |
| 151 | ) |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 152 | public boolean progressInTermTitle; |
| 153 | |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 154 | @Option( |
| 155 | name = "experimental_external_repositories", |
| 156 | defaultValue = "false", |
| 157 | category = "verbosity", |
| 158 | help = "Use external repositories for improved stability and speed when available." |
| 159 | ) |
Googler | 2f95be3 | 2015-03-20 17:26:11 +0000 | [diff] [blame] | 160 | public boolean externalRepositories; |
Michajlo Matijkiw | c0770d9 | 2015-07-31 17:58:35 +0000 | [diff] [blame] | 161 | |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 162 | @Option( |
| 163 | name = "force_experimental_external_repositories", |
| 164 | defaultValue = "false", |
| 165 | category = "verbosity", |
| 166 | help = "Forces --experimental_external_repositories." |
| 167 | ) |
Googler | 2f95be3 | 2015-03-20 17:26:11 +0000 | [diff] [blame] | 168 | public boolean forceExternalRepositories; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 169 | |
Ulf Adams | d6347a9 | 2016-02-24 13:23:54 +0000 | [diff] [blame] | 170 | @Option( |
| 171 | name = "experimental_ui", |
| 172 | defaultValue = "false", |
Ulf Adams | 9934f15 | 2016-06-22 12:04:40 +0000 | [diff] [blame] | 173 | category = "verbosity", |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 174 | help = |
| 175 | "Switches to an alternative progress bar that more explicitly shows progress, such " |
| 176 | + "as loaded packages and executed actions." |
| 177 | ) |
Ulf Adams | d6347a9 | 2016-02-24 13:23:54 +0000 | [diff] [blame] | 178 | public boolean experimentalUi; |
| 179 | |
Klaus Aehlig | 9d068c0 | 2016-02-24 16:09:07 +0000 | [diff] [blame] | 180 | @Option( |
| 181 | name = "experimental_ui_debug_all_events", |
| 182 | defaultValue = "false", |
ccalvarin | 2eaa02e | 2017-04-17 23:37:46 +0200 | [diff] [blame] | 183 | optionUsageRestrictions = OptionUsageRestrictions.HIDDEN, |
Klaus Aehlig | 9d068c0 | 2016-02-24 16:09:07 +0000 | [diff] [blame] | 184 | help = "Report all events known to the experimental new Bazel UI." |
| 185 | ) |
| 186 | public boolean experimentalUiDebugAllEvents; |
| 187 | |
Klaus Aehlig | cd211ee | 2016-06-21 08:44:15 +0000 | [diff] [blame] | 188 | @Option( |
| 189 | name = "experimental_ui_actions_shown", |
| 190 | defaultValue = "3", |
Klaus Aehlig | 6c07f62 | 2016-06-27 15:15:52 +0000 | [diff] [blame] | 191 | category = "verbosity", |
| 192 | help = |
| 193 | "Number of concurrent actions shown in the alternative progress bar; each " |
| 194 | + "action is shown on a separate line. The alternative progress bar always shows " |
| 195 | + "at least one one, all numbers less than 1 are mapped to 1. " |
| 196 | + "This option has no effect unless --experimental_ui is set." |
Klaus Aehlig | cd211ee | 2016-06-21 08:44:15 +0000 | [diff] [blame] | 197 | ) |
| 198 | public int experimentalUiActionsShown; |
Klaus Aehlig | 9d068c0 | 2016-02-24 16:09:07 +0000 | [diff] [blame] | 199 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 200 | public boolean useColor() { |
| 201 | return useColorEnum == UseColor.YES || (useColorEnum == UseColor.AUTO && isATty); |
| 202 | } |
| 203 | |
| 204 | public boolean useCursorControl() { |
| 205 | return useCursesEnum == UseCurses.YES || (useCursesEnum == UseCurses.AUTO && isATty); |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | private static final DateTimeFormatter TIMESTAMP_FORMAT = |
| 210 | DateTimeFormat.forPattern("(MM-dd HH:mm:ss.SSS) "); |
| 211 | |
| 212 | protected final OutErr outErr; |
| 213 | |
| 214 | private final PrintStream errPrintStream; |
| 215 | |
| 216 | protected final Set<EventKind> eventMask = |
| 217 | EnumSet.copyOf(EventKind.ERRORS_WARNINGS_AND_INFO_AND_OUTPUT); |
| 218 | |
| 219 | protected final boolean showTimestamp; |
| 220 | |
| 221 | public BlazeCommandEventHandler(OutErr outErr, Options eventOptions) { |
| 222 | this.outErr = outErr; |
| 223 | this.errPrintStream = new PrintStream(outErr.getErrorStream(), true); |
| 224 | if (eventOptions.showProgress) { |
| 225 | eventMask.add(EventKind.PROGRESS); |
| 226 | eventMask.add(EventKind.START); |
| 227 | } else { |
| 228 | // Skip PASS events if --noshow_progress is requested. |
| 229 | eventMask.remove(EventKind.PASS); |
| 230 | } |
| 231 | if (eventOptions.showTaskFinish) { |
| 232 | eventMask.add(EventKind.FINISH); |
| 233 | } |
| 234 | eventMask.add(EventKind.SUBCOMMAND); |
| 235 | this.showTimestamp = eventOptions.showTimestamp; |
| 236 | } |
| 237 | |
| 238 | /** See EventHandler.handle. */ |
| 239 | @Override |
| 240 | public void handle(Event event) { |
| 241 | if (!eventMask.contains(event.getKind())) { |
| 242 | return; |
| 243 | } |
| 244 | String prefix; |
| 245 | switch (event.getKind()) { |
| 246 | case STDOUT: |
| 247 | putOutput(outErr.getOutputStream(), event); |
| 248 | return; |
| 249 | case STDERR: |
| 250 | putOutput(outErr.getErrorStream(), event); |
| 251 | return; |
| 252 | case PASS: |
| 253 | case FAIL: |
| 254 | case TIMEOUT: |
| 255 | case ERROR: |
| 256 | case WARNING: |
| 257 | case DEPCHECKER: |
| 258 | prefix = event.getKind() + ": "; |
| 259 | break; |
| 260 | case SUBCOMMAND: |
| 261 | prefix = ">>>>>>>>> "; |
| 262 | break; |
| 263 | case INFO: |
| 264 | case PROGRESS: |
| 265 | case START: |
| 266 | case FINISH: |
| 267 | prefix = "____"; |
| 268 | break; |
| 269 | default: |
| 270 | throw new IllegalStateException("" + event.getKind()); |
| 271 | } |
| 272 | StringBuilder buf = new StringBuilder(); |
| 273 | buf.append(prefix); |
| 274 | |
| 275 | if (showTimestamp) { |
| 276 | buf.append(timestamp()); |
| 277 | } |
| 278 | |
| 279 | Location location = event.getLocation(); |
| 280 | if (location != null) { |
| 281 | buf.append(location.print()).append(": "); |
| 282 | } |
| 283 | |
| 284 | buf.append(event.getMessage()); |
| 285 | if (event.getKind() == EventKind.FINISH) { |
| 286 | buf.append(" DONE"); |
| 287 | } |
| 288 | |
| 289 | // Add a trailing period for ERROR and WARNING messages, which are |
| 290 | // typically English sentences composed from exception messages. |
| 291 | if (event.getKind() == EventKind.WARNING || |
| 292 | event.getKind() == EventKind.ERROR) { |
| 293 | buf.append('.'); |
| 294 | } |
| 295 | |
| 296 | // Event messages go to stderr; results (e.g. 'blaze query') go to stdout. |
| 297 | errPrintStream.println(buf); |
| 298 | } |
| 299 | |
| 300 | private void putOutput(OutputStream out, Event event) { |
| 301 | try { |
| 302 | out.write(event.getMessageBytes()); |
| 303 | out.flush(); |
| 304 | } catch (IOException e) { |
Michajlo Matijkiw | dda04e2 | 2015-08-12 18:16:58 +0000 | [diff] [blame] | 305 | // This can happen in server mode if the blaze client has exited, or if output is redirected |
| 306 | // to a file and the disk is full, etc. May be moot in the case of full disk, or useful in |
| 307 | // the case of real bug in our handling of streams. |
| 308 | LOG.log(Level.WARNING, "Failed to write event", e); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 309 | } |
| 310 | } |
| 311 | |
| 312 | /** |
| 313 | * @return a string representing the current time, eg "04-26 13:47:32.124". |
| 314 | */ |
| 315 | protected String timestamp() { |
| 316 | return TIMESTAMP_FORMAT.print(System.currentTimeMillis()); |
| 317 | } |
| 318 | } |