| // Copyright 2014 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.lib.runtime; | |
| import com.google.devtools.build.lib.events.Event; | |
| import com.google.devtools.build.lib.events.EventHandler; | |
| import com.google.devtools.build.lib.events.EventKind; | |
| import com.google.devtools.build.lib.events.Location; | |
| import com.google.devtools.build.lib.util.io.OutErr; | |
| import com.google.devtools.common.options.EnumConverter; | |
| import com.google.devtools.common.options.Option; | |
| import com.google.devtools.common.options.OptionsBase; | |
| import org.joda.time.format.DateTimeFormat; | |
| import org.joda.time.format.DateTimeFormatter; | |
| import java.io.IOException; | |
| import java.io.OutputStream; | |
| import java.io.PrintStream; | |
| import java.util.EnumSet; | |
| import java.util.Set; | |
| import java.util.logging.Level; | |
| import java.util.logging.Logger; | |
| /** | |
| * BlazeCommandEventHandler: an event handler established for the duration of a | |
| * single Blaze command. | |
| */ | |
| public class BlazeCommandEventHandler implements EventHandler { | |
| private static final Logger LOG = Logger.getLogger(BlazeCommandEventHandler.class.getName()); | |
| public enum UseColor { YES, NO, AUTO } | |
| public enum UseCurses { YES, NO, AUTO } | |
| public static class UseColorConverter extends EnumConverter<UseColor> { | |
| public UseColorConverter() { | |
| super(UseColor.class, "--color setting"); | |
| } | |
| } | |
| public static class UseCursesConverter extends EnumConverter<UseCurses> { | |
| public UseCursesConverter() { | |
| super(UseCurses.class, "--curses setting"); | |
| } | |
| } | |
| public static class Options extends OptionsBase { | |
| @Option(name = "show_progress", | |
| defaultValue = "true", | |
| category = "verbosity", | |
| help = "Display progress messages during a build.") | |
| public boolean showProgress; | |
| @Option(name = "show_task_finish", | |
| defaultValue = "false", | |
| category = "verbosity", | |
| help = "Display progress messages when tasks complete, not just when they start.") | |
| public boolean showTaskFinish; | |
| @Option(name = "show_progress_rate_limit", | |
| defaultValue = "0.03", // A nice middle ground; snappy but not too spammy in logs. | |
| category = "verbosity", | |
| help = "Minimum number of seconds between progress messages in the output.") | |
| public double showProgressRateLimit; | |
| @Option(name = "color", | |
| defaultValue = "auto", | |
| converter = UseColorConverter.class, | |
| category = "verbosity", | |
| help = "Use terminal controls to colorize output.") | |
| public UseColor useColorEnum; | |
| @Option(name = "curses", | |
| defaultValue = "auto", | |
| converter = UseCursesConverter.class, | |
| category = "verbosity", | |
| help = "Use terminal cursor controls to minimize scrolling output") | |
| public UseCurses useCursesEnum; | |
| @Option(name = "terminal_columns", | |
| defaultValue = "80", | |
| category = "hidden", | |
| help = "A system-generated parameter which specifies the terminal " | |
| + " width in columns.") | |
| public int terminalColumns; | |
| @Option(name = "isatty", | |
| defaultValue = "false", | |
| category = "hidden", | |
| help = "A system-generated parameter which is used to notify the " | |
| + "server whether this client is running in a terminal. " | |
| + "If this is set to false, then '--color=auto' will be treated as '--color=no'. " | |
| + "If this is set to true, then '--color=auto' will be treated as '--color=yes'.") | |
| public boolean isATty; | |
| // This lives here (as opposed to the more logical BuildRequest.Options) | |
| // because the client passes it to the server *always*. We don't want the | |
| // client to have to figure out when it should or shouldn't to send it. | |
| @Option(name = "emacs", | |
| defaultValue = "false", | |
| category = "undocumented", | |
| help = "A system-generated parameter which is true iff EMACS=t or INSIDE_EMACS is set " | |
| + "in the environment of the client. This option controls certain display " | |
| + "features.") | |
| public boolean runningInEmacs; | |
| @Option(name = "show_timestamps", | |
| defaultValue = "false", | |
| category = "verbosity", | |
| help = "Include timestamps in messages") | |
| public boolean showTimestamp; | |
| @Option(name = "progress_in_terminal_title", | |
| defaultValue = "false", | |
| category = "verbosity", | |
| help = "Show the command progress in the terminal title. " | |
| + "Useful to see what blaze is doing when having multiple terminal tabs.") | |
| public boolean progressInTermTitle; | |
| @Option(name = "experimental_external_repositories", | |
| defaultValue = "false", | |
| category = "verbosity", | |
| help = "Use external repositories for improved stability and speed when available.") | |
| public boolean externalRepositories; | |
| @Option(name = "force_experimental_external_repositories", | |
| defaultValue = "false", | |
| category = "verbosity", | |
| help = "Forces --experimental_external_repositories.") | |
| public boolean forceExternalRepositories; | |
| @Option( | |
| name = "experimental_ui", | |
| defaultValue = "false", | |
| category = "verbosity", | |
| help = "Switches to an alternative progress bar that more explicitly shows progress, such " | |
| + "as loaded packages and executed actions.") | |
| public boolean experimentalUi; | |
| @Option( | |
| name = "experimental_ui_debug_all_events", | |
| defaultValue = "false", | |
| category = "hidden", | |
| help = "Report all events known to the experimental new Bazel UI." | |
| ) | |
| public boolean experimentalUiDebugAllEvents; | |
| @Option( | |
| name = "experimental_ui_actions_shown", | |
| defaultValue = "3", | |
| category = "verbosity", | |
| help = | |
| "Number of concurrent actions shown in the alternative progress bar; each " | |
| + "action is shown on a separate line. The alternative progress bar always shows " | |
| + "at least one one, all numbers less than 1 are mapped to 1. " | |
| + "This option has no effect unless --experimental_ui is set." | |
| ) | |
| public int experimentalUiActionsShown; | |
| public boolean useColor() { | |
| return useColorEnum == UseColor.YES || (useColorEnum == UseColor.AUTO && isATty); | |
| } | |
| public boolean useCursorControl() { | |
| return useCursesEnum == UseCurses.YES || (useCursesEnum == UseCurses.AUTO && isATty); | |
| } | |
| } | |
| private static final DateTimeFormatter TIMESTAMP_FORMAT = | |
| DateTimeFormat.forPattern("(MM-dd HH:mm:ss.SSS) "); | |
| protected final OutErr outErr; | |
| private final PrintStream errPrintStream; | |
| protected final Set<EventKind> eventMask = | |
| EnumSet.copyOf(EventKind.ERRORS_WARNINGS_AND_INFO_AND_OUTPUT); | |
| protected final boolean showTimestamp; | |
| public BlazeCommandEventHandler(OutErr outErr, Options eventOptions) { | |
| this.outErr = outErr; | |
| this.errPrintStream = new PrintStream(outErr.getErrorStream(), true); | |
| if (eventOptions.showProgress) { | |
| eventMask.add(EventKind.PROGRESS); | |
| eventMask.add(EventKind.START); | |
| } else { | |
| // Skip PASS events if --noshow_progress is requested. | |
| eventMask.remove(EventKind.PASS); | |
| } | |
| if (eventOptions.showTaskFinish) { | |
| eventMask.add(EventKind.FINISH); | |
| } | |
| eventMask.add(EventKind.SUBCOMMAND); | |
| this.showTimestamp = eventOptions.showTimestamp; | |
| } | |
| /** See EventHandler.handle. */ | |
| @Override | |
| public void handle(Event event) { | |
| if (!eventMask.contains(event.getKind())) { | |
| return; | |
| } | |
| String prefix; | |
| switch (event.getKind()) { | |
| case STDOUT: | |
| putOutput(outErr.getOutputStream(), event); | |
| return; | |
| case STDERR: | |
| putOutput(outErr.getErrorStream(), event); | |
| return; | |
| case PASS: | |
| case FAIL: | |
| case TIMEOUT: | |
| case ERROR: | |
| case WARNING: | |
| case DEPCHECKER: | |
| prefix = event.getKind() + ": "; | |
| break; | |
| case SUBCOMMAND: | |
| prefix = ">>>>>>>>> "; | |
| break; | |
| case INFO: | |
| case PROGRESS: | |
| case START: | |
| case FINISH: | |
| prefix = "____"; | |
| break; | |
| default: | |
| throw new IllegalStateException("" + event.getKind()); | |
| } | |
| StringBuilder buf = new StringBuilder(); | |
| buf.append(prefix); | |
| if (showTimestamp) { | |
| buf.append(timestamp()); | |
| } | |
| Location location = event.getLocation(); | |
| if (location != null) { | |
| buf.append(location.print()).append(": "); | |
| } | |
| buf.append(event.getMessage()); | |
| if (event.getKind() == EventKind.FINISH) { | |
| buf.append(" DONE"); | |
| } | |
| // Add a trailing period for ERROR and WARNING messages, which are | |
| // typically English sentences composed from exception messages. | |
| if (event.getKind() == EventKind.WARNING || | |
| event.getKind() == EventKind.ERROR) { | |
| buf.append('.'); | |
| } | |
| // Event messages go to stderr; results (e.g. 'blaze query') go to stdout. | |
| errPrintStream.println(buf); | |
| } | |
| private void putOutput(OutputStream out, Event event) { | |
| try { | |
| out.write(event.getMessageBytes()); | |
| out.flush(); | |
| } catch (IOException e) { | |
| // This can happen in server mode if the blaze client has exited, or if output is redirected | |
| // to a file and the disk is full, etc. May be moot in the case of full disk, or useful in | |
| // the case of real bug in our handling of streams. | |
| LOG.log(Level.WARNING, "Failed to write event", e); | |
| } | |
| } | |
| /** | |
| * @return a string representing the current time, eg "04-26 13:47:32.124". | |
| */ | |
| protected String timestamp() { | |
| return TIMESTAMP_FORMAT.print(System.currentTimeMillis()); | |
| } | |
| } |