// Copyright 2017 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.buildeventservice; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.devtools.build.lib.buildeventservice.BuildEventServiceTransport.UPLOAD_FAILED_MESSAGE; import static java.lang.String.format; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions; import com.google.devtools.build.lib.buildeventservice.client.BuildEventServiceClient; import com.google.devtools.build.lib.buildeventstream.BuildEventTransport; import com.google.devtools.build.lib.buildeventstream.PathConverter; import com.google.devtools.build.lib.buildeventstream.transports.BuildEventStreamOptions; import com.google.devtools.build.lib.buildeventstream.transports.BuildEventTransportFactory; import com.google.devtools.build.lib.clock.Clock; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.events.Reporter; import com.google.devtools.build.lib.runtime.BlazeModule; import com.google.devtools.build.lib.runtime.BuildEventStreamer; import com.google.devtools.build.lib.runtime.Command; import com.google.devtools.build.lib.runtime.CommandEnvironment; import com.google.devtools.build.lib.runtime.SynchronizedOutputStream; import com.google.devtools.build.lib.util.AbruptExitException; import com.google.devtools.build.lib.util.ExitCode; import com.google.devtools.build.lib.util.io.OutErr; import com.google.devtools.common.options.OptionsBase; import com.google.devtools.common.options.OptionsProvider; import java.io.IOException; import java.util.Set; import java.util.logging.Logger; import javax.annotation.Nullable; /** * Module responsible for the Build Event Transport (BEP) and Build Event Service (BES) * functionality. * * Implementors of this class have to overwrite {@link #optionsClass()} and * {@link #createBesClient(T besOptions, AuthAndTLSOptions authAndTLSOptions)}. */ public abstract class BuildEventServiceModule extends BlazeModule { private static final Logger logger = Logger.getLogger(BuildEventServiceModule.class.getName()); private OutErr outErr; private Set transports = ImmutableSet.of(); @Override public Iterable> getCommandOptions(Command command) { return ImmutableList.of(optionsClass(), AuthAndTLSOptions.class, BuildEventStreamOptions.class); } @Override public void beforeCommand(CommandEnvironment commandEnvironment) throws AbruptExitException { // Reset to null in case afterCommand was not called. this.outErr = null; if (!whitelistedCommands().contains(commandEnvironment.getCommandName())) { return; } BuildEventStreamer streamer = tryCreateStreamer( commandEnvironment.getOptions(), commandEnvironment.getReporter(), commandEnvironment.getBlazeModuleEnvironment(), commandEnvironment.getRuntime().getClock(), commandEnvironment.getRuntime().getPathToUriConverter(), commandEnvironment.getReporter(), commandEnvironment.getBuildRequestId().toString(), commandEnvironment.getCommandId().toString(), commandEnvironment.getCommandName()); if (streamer != null) { commandEnvironment.getReporter().addHandler(streamer); commandEnvironment.getEventBus().register(streamer); long bufferSize = commandEnvironment.getOptions().getOptions(optionsClass()).besOuterrBufferSize; final SynchronizedOutputStream out = new SynchronizedOutputStream(bufferSize); final SynchronizedOutputStream err = new SynchronizedOutputStream(bufferSize); this.outErr = OutErr.create(out, err); streamer.registerOutErrProvider( new BuildEventStreamer.OutErrProvider() { @Override public String getOut() { return out.readAndReset(); } @Override public String getErr() { return err.readAndReset(); } }); err.registerStreamer(streamer); out.registerStreamer(streamer); logger.fine("BuildEventStreamer created and registered successfully."); } } @Override public OutErr getOutputListener() { return outErr; } @Override public void afterCommand() { this.outErr = null; } /** * Returns {@code null} if no stream could be created. */ @Nullable @VisibleForTesting BuildEventStreamer tryCreateStreamer( OptionsProvider optionsProvider, EventHandler commandLineReporter, ModuleEnvironment moduleEnvironment, Clock clock, PathConverter pathConverter, Reporter reporter, String buildRequestId, String invocationId, String commandName) { try { T besOptions = checkNotNull( optionsProvider.getOptions(optionsClass()), "Could not get BuildEventServiceOptions."); AuthAndTLSOptions authTlsOptions = checkNotNull(optionsProvider.getOptions(AuthAndTLSOptions.class), "Could not get AuthAndTLSOptions."); BuildEventStreamOptions bepOptions = checkNotNull(optionsProvider.getOptions(BuildEventStreamOptions.class), "Could not get BuildEventStreamOptions."); BuildEventTransport besTransport = null; try { besTransport = tryCreateBesTransport( besOptions, authTlsOptions, buildRequestId, invocationId, commandName, moduleEnvironment, clock, pathConverter, commandLineReporter); } catch (Exception e) { if (besOptions.besBestEffort) { commandLineReporter.handle(Event.warn(format(UPLOAD_FAILED_MESSAGE, e.getMessage()))); } else { commandLineReporter.handle(Event.error(format(UPLOAD_FAILED_MESSAGE, e.getMessage()))); moduleEnvironment.exit(new AbruptExitException(ExitCode.PUBLISH_ERROR)); return null; } } ImmutableSet bepTransports = BuildEventTransportFactory.createFromOptions(bepOptions, pathConverter); ImmutableSet.Builder transportsBuilder = ImmutableSet.builder().addAll(bepTransports); if (besTransport != null) { transportsBuilder.add(besTransport); } transports = transportsBuilder.build(); if (!transports.isEmpty()) { return new BuildEventStreamer(transports, reporter); } } catch (Exception e) { moduleEnvironment.exit(new AbruptExitException(ExitCode.LOCAL_ENVIRONMENTAL_ERROR, e)); } return null; } @Nullable private BuildEventTransport tryCreateBesTransport( T besOptions, AuthAndTLSOptions authTlsOptions, String buildRequestId, String invocationId, String commandName, ModuleEnvironment moduleEnvironment, Clock clock, PathConverter pathConverter, EventHandler commandLineReporter) throws IOException { if (isNullOrEmpty(besOptions.besBackend)) { logger.fine("BuildEventServiceTransport is disabled."); return null; } else { logger.fine(format("Will create BuildEventServiceTransport streaming to '%s'", besOptions.besBackend)); commandLineReporter.handle( Event.info( format( "Streaming Build Event Protocol to %s build_request_id: %s invocation_id: %s", besOptions.besBackend, buildRequestId, invocationId))); BuildEventTransport besTransport = new BuildEventServiceTransport( createBesClient(besOptions, authTlsOptions), besOptions.besTimeout, besOptions.besBestEffort, besOptions.besLifecycleEvents, buildRequestId, invocationId, commandName, moduleEnvironment, clock, pathConverter, commandLineReporter, besOptions.projectId, keywords(besOptions)); logger.fine("BuildEventServiceTransport was created successfully"); return besTransport; } } @Override public void blazeShutdown() { for (BuildEventTransport transport : transports) { transport.closeNow(); } } protected abstract Class optionsClass(); protected abstract BuildEventServiceClient createBesClient(T besOptions, AuthAndTLSOptions authAndTLSOptions) throws IOException; protected abstract Set whitelistedCommands(); protected Set keywords(T besOptions) { return besOptions .besKeywords .stream() .map(keyword -> "user_keyword=" + keyword) .collect(ImmutableSet.toImmutableSet()); } }