| // 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.events; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.collect.ImmutableList.toImmutableList; |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| import static java.util.Comparator.comparing; |
| |
| import com.google.common.collect.ImmutableClassToInstanceMap; |
| import com.google.errorprone.annotations.CheckReturnValue; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.stream.Stream; |
| import javax.annotation.Nullable; |
| import javax.annotation.concurrent.Immutable; |
| import net.starlark.java.eval.StarlarkThread; |
| import net.starlark.java.syntax.Location; |
| import net.starlark.java.syntax.SyntaxError; |
| |
| /** |
| * A situation encountered by the build system that's worth reporting. |
| * |
| * <p>An event specifies an {@link EventKind}, a message, and (optionally) additional properties. |
| */ |
| @Immutable |
| @CheckReturnValue |
| public final class Event implements Reportable { |
| |
| private final EventKind kind; |
| |
| /** |
| * This field has type {@link String} or {@link byte[]}. |
| * |
| * <p>If this field is a byte array then it contains the UTF-8-encoded bytes of a message. This |
| * optimization avoids converting back and forth between strings and bytes. |
| */ |
| private final Object message; |
| |
| /** |
| * This map's entries are ordered by {@link Class#getName}. |
| * |
| * <p>That is not a total ordering because of classloaders. The order of entries whose key names |
| * are equal is not deterministic. |
| */ |
| private final ImmutableClassToInstanceMap<Object> properties; |
| |
| private int hashCode; |
| |
| private Event(EventKind kind, Object message, ImmutableClassToInstanceMap<Object> properties) { |
| this.kind = checkNotNull(kind); |
| this.message = checkNotNull(message); |
| this.properties = checkNotNull(properties); |
| } |
| |
| @Override |
| public void reportTo(ExtendedEventHandler handler) { |
| handler.handle(this); |
| } |
| |
| @Override |
| public boolean storeForReplay() { |
| return kind != EventKind.PROGRESS |
| && kind != EventKind.WARNING |
| && kind != EventKind.INFO |
| && kind != EventKind.DEBUG; |
| } |
| |
| public EventKind getKind() { |
| return kind; |
| } |
| |
| public String getMessage() { |
| return message instanceof String ? (String) message : new String((byte[]) message, UTF_8); |
| } |
| |
| /** |
| * Returns this event's message as a {@link byte[]}. If this event was instantiated using a {@link |
| * String}, the returned byte array is encoded using {@link |
| * java.nio.charset.StandardCharsets#UTF_8}. |
| */ |
| public byte[] getMessageBytes() { |
| return message instanceof byte[] ? (byte[]) message : ((String) message).getBytes(UTF_8); |
| } |
| |
| /** Returns the property value associated with {@code type} if any, and {@code null} otherwise. */ |
| @Nullable |
| public <T> T getProperty(Class<T> type) { |
| return properties.getInstance(type); |
| } |
| |
| /** |
| * Returns an {@link Event} instance that has the same type, message, and properties as the event |
| * this is called on, and additionally associates {@code propertyValue} (if non-{@code null}) with |
| * {@code type}. |
| * |
| * <p>If the event this is called on already has a property associated with {@code type} and |
| * {@code propertyValue} is non-{@code null}, the returned event will have {@code propertyValue} |
| * associated with it instead. If {@code propertyValue} is non-{@code null}, the returned event |
| * will have no property associated with {@code type}. |
| * |
| * <p>If the event this is called on has no property associated with {@code type}, and {@code |
| * propertyValue} is {@code null}, then this returns that event (it does not create a new {@link |
| * Event} instance). |
| * |
| * <p>In any case, the event this is called on does not change. |
| */ |
| // This implementation would be inefficient if #withProperty is called repeatedly because it may |
| // copy and sort the key collection. In practice we expect it to be called a small number of times |
| // per event (e.g. fewer than 5; usually 0). |
| // |
| // If that changes then consider an Event.Builder strategy instead. |
| public <T> Event withProperty(Class<T> type, @Nullable T propertyValue) { |
| Iterable<Class<?>> orderedKeys; |
| boolean containsKey = properties.containsKey(type); |
| if (!containsKey && propertyValue != null) { |
| orderedKeys = |
| Stream.concat(properties.keySet().stream(), Stream.of(type)) |
| .sorted(comparing(Class::getName)) |
| .collect(toImmutableList()); |
| } else if (containsKey) { |
| orderedKeys = properties.keySet(); |
| } else { |
| // !containsKey and propertyValue is null, so there's nothing to change. |
| return this; |
| } |
| |
| ImmutableClassToInstanceMap.Builder<Object> newProperties = |
| new ImmutableClassToInstanceMap.Builder<>(); |
| for (Class<?> key : orderedKeys) { |
| if (key.equals(type)) { |
| if (propertyValue != null) { |
| newProperties.put(type, propertyValue); |
| } |
| } else { |
| addToBuilder(newProperties, key); |
| } |
| } |
| |
| return new Event(kind, message, newProperties.build()); |
| } |
| |
| /** |
| * This type-parameterized method solves a problem where a {@code properties.getInstance(key)} |
| * expression would have type {@link Object} when {@code key} is a wildcard-parameterized {@link |
| * Class}. That {@link Object}-typed expression would then fail to type check in a {@code |
| * builder.put(key, properties.getInstance(key))} statement. |
| */ |
| private <T> void addToBuilder(ImmutableClassToInstanceMap.Builder<Object> builder, Class<T> key) { |
| builder.put(key, checkNotNull(properties.getInstance(key))); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * <p>Behaves like {@link #withProperty(Class, Object)}, with {@code type.equals(String.class)}. |
| * |
| * <p>Additionally, if the event this is called on already has a {@link String} property with |
| * value {@code tag}, or if {@code tag} is {@code null} and the event has no {@link String} |
| * property, then this returns that event (it does not create a new {@link Event} instance). |
| */ |
| @Override |
| public Event withTag(@Nullable String tag) { |
| if (Objects.equals(tag, getProperty(String.class))) { |
| return this; |
| } |
| return withProperty(String.class, tag); |
| } |
| |
| /** |
| * Returns a new event with the provided {@link ProcessOutput} property. See {@link #withProperty} |
| * for more specifics. |
| */ |
| public Event withProcessOutput(ProcessOutput processOutput) { |
| return withProperty(ProcessOutput.class, processOutput); |
| } |
| |
| /** |
| * Returns the {@link String} property, if any, asssociated with the event. When non-null, this |
| * value typically describes some property of the action that generated the event. |
| */ |
| // TODO(mschaller): change code which relies on this to rely on a more structured value, using |
| // types less prone to interference. |
| @Nullable |
| public String getTag() { |
| return getProperty(String.class); |
| } |
| |
| @Nullable |
| public ProcessOutput getProcessOutput() { |
| return getProperty(ProcessOutput.class); |
| } |
| |
| /** Returns the stdout bytes associated with this event if any, and {@code null} otherwise. */ |
| @Nullable |
| public byte[] getStdOut() { |
| ProcessOutput processOutput = getProperty(ProcessOutput.class); |
| if (processOutput == null) { |
| return null; |
| } |
| return processOutput.getStdOut(); |
| } |
| |
| /** Returns the stderr bytes associated with this event if any, and {@code null} otherwise. */ |
| @Nullable |
| public byte[] getStdErr() { |
| ProcessOutput processOutput = getProperty(ProcessOutput.class); |
| if (processOutput == null) { |
| return null; |
| } |
| return processOutput.getStdErr(); |
| } |
| |
| /** |
| * Returns the location of this event, if any. Returns null iff the event wasn't associated with |
| * any particular location, for example, a progress message. |
| */ |
| @Nullable |
| public Location getLocation() { |
| return getProperty(Location.class); |
| } |
| |
| /** Returns the event formatted as {@code "ERROR foo.bzl:1:2: oops"}. */ |
| @Override |
| public String toString() { |
| Location location = getLocation(); |
| // TODO(adonovan): <no location> is just noise. |
| return kind |
| + " " |
| + (location != null ? location.toString() : "<no location>") |
| + ": " |
| + getMessage(); |
| } |
| |
| @Override |
| public int hashCode() { |
| // We defer the computation of hashCode until it is needed to avoid the overhead of computing it |
| // and then never using it. In particular, we use Event for streaming stdout and stderr, which |
| // are both large and the hashCode is never used. |
| // |
| // This uses the same construction as String.hashCode. We don't lock, so reads and writes to the |
| // field can race. However, integer reads and writes are atomic, and this code guarantees that |
| // all writes have the same value, so the memory location can only be either 0 or the final |
| // value. Note that a reader could see the final value on the first read and 0 on the second |
| // read, so we must take care to only read the field once. |
| int h = hashCode; |
| if (h == 0) { |
| h = |
| Objects.hash( |
| kind, |
| message instanceof String ? message : Arrays.hashCode((byte[]) message), |
| properties); |
| hashCode = h; |
| } |
| return h; |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (other == this) { |
| return true; |
| } |
| if (other == null || !other.getClass().equals(getClass())) { |
| return false; |
| } |
| Event that = (Event) other; |
| return Objects.equals(this.kind, that.kind) |
| && this.message.getClass().equals(that.message.getClass()) |
| && (this.message instanceof String |
| ? Objects.equals(this.message, that.message) |
| : Arrays.equals((byte[]) this.message, (byte[]) that.message)) |
| && Objects.equals(this.properties, that.properties); |
| } |
| |
| /** Constructs an event with the provided {@link EventKind} and {@link String} message. */ |
| public static Event of(EventKind kind, String message) { |
| return new Event(kind, message, ImmutableClassToInstanceMap.of()); |
| } |
| |
| /** |
| * Constructs an event with the provided {@link EventKind}, {@link String} message, and single |
| * property value. |
| * |
| * <p>See {@link #withProperty(Class, Object)} if more than one property value is desired. |
| */ |
| public static <T> Event of( |
| EventKind kind, String message, Class<T> propertyType, T propertyValue) { |
| return new Event(kind, message, ImmutableClassToInstanceMap.of(propertyType, propertyValue)); |
| } |
| |
| /** Constructs an event with the provided {@link EventKind} and {@link byte[]} message. */ |
| public static Event of(EventKind kind, byte[] messageBytes) { |
| return new Event(kind, messageBytes, ImmutableClassToInstanceMap.of()); |
| } |
| |
| /** |
| * Constructs an event with the provided {@link EventKind}, {@link byte[]} message, and single |
| * property value. |
| * |
| * <p>See {@link #withProperty(Class, Object)} if more than one property value is desired. |
| */ |
| public static <T> Event of( |
| EventKind kind, byte[] messageBytes, Class<T> propertyType, T propertyValue) { |
| return new Event( |
| kind, messageBytes, ImmutableClassToInstanceMap.of(propertyType, propertyValue)); |
| } |
| |
| /** |
| * Constructs an event with the provided {@link EventKind} and {@link String} message, with an |
| * optional {@link Location}. |
| */ |
| public static Event of(EventKind kind, @Nullable Location location, String message) { |
| return location == null ? of(kind, message) : of(kind, message, Location.class, location); |
| } |
| |
| /** |
| * Constructs an event with a {@code byte[]} array instead of a {@link String} for its message. |
| * |
| * <p>The bytes must be decodable as UTF-8 text. |
| */ |
| public static Event of(EventKind kind, @Nullable Location location, byte[] messageBytes) { |
| return location == null |
| ? of(kind, messageBytes) |
| : of(kind, messageBytes, Location.class, location); |
| } |
| |
| /** Constructs an event with kind {@link EventKind#FATAL}. */ |
| public static Event fatal(String message) { |
| return of(EventKind.FATAL, message); |
| } |
| |
| /** Constructs an event with kind {@link EventKind#ERROR}, with an optional {@link Location}. */ |
| public static Event error(@Nullable Location location, String message) { |
| return location == null |
| ? of(EventKind.ERROR, message) |
| : of(EventKind.ERROR, message, Location.class, location); |
| } |
| |
| /** Constructs an event with kind {@link EventKind#ERROR}. */ |
| public static Event error(String message) { |
| return of(EventKind.ERROR, message); |
| } |
| |
| /** Constructs an event with kind {@link EventKind#WARNING}, with an optional {@link Location}. */ |
| public static Event warn(@Nullable Location location, String message) { |
| return location == null |
| ? of(EventKind.WARNING, message) |
| : of(EventKind.WARNING, message, Location.class, location); |
| } |
| |
| /** Constructs an event with kind {@link EventKind#WARNING}. */ |
| public static Event warn(String message) { |
| return of(EventKind.WARNING, message); |
| } |
| |
| /** Constructs an event with kind {@link EventKind#INFO}, with an optional {@link Location}. */ |
| public static Event info(@Nullable Location location, String message) { |
| return location == null |
| ? of(EventKind.INFO, message) |
| : of(EventKind.INFO, message, Location.class, location); |
| } |
| |
| /** Constructs an event with kind {@link EventKind#INFO}. */ |
| public static Event info(String message) { |
| return of(EventKind.INFO, message); |
| } |
| |
| /** |
| * Constructs an event with kind {@link EventKind#PROGRESS}, with an optional {@link Location}. |
| */ |
| public static Event progress(@Nullable Location location, String message) { |
| return location == null |
| ? of(EventKind.PROGRESS, message) |
| : of(EventKind.PROGRESS, message, Location.class, location); |
| } |
| |
| /** Constructs an event with kind {@link EventKind#PROGRESS}. */ |
| public static Event progress(String message) { |
| return of(EventKind.PROGRESS, message); |
| } |
| |
| /** Constructs an event with kind {@link EventKind#DEBUG}, with an optional {@link Location}. */ |
| public static Event debug(@Nullable Location location, String message) { |
| return location == null |
| ? of(EventKind.DEBUG, message) |
| : of(EventKind.DEBUG, message, Location.class, location); |
| } |
| |
| /** Constructs an event with kind {@link EventKind#DEBUG}. */ |
| public static Event debug(String message) { |
| return of(EventKind.DEBUG, message); |
| } |
| |
| /** Replays a sequence of events on {@code handler}. */ |
| public static void replayEventsOn(EventHandler handler, Iterable<Event> events) { |
| for (Event event : events) { |
| handler.handle(event); |
| } |
| } |
| |
| /** Converts a list of {@link SyntaxError}s to events and replays them on {@code handler}. */ |
| public static void replayEventsOn(EventHandler handler, List<SyntaxError> errors) { |
| for (SyntaxError error : errors) { |
| handler.handle(Event.error(error.location(), error.message())); |
| } |
| } |
| |
| /** |
| * Returns a {@link StarlarkThread.PrintHandler} that sends {@link EventKind#DEBUG} events to the |
| * provided {@link EventHandler}. |
| */ |
| public static StarlarkThread.PrintHandler makeDebugPrintHandler(EventHandler h) { |
| return (thread, msg) -> h.handle(Event.debug(thread.getCallerLocation(), msg)); |
| } |
| |
| /** |
| * Process output associated with an event. The contents is just-about-certainly on disk, so |
| * special care should be taken when accessing it. |
| * |
| * <p>Note that this indirection exists partially for documentation sake, but also to keep the |
| * event library lightweight and broadly usable by avoiding bringing in all of the dependencies |
| * that come with dealing with process output (specifically the filesystem library). |
| */ |
| public interface ProcessOutput { |
| /** |
| * Returns the string representation of the path containing the process's stdout for |
| * logging/debugging purposes. |
| */ |
| String getStdOutPath(); |
| |
| long getStdOutSize() throws IOException; |
| |
| byte[] getStdOut(); |
| |
| /** |
| * Returns the string representation of the path containing the process's stderr for |
| * logging/debugging purposes. |
| */ |
| String getStdErrPath(); |
| |
| long getStdErrSize() throws IOException; |
| |
| byte[] getStdErr(); |
| } |
| } |