| // Copyright 2016 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.remote; | 
 |  | 
 | import build.bazel.remote.execution.v2.DigestFunction; | 
 | import build.bazel.remote.execution.v2.ServerCapabilities; | 
 | import com.google.common.base.Preconditions; | 
 | import com.google.common.base.Strings; | 
 | import com.google.common.collect.ImmutableList; | 
 | import com.google.common.collect.ImmutableSet; | 
 | import com.google.common.util.concurrent.ListeningScheduledExecutorService; | 
 | import com.google.common.util.concurrent.MoreExecutors; | 
 | import com.google.devtools.build.lib.actions.ActionInput; | 
 | import com.google.devtools.build.lib.actions.Artifact; | 
 | import com.google.devtools.build.lib.analysis.ConfiguredTarget; | 
 | import com.google.devtools.build.lib.analysis.FilesToRunProvider; | 
 | import com.google.devtools.build.lib.analysis.RunfilesSupport; | 
 | import com.google.devtools.build.lib.analysis.TopLevelArtifactContext; | 
 | import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper; | 
 | import com.google.devtools.build.lib.analysis.config.BuildOptions; | 
 | import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget; | 
 | import com.google.devtools.build.lib.analysis.test.TestProvider; | 
 | import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions; | 
 | import com.google.devtools.build.lib.authandtls.GoogleAuthUtils; | 
 | import com.google.devtools.build.lib.buildeventstream.BuildEventArtifactUploader; | 
 | import com.google.devtools.build.lib.buildeventstream.LocalFilesArtifactUploader; | 
 | import com.google.devtools.build.lib.buildtool.BuildRequest; | 
 | import com.google.devtools.build.lib.collect.nestedset.NestedSet; | 
 | import com.google.devtools.build.lib.events.Event; | 
 | import com.google.devtools.build.lib.events.Reporter; | 
 | import com.google.devtools.build.lib.exec.ExecutorBuilder; | 
 | import com.google.devtools.build.lib.packages.TargetUtils; | 
 | import com.google.devtools.build.lib.remote.common.RemoteCacheClient; | 
 | import com.google.devtools.build.lib.remote.logging.LoggingInterceptor; | 
 | import com.google.devtools.build.lib.remote.options.RemoteOptions; | 
 | import com.google.devtools.build.lib.remote.options.RemoteOutputsMode; | 
 | import com.google.devtools.build.lib.remote.util.DigestUtil; | 
 | import com.google.devtools.build.lib.remote.util.TracingMetadataUtils; | 
 | import com.google.devtools.build.lib.remote.util.Utils; | 
 | import com.google.devtools.build.lib.runtime.BlazeModule; | 
 | import com.google.devtools.build.lib.runtime.BuildEventArtifactUploaderFactory; | 
 | import com.google.devtools.build.lib.runtime.Command; | 
 | import com.google.devtools.build.lib.runtime.CommandEnvironment; | 
 | import com.google.devtools.build.lib.runtime.RepositoryRemoteExecutor; | 
 | import com.google.devtools.build.lib.runtime.RepositoryRemoteExecutorFactory; | 
 | import com.google.devtools.build.lib.runtime.ServerBuilder; | 
 | import com.google.devtools.build.lib.skyframe.AspectValue; | 
 | import com.google.devtools.build.lib.util.AbruptExitException; | 
 | import com.google.devtools.build.lib.util.ExitCode; | 
 | import com.google.devtools.build.lib.util.io.AsynchronousFileOutputStream; | 
 | import com.google.devtools.build.lib.vfs.DigestHashFunction; | 
 | import com.google.devtools.build.lib.vfs.OutputService; | 
 | import com.google.devtools.build.lib.vfs.Path; | 
 | import com.google.devtools.common.options.OptionsBase; | 
 | import com.google.devtools.common.options.OptionsParsingResult; | 
 | import io.grpc.CallCredentials; | 
 | import io.grpc.ClientInterceptor; | 
 | import io.grpc.Context; | 
 | import java.io.IOException; | 
 | import java.util.List; | 
 | import java.util.concurrent.Executors; | 
 | import java.util.logging.Level; | 
 | import java.util.logging.Logger; | 
 |  | 
 | /** RemoteModule provides distributed cache and remote execution for Bazel. */ | 
 | public final class RemoteModule extends BlazeModule { | 
 |  | 
 |   private static final Logger logger = Logger.getLogger(RemoteModule.class.getName()); | 
 |  | 
 |   private AsynchronousFileOutputStream rpcLogFile; | 
 |  | 
 |   private final ListeningScheduledExecutorService retryScheduler = | 
 |       MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(1)); | 
 |  | 
 |   private RemoteActionContextProvider actionContextProvider; | 
 |   private RemoteActionInputFetcher actionInputFetcher; | 
 |   private RemoteOutputsMode remoteOutputsMode; | 
 |   private RemoteOutputService remoteOutputService; | 
 |  | 
 |   private final BuildEventArtifactUploaderFactoryDelegate | 
 |       buildEventArtifactUploaderFactoryDelegate = new BuildEventArtifactUploaderFactoryDelegate(); | 
 |  | 
 |   private final RepositoryRemoteExecutorFactoryDelegate repositoryRemoteExecutorFactoryDelegate = | 
 |       new RepositoryRemoteExecutorFactoryDelegate(); | 
 |  | 
 |   @Override | 
 |   public void serverInit(OptionsParsingResult startupOptions, ServerBuilder builder) { | 
 |     builder.addBuildEventArtifactUploaderFactory( | 
 |         buildEventArtifactUploaderFactoryDelegate, "remote"); | 
 |     builder.setRepositoryRemoteExecutorFactory(repositoryRemoteExecutorFactoryDelegate); | 
 |   } | 
 |  | 
 |   /** Returns whether remote execution should be available. */ | 
 |   public static boolean shouldEnableRemoteExecution(RemoteOptions options) { | 
 |     return !Strings.isNullOrEmpty(options.remoteExecutor); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public void beforeCommand(CommandEnvironment env) throws AbruptExitException { | 
 |     Preconditions.checkState(actionContextProvider == null, "actionContextProvider must be null"); | 
 |     Preconditions.checkState(actionInputFetcher == null, "actionInputFetcher must be null"); | 
 |     Preconditions.checkState(remoteOutputsMode == null, "remoteOutputsMode must be null"); | 
 |  | 
 |     RemoteOptions remoteOptions = env.getOptions().getOptions(RemoteOptions.class); | 
 |     if (remoteOptions == null) { | 
 |       // Quit if no supported command is being used. See getCommandOptions for details. | 
 |       return; | 
 |     } | 
 |  | 
 |     remoteOutputsMode = remoteOptions.remoteOutputsMode; | 
 |  | 
 |     AuthAndTLSOptions authAndTlsOptions = env.getOptions().getOptions(AuthAndTLSOptions.class); | 
 |     DigestHashFunction hashFn = env.getRuntime().getFileSystem().getDigestFunction(); | 
 |     DigestUtil digestUtil = new DigestUtil(hashFn); | 
 |  | 
 |     boolean enableDiskCache = RemoteCacheClientFactory.isDiskCache(remoteOptions); | 
 |     boolean enableHttpCache = RemoteCacheClientFactory.isHttpCache(remoteOptions); | 
 |     boolean enableGrpcCache = GrpcCacheClient.isRemoteCacheOptions(remoteOptions); | 
 |     boolean enableRemoteExecution = shouldEnableRemoteExecution(remoteOptions); | 
 |  | 
 |     if (!enableDiskCache && !enableHttpCache && !enableGrpcCache && !enableRemoteExecution) { | 
 |       // Quit if no remote caching or execution was enabled. | 
 |       return; | 
 |     } | 
 |  | 
 |     if ((enableHttpCache || enableDiskCache) && enableRemoteExecution) { | 
 |       throw new AbruptExitException( | 
 |           "Cannot combine gRPC based remote execution with disk caching or" + " HTTP-based caching", | 
 |           ExitCode.COMMAND_LINE_ERROR); | 
 |     } | 
 |  | 
 |     env.getEventBus().register(this); | 
 |     String invocationId = env.getCommandId().toString(); | 
 |     String buildRequestId = env.getBuildRequestId(); | 
 |     env.getReporter().handle(Event.info(String.format("Invocation ID: %s", invocationId))); | 
 |  | 
 |     Path logDir = | 
 |         env.getOutputBase().getRelative(env.getRuntime().getProductName() + "-remote-logs"); | 
 |     cleanAndCreateRemoteLogsDir(logDir); | 
 |  | 
 |     try { | 
 |       if ((enableHttpCache || enableDiskCache) && !enableGrpcCache) { | 
 |         RemoteCacheClient cacheClient = | 
 |             RemoteCacheClientFactory.create( | 
 |                 remoteOptions, | 
 |                 GoogleAuthUtils.newCredentials(authAndTlsOptions), | 
 |                 Preconditions.checkNotNull(env.getWorkingDirectory(), "workingDirectory"), | 
 |                 digestUtil); | 
 |         RemoteCache remoteCache = new RemoteCache(cacheClient, remoteOptions, digestUtil); | 
 |         actionContextProvider = | 
 |             RemoteActionContextProvider.createForRemoteCaching( | 
 |                 env, remoteCache, /* retryScheduler= */ null, digestUtil); | 
 |         return; | 
 |       } | 
 |  | 
 |       Preconditions.checkState(enableGrpcCache || enableRemoteExecution); | 
 |  | 
 |       ClientInterceptor loggingInterceptor = null; | 
 |       if (remoteOptions.experimentalRemoteGrpcLog != null) { | 
 |         rpcLogFile = | 
 |             new AsynchronousFileOutputStream( | 
 |                 env.getWorkingDirectory().getRelative(remoteOptions.experimentalRemoteGrpcLog)); | 
 |         loggingInterceptor = new LoggingInterceptor(rpcLogFile, env.getRuntime().getClock()); | 
 |       } | 
 |  | 
 |       ReferenceCountedChannel execChannel = null; | 
 |       ReferenceCountedChannel cacheChannel = null; | 
 |       if (enableRemoteExecution) { | 
 |         execChannel = | 
 |             RemoteCacheClientFactory.createGrpcChannel( | 
 |                 remoteOptions.remoteExecutor, | 
 |                 remoteOptions.remoteProxy, | 
 |                 authAndTlsOptions, | 
 |                 loggingInterceptor); | 
 |         // Create a separate channel if --remote_executor and --remote_cache point to different | 
 |         // endpoints. | 
 |         if (Strings.isNullOrEmpty(remoteOptions.remoteCache) | 
 |             || remoteOptions.remoteCache.equals(remoteOptions.remoteExecutor)) { | 
 |           cacheChannel = execChannel.retain(); | 
 |         } | 
 |       } | 
 |  | 
 |       if (cacheChannel == null) { | 
 |         cacheChannel = | 
 |             RemoteCacheClientFactory.createGrpcChannel( | 
 |                 remoteOptions.remoteCache, | 
 |                 remoteOptions.remoteProxy, | 
 |                 authAndTlsOptions, | 
 |                 loggingInterceptor); | 
 |       } | 
 |  | 
 |       CallCredentials credentials = GoogleAuthUtils.newCallCredentials(authAndTlsOptions); | 
 |       RemoteRetrier retrier = | 
 |           new RemoteRetrier( | 
 |               remoteOptions, | 
 |               RemoteRetrier.RETRIABLE_GRPC_ERRORS, | 
 |               retryScheduler, | 
 |               Retrier.ALLOW_ALL_CALLS); | 
 |  | 
 |       // We always query the execution server for capabilities, if it is defined. A remote | 
 |       // execution/cache system should have all its servers to return the capabilities pertaining | 
 |       // to the system as a whole. | 
 |       RemoteServerCapabilities rsc = | 
 |           new RemoteServerCapabilities( | 
 |               remoteOptions.remoteInstanceName, | 
 |               (execChannel != null ? execChannel : cacheChannel), | 
 |               credentials, | 
 |               remoteOptions.remoteTimeout, | 
 |               retrier); | 
 |       ServerCapabilities capabilities = null; | 
 |       try { | 
 |         capabilities = rsc.get(buildRequestId, invocationId); | 
 |       } catch (IOException e) { | 
 |         throw new AbruptExitException( | 
 |             "Failed to query remote execution capabilities: " + Utils.grpcAwareErrorMessage(e), | 
 |             ExitCode.REMOTE_ERROR, | 
 |             e); | 
 |       } catch (InterruptedException e) { | 
 |         Thread.currentThread().interrupt(); | 
 |         return; | 
 |       } | 
 |       checkClientServerCompatibility( | 
 |           capabilities, remoteOptions, digestUtil.getDigestFunction(), env.getReporter()); | 
 |  | 
 |       ByteStreamUploader uploader = | 
 |           new ByteStreamUploader( | 
 |               remoteOptions.remoteInstanceName, | 
 |               cacheChannel.retain(), | 
 |               credentials, | 
 |               remoteOptions.remoteTimeout, | 
 |               retrier); | 
 |       cacheChannel.release(); | 
 |       RemoteCacheClient cacheClient = | 
 |           new GrpcCacheClient( | 
 |               cacheChannel.retain(), | 
 |               credentials, | 
 |               remoteOptions, | 
 |               retrier, | 
 |               digestUtil, | 
 |               uploader.retain()); | 
 |       uploader.release(); | 
 |       Context requestContext = | 
 |           TracingMetadataUtils.contextWithMetadata(buildRequestId, invocationId, "bes-upload"); | 
 |       buildEventArtifactUploaderFactoryDelegate.init( | 
 |           new ByteStreamBuildEventArtifactUploaderFactory( | 
 |               uploader, | 
 |               cacheClient, | 
 |               cacheChannel.authority(), | 
 |               requestContext, | 
 |               remoteOptions.remoteInstanceName)); | 
 |  | 
 |       if (enableRemoteExecution) { | 
 |         RemoteRetrier execRetrier = | 
 |             new RemoteRetrier( | 
 |                 remoteOptions, | 
 |                 RemoteRetrier.RETRIABLE_GRPC_EXEC_ERRORS, | 
 |                 retryScheduler, | 
 |                 Retrier.ALLOW_ALL_CALLS); | 
 |         GrpcRemoteExecutor remoteExecutor = | 
 |             new GrpcRemoteExecutor( | 
 |                 execChannel.retain(), | 
 |                 GoogleAuthUtils.newCallCredentials(authAndTlsOptions), | 
 |                 execRetrier, | 
 |                 remoteOptions); | 
 |         execChannel.release(); | 
 |         RemoteExecutionCache remoteCache = | 
 |             new RemoteExecutionCache(cacheClient, remoteOptions, digestUtil); | 
 |         actionContextProvider = | 
 |             RemoteActionContextProvider.createForRemoteExecution( | 
 |                 env, remoteCache, remoteExecutor, retryScheduler, digestUtil, logDir); | 
 |         Context repoContext = | 
 |             TracingMetadataUtils.contextWithMetadata( | 
 |                 buildRequestId, invocationId, "repository_rule"); | 
 |         repositoryRemoteExecutorFactoryDelegate.init( | 
 |             new RemoteRepositoryRemoteExecutorFactory( | 
 |                 remoteCache, | 
 |                 remoteExecutor, | 
 |                 digestUtil, | 
 |                 repoContext, | 
 |                 remoteOptions.remoteInstanceName, | 
 |                 remoteOptions.remoteAcceptCached)); | 
 |       } else { | 
 |         if (enableDiskCache) { | 
 |           cacheClient = | 
 |               RemoteCacheClientFactory.createDiskAndRemoteClient( | 
 |                   env.getWorkingDirectory(), | 
 |                   remoteOptions.diskCache, | 
 |                   remoteOptions.remoteVerifyDownloads, | 
 |                   digestUtil, | 
 |                   cacheClient, | 
 |                   remoteOptions); | 
 |         } | 
 |  | 
 |         RemoteCache remoteCache = new RemoteCache(cacheClient, remoteOptions, digestUtil); | 
 |         actionContextProvider = | 
 |             RemoteActionContextProvider.createForRemoteCaching( | 
 |                 env, remoteCache, retryScheduler, digestUtil); | 
 |       } | 
 |     } catch (IOException e) { | 
 |       env.getReporter().handle(Event.error(e.getMessage())); | 
 |       env.getBlazeModuleEnvironment() | 
 |           .exit( | 
 |               new AbruptExitException( | 
 |                   "Error initializing RemoteModule", ExitCode.COMMAND_LINE_ERROR)); | 
 |     } | 
 |   } | 
 |  | 
 |   private static ImmutableList<Artifact> getRunfiles(ConfiguredTarget buildTarget) { | 
 |     FilesToRunProvider runfilesProvider = buildTarget.getProvider(FilesToRunProvider.class); | 
 |     if (runfilesProvider == null) { | 
 |       return ImmutableList.of(); | 
 |     } | 
 |     RunfilesSupport runfilesSupport = runfilesProvider.getRunfilesSupport(); | 
 |     if (runfilesSupport == null) { | 
 |       return ImmutableList.of(); | 
 |     } | 
 |     boolean noPruningManifestsInBazel = | 
 |         runfilesSupport.getRunfiles().getPruningManifests().isEmpty(); | 
 |     Preconditions.checkState( | 
 |         noPruningManifestsInBazel, "Bazel should not have pruning manifests. This is a bug."); | 
 |     ImmutableList.Builder<Artifact> runfilesBuilder = ImmutableList.builder(); | 
 |     for (Artifact runfile : runfilesSupport.getRunfiles().getUnconditionalArtifacts().toList()) { | 
 |       if (runfile.isSourceArtifact()) { | 
 |         continue; | 
 |       } | 
 |       runfilesBuilder.add(runfile); | 
 |     } | 
 |     return runfilesBuilder.build(); | 
 |   } | 
 |  | 
 |   private static ImmutableList<ActionInput> getTestOutputs(ConfiguredTarget testTarget) { | 
 |     TestProvider testProvider = testTarget.getProvider(TestProvider.class); | 
 |     if (testProvider == null) { | 
 |       return ImmutableList.of(); | 
 |     } | 
 |     return testProvider.getTestParams().getOutputs(); | 
 |   } | 
 |  | 
 |   private static NestedSet<? extends ActionInput> getArtifactsToBuild( | 
 |       ConfiguredTarget buildTarget, TopLevelArtifactContext topLevelArtifactContext) { | 
 |     return TopLevelArtifactHelper.getAllArtifactsToBuild(buildTarget, topLevelArtifactContext) | 
 |         .getImportantArtifacts(); | 
 |   } | 
 |  | 
 |   private static boolean isTestRule(ConfiguredTarget configuredTarget) { | 
 |     if (configuredTarget instanceof RuleConfiguredTarget) { | 
 |       RuleConfiguredTarget ruleConfiguredTarget = (RuleConfiguredTarget) configuredTarget; | 
 |       return TargetUtils.isTestRuleName(ruleConfiguredTarget.getRuleClassString()); | 
 |     } | 
 |     return false; | 
 |   } | 
 |  | 
 |   @Override | 
 |   public void afterAnalysis( | 
 |       CommandEnvironment env, | 
 |       BuildRequest request, | 
 |       BuildOptions buildOptions, | 
 |       Iterable<ConfiguredTarget> configuredTargets, | 
 |       ImmutableSet<AspectValue> aspects) { | 
 |     if (remoteOutputsMode != null && remoteOutputsMode.downloadToplevelOutputsOnly()) { | 
 |       Preconditions.checkState(actionContextProvider != null, "actionContextProvider was null"); | 
 |       boolean isTestCommand = env.getCommandName().equals("test"); | 
 |       TopLevelArtifactContext artifactContext = request.getTopLevelArtifactContext(); | 
 |       ImmutableSet.Builder<ActionInput> filesToDownload = ImmutableSet.builder(); | 
 |       for (ConfiguredTarget configuredTarget : configuredTargets) { | 
 |         if (isTestCommand && isTestRule(configuredTarget)) { | 
 |           // When running a test download the test.log and test.xml. | 
 |           filesToDownload.addAll(getTestOutputs(configuredTarget)); | 
 |         } else { | 
 |           filesToDownload.addAll(getArtifactsToBuild(configuredTarget, artifactContext).toList()); | 
 |           filesToDownload.addAll(getRunfiles(configuredTarget)); | 
 |         } | 
 |       } | 
 |       actionContextProvider.setFilesToDownload(filesToDownload.build()); | 
 |     } | 
 |   } | 
 |  | 
 |   private static void cleanAndCreateRemoteLogsDir(Path logDir) throws AbruptExitException { | 
 |     try { | 
 |       // Clean out old logs files. | 
 |       if (logDir.exists()) { | 
 |         logDir.deleteTree(); | 
 |       } | 
 |       logDir.createDirectory(); | 
 |     } catch (IOException e) { | 
 |       String message = String.format("Could not create base directory for remote logs: %s", logDir); | 
 |       throw new AbruptExitException(message, ExitCode.LOCAL_ENVIRONMENTAL_ERROR, e); | 
 |     } | 
 |   } | 
 |  | 
 |   private void checkClientServerCompatibility( | 
 |       ServerCapabilities capabilities, | 
 |       RemoteOptions remoteOptions, | 
 |       DigestFunction.Value digestFunction, | 
 |       Reporter reporter) | 
 |       throws AbruptExitException { | 
 |     RemoteServerCapabilities.ClientServerCompatibilityStatus st = | 
 |         RemoteServerCapabilities.checkClientServerCompatibility( | 
 |             capabilities, remoteOptions, digestFunction); | 
 |     for (String warning : st.getWarnings()) { | 
 |       reporter.handle(Event.warn(warning)); | 
 |     } | 
 |     List<String> errors = st.getErrors(); | 
 |     for (int i = 0; i < errors.size() - 1; ++i) { | 
 |       reporter.handle(Event.error(errors.get(i))); | 
 |     } | 
 |     if (!errors.isEmpty()) { | 
 |       throw new AbruptExitException(errors.get(errors.size() - 1), ExitCode.REMOTE_ERROR); | 
 |     } | 
 |   } | 
 |  | 
 |   @Override | 
 |   public void afterCommand() throws AbruptExitException { | 
 |     IOException failure = null; | 
 |  | 
 |     try { | 
 |       closeRpcLogFile(); | 
 |     } catch (IOException e) { | 
 |       logger.log(Level.WARNING, "Partially wrote rpc log file", e); | 
 |       failure = e; | 
 |     } | 
 |  | 
 |     try { | 
 |       deleteDownloadedInputs(); | 
 |     } catch (IOException e) { | 
 |       failure = e; | 
 |     } | 
 |  | 
 |     buildEventArtifactUploaderFactoryDelegate.reset(); | 
 |     repositoryRemoteExecutorFactoryDelegate.reset(); | 
 |     actionContextProvider = null; | 
 |     actionInputFetcher = null; | 
 |     remoteOutputsMode = null; | 
 |     remoteOutputService = null; | 
 |  | 
 |     if (failure != null) { | 
 |       throw new AbruptExitException(ExitCode.LOCAL_ENVIRONMENTAL_ERROR, failure); | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * Delete any input files that have been fetched from the remote cache during the build. This is | 
 |    * so that Bazel's view of the output base is identical with the output base after a build i.e. | 
 |    * files that Bazel thinks exist only remotely actually do. | 
 |    */ | 
 |   private void deleteDownloadedInputs() throws IOException { | 
 |     if (actionInputFetcher == null) { | 
 |       return; | 
 |     } | 
 |     IOException deletionFailure = null; | 
 |     for (Path file : actionInputFetcher.downloadedFiles()) { | 
 |       try { | 
 |         file.delete(); | 
 |       } catch (IOException e) { | 
 |         logger.log( | 
 |             Level.SEVERE, | 
 |             String.format("Failed to delete remote output '%s' from the " + "output base.", file), | 
 |             e); | 
 |         deletionFailure = e; | 
 |       } | 
 |     } | 
 |     if (deletionFailure != null) { | 
 |       throw deletionFailure; | 
 |     } | 
 |   } | 
 |  | 
 |   private void closeRpcLogFile() throws IOException { | 
 |     if (rpcLogFile != null) { | 
 |       AsynchronousFileOutputStream oldLogFile = rpcLogFile; | 
 |       rpcLogFile = null; | 
 |       oldLogFile.close(); | 
 |     } | 
 |   } | 
 |  | 
 |   @Override | 
 |   public void executorInit(CommandEnvironment env, BuildRequest request, ExecutorBuilder builder) { | 
 |     Preconditions.checkState(actionInputFetcher == null, "actionInputFetcher must be null"); | 
 |     Preconditions.checkNotNull(remoteOutputsMode, "remoteOutputsMode must not be null"); | 
 |  | 
 |     if (actionContextProvider == null) { | 
 |       return; | 
 |     } | 
 |     actionContextProvider.registerActionContexts(builder); | 
 |     builder.addExecutorLifecycleListener(actionContextProvider); | 
 |     RemoteOptions remoteOptions = | 
 |         Preconditions.checkNotNull( | 
 |             env.getOptions().getOptions(RemoteOptions.class), "RemoteOptions"); | 
 |     RemoteOutputsMode remoteOutputsMode = remoteOptions.remoteOutputsMode; | 
 |     if (!remoteOutputsMode.downloadAllOutputs()) { | 
 |       Context ctx = | 
 |           TracingMetadataUtils.contextWithMetadata( | 
 |               env.getBuildRequestId(), env.getCommandId().toString(), "fetch-remote-inputs"); | 
 |       actionInputFetcher = | 
 |           new RemoteActionInputFetcher( | 
 |               actionContextProvider.getRemoteCache(), env.getExecRoot(), ctx); | 
 |       builder.setActionInputPrefetcher(actionInputFetcher); | 
 |       remoteOutputService.setActionInputFetcher(actionInputFetcher); | 
 |     } | 
 |  | 
 |     builder.setRemoteFallbackStrategy(remoteOptions.remoteLocalFallbackStrategy); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public OutputService getOutputService() { | 
 |     Preconditions.checkState(remoteOutputService == null, "remoteOutputService must be null"); | 
 |     if (remoteOutputsMode != null && !remoteOutputsMode.downloadAllOutputs()) { | 
 |       remoteOutputService = new RemoteOutputService(); | 
 |     } | 
 |     return remoteOutputService; | 
 |   } | 
 |  | 
 |   @Override | 
 |   public Iterable<Class<? extends OptionsBase>> getCommandOptions(Command command) { | 
 |     return ImmutableList.of("build", "test", "fetch").contains(command.name()) | 
 |         ? ImmutableList.of(RemoteOptions.class, AuthAndTLSOptions.class) | 
 |         : ImmutableList.of(); | 
 |   } | 
 |  | 
 |   private static class BuildEventArtifactUploaderFactoryDelegate | 
 |       implements BuildEventArtifactUploaderFactory { | 
 |  | 
 |     private volatile BuildEventArtifactUploaderFactory uploaderFactory; | 
 |  | 
 |     public void init(BuildEventArtifactUploaderFactory uploaderFactory) { | 
 |       Preconditions.checkState(this.uploaderFactory == null); | 
 |       this.uploaderFactory = uploaderFactory; | 
 |     } | 
 |  | 
 |     public void reset() { | 
 |       this.uploaderFactory = null; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public BuildEventArtifactUploader create(CommandEnvironment env) throws IOException { | 
 |       BuildEventArtifactUploaderFactory uploaderFactory0 = this.uploaderFactory; | 
 |       if (uploaderFactory0 == null) { | 
 |         return new LocalFilesArtifactUploader(); | 
 |       } | 
 |       return uploaderFactory0.create(env); | 
 |     } | 
 |   } | 
 |  | 
 |   private static class RepositoryRemoteExecutorFactoryDelegate | 
 |       implements RepositoryRemoteExecutorFactory { | 
 |  | 
 |     private volatile RepositoryRemoteExecutorFactory delegate; | 
 |  | 
 |     public void init(RepositoryRemoteExecutorFactory delegate) { | 
 |       Preconditions.checkState(this.delegate == null); | 
 |       this.delegate = delegate; | 
 |     } | 
 |  | 
 |     public void reset() { | 
 |       this.delegate = null; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public RepositoryRemoteExecutor create() { | 
 |       RepositoryRemoteExecutorFactory delegate = this.delegate; | 
 |       if (delegate == null) { | 
 |         return null; | 
 |       } | 
 |       return delegate.create(); | 
 |     } | 
 |   } | 
 | } |