blob: d6d0c485a68520bbfdbb865ee99a84c6821a5605 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2015 The Bazel Authors. All rights reserved.
Ulf Adams633f5392015-09-15 11:13:08 +00002//
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
15package com.google.devtools.build.lib.runtime;
16
Ulf Adams706b7f22015-10-20 09:06:45 +000017import static com.google.devtools.build.lib.profiler.AutoProfiler.profiled;
18
Ulf Adams8d2e60d2015-09-17 08:11:24 +000019import com.google.common.annotations.VisibleForTesting;
Ulf Adams50e7db62015-10-20 09:14:16 +000020import com.google.common.collect.ImmutableList;
Ulf Adamsca2d8d22015-09-16 13:00:45 +000021import com.google.common.collect.ImmutableSet;
Ulf Adams633f5392015-09-15 11:13:08 +000022import com.google.common.eventbus.EventBus;
Ulf Adamsd5500a22016-11-16 09:54:55 +000023import com.google.devtools.build.lib.actions.ActionInput;
Ulf Adams5b9009b2015-09-24 09:52:53 +000024import com.google.devtools.build.lib.actions.PackageRootResolver;
Ulf Adams80613022015-09-16 09:11:33 +000025import com.google.devtools.build.lib.actions.cache.ActionCache;
Ulf Adams633f5392015-09-15 11:13:08 +000026import com.google.devtools.build.lib.analysis.BlazeDirectories;
Ulf Adams80613022015-09-16 09:11:33 +000027import com.google.devtools.build.lib.analysis.BuildView;
Ulf Adams5b9009b2015-09-24 09:52:53 +000028import com.google.devtools.build.lib.analysis.SkyframePackageRootResolver;
Ulf Adams3815b4c2015-09-18 07:34:13 +000029import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
Ulf Adamsca2d8d22015-09-16 13:00:45 +000030import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
31import com.google.devtools.build.lib.analysis.config.BuildOptions;
32import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
Lukacs Berki6e91eb92015-09-21 09:12:37 +000033import com.google.devtools.build.lib.cmdline.Label;
Ulf Adams633f5392015-09-15 11:13:08 +000034import com.google.devtools.build.lib.events.Reporter;
Philipp Wollermann49c20aa2016-08-25 12:59:52 +000035import com.google.devtools.build.lib.exec.ActionInputPrefetcher;
Ulf Adams706b7f22015-10-20 09:06:45 +000036import com.google.devtools.build.lib.exec.OutputService;
Ulf Adamsca2d8d22015-09-16 13:00:45 +000037import com.google.devtools.build.lib.packages.NoSuchThingException;
38import com.google.devtools.build.lib.packages.Target;
Ulf Adams3815b4c2015-09-18 07:34:13 +000039import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
Ulf Adams80613022015-09-16 09:11:33 +000040import com.google.devtools.build.lib.pkgcache.PackageManager;
Ulf Adamsebf1b2e2015-09-29 11:06:53 +000041import com.google.devtools.build.lib.pkgcache.TargetPatternEvaluator;
Ulf Adams706b7f22015-10-20 09:06:45 +000042import com.google.devtools.build.lib.profiler.AutoProfiler;
43import com.google.devtools.build.lib.profiler.ProfilerTask;
Ulf Adams3d67e002016-03-29 16:23:01 +000044import com.google.devtools.build.lib.skyframe.SkyframeBuildView;
Ulf Adams80613022015-09-16 09:11:33 +000045import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
Ulf Adamsca2d8d22015-09-16 13:00:45 +000046import com.google.devtools.build.lib.util.AbruptExitException;
47import com.google.devtools.build.lib.util.ExitCode;
Mark Schaller6df81792015-12-10 18:47:47 +000048import com.google.devtools.build.lib.util.Preconditions;
Ulf Adamsc73051c62016-03-23 09:18:13 +000049import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
Ulf Adams706b7f22015-10-20 09:06:45 +000050import com.google.devtools.build.lib.vfs.FileSystemUtils;
Ulf Adams80613022015-09-16 09:11:33 +000051import com.google.devtools.build.lib.vfs.Path;
Ulf Adams08663e62016-02-12 09:59:22 +000052import com.google.devtools.build.lib.vfs.PathFragment;
Ulf Adams50e7db62015-10-20 09:14:16 +000053import com.google.devtools.common.options.OptionPriority;
Ulf Adamsde14ade2016-10-14 14:20:31 +000054import com.google.devtools.common.options.OptionsClassProvider;
Ulf Adamsebf1b2e2015-09-29 11:06:53 +000055import com.google.devtools.common.options.OptionsParser;
Ulf Adams50e7db62015-10-20 09:14:16 +000056import com.google.devtools.common.options.OptionsParsingException;
Ulf Adamsca2d8d22015-09-16 13:00:45 +000057import com.google.devtools.common.options.OptionsProvider;
Ulf Adams80613022015-09-16 09:11:33 +000058import java.io.IOException;
Ulf Adams8d2e60d2015-09-17 08:11:24 +000059import java.util.Collection;
60import java.util.Collections;
Ulf Adams8d2e60d2015-09-17 08:11:24 +000061import java.util.List;
Ulf Adams633f5392015-09-15 11:13:08 +000062import java.util.Map;
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +000063import java.util.Set;
Ulf Adams50e7db62015-10-20 09:14:16 +000064import java.util.TreeMap;
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +000065import java.util.TreeSet;
Ulf Adams80613022015-09-16 09:11:33 +000066import java.util.UUID;
Ulf Adams88f643c2015-09-17 10:55:52 +000067import java.util.concurrent.atomic.AtomicReference;
Michael Staib6e5e8fb2016-10-04 21:26:37 +000068import javax.annotation.Nullable;
Ulf Adams633f5392015-09-15 11:13:08 +000069
70/**
71 * Encapsulates the state needed for a single command. The environment is dropped when the current
72 * command is done and all corresponding objects are garbage collected.
73 */
74public final class CommandEnvironment {
75 private final BlazeRuntime runtime;
Ulf Adamsab43b972016-03-30 12:23:50 +000076 private final BlazeWorkspace workspace;
Ulf Adams94b72db2016-03-30 11:58:37 +000077 private final BlazeDirectories directories;
Ulf Adams3815b4c2015-09-18 07:34:13 +000078
Klaus Aehlig88a590a2016-08-12 08:56:30 +000079 private UUID commandId; // Unique identifier for the command being run
Ulf Adamsca2d8d22015-09-16 13:00:45 +000080 private final Reporter reporter;
Ulf Adams633f5392015-09-15 11:13:08 +000081 private final EventBus eventBus;
Ulf Adamsca2d8d22015-09-16 13:00:45 +000082 private final BlazeModule.ModuleEnvironment blazeModuleEnvironment;
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +000083 private final Map<String, String> clientEnv = new TreeMap<>();
84 private final Set<String> visibleClientEnv = new TreeSet<>();
Ulf Adamsc73051c62016-03-23 09:18:13 +000085 private final TimestampGranularityMonitor timestampGranularityMonitor;
Michael Staib6e5e8fb2016-10-04 21:26:37 +000086 private final Thread commandThread;
Ulf Adamsebf1b2e2015-09-29 11:06:53 +000087
Klaus Aehlig88a590a2016-08-12 08:56:30 +000088 private String[] crashData;
89
Ulf Adams08663e62016-02-12 09:59:22 +000090 private PathFragment relativeWorkingDirectory = PathFragment.EMPTY_FRAGMENT;
Ulf Adamsb5146102015-10-20 08:57:26 +000091 private long commandStartTime;
Ulf Adams706b7f22015-10-20 09:06:45 +000092 private OutputService outputService;
Ulf Adamsd5500a22016-11-16 09:54:55 +000093 private ActionInputPrefetcher actionInputPrefetcher;
Ulf Adamsc5855302015-10-20 08:46:38 +000094 private Path workingDirectory;
Ulf Adams47cb9162015-09-18 08:12:30 +000095
Ulf Adams3877ebd2016-11-15 10:22:59 +000096 private String commandName;
97 private OptionsProvider options;
Ulf Adamsa0e3af42016-10-31 16:52:48 +000098
Ulf Adams88f643c2015-09-17 10:55:52 +000099 private AtomicReference<AbruptExitException> pendingException = new AtomicReference<>();
Ulf Adamsca2d8d22015-09-16 13:00:45 +0000100
101 private class BlazeModuleEnvironment implements BlazeModule.ModuleEnvironment {
102 @Override
Ulf Adamsae90bc92015-09-18 09:18:58 +0000103 public Path getFileFromWorkspace(Label label)
Ulf Adamsca2d8d22015-09-16 13:00:45 +0000104 throws NoSuchThingException, InterruptedException, IOException {
105 Target target = getPackageManager().getTarget(reporter, label);
Ulf Adams706b7f22015-10-20 09:06:45 +0000106 return (outputService != null)
107 ? outputService.stageTool(target)
Ulf Adamsca2d8d22015-09-16 13:00:45 +0000108 : target.getPackage().getPackageDirectory().getRelative(target.getName());
109 }
110
111 @Override
112 public void exit(AbruptExitException exception) {
Michael Staib6e5e8fb2016-10-04 21:26:37 +0000113 Preconditions.checkNotNull(exception);
114 Preconditions.checkNotNull(exception.getExitCode());
115 if (pendingException.compareAndSet(null, exception)) {
116 // There was no exception, so we're the first one to ask for an exit. Interrupt the command.
117 commandThread.interrupt();
118 }
Ulf Adamsca2d8d22015-09-16 13:00:45 +0000119 }
120 }
121
Michael Staib6e5e8fb2016-10-04 21:26:37 +0000122 /**
123 * Creates a new command environment which can be used for executing commands for the given
124 * runtime in the given workspace, which will publish events on the given eventBus. The
125 * commandThread passed is interrupted when a module requests an early exit.
126 */
127 CommandEnvironment(
128 BlazeRuntime runtime, BlazeWorkspace workspace, EventBus eventBus, Thread commandThread) {
Ulf Adams633f5392015-09-15 11:13:08 +0000129 this.runtime = runtime;
Ulf Adams69cc0032016-03-30 13:52:25 +0000130 this.workspace = workspace;
Ulf Adamsab43b972016-03-30 12:23:50 +0000131 this.directories = workspace.getDirectories();
Klaus Aehlig88a590a2016-08-12 08:56:30 +0000132 this.commandId = null; // Will be set once we get the client environment
Ulf Adams39b18ca2015-11-02 08:45:26 +0000133 this.reporter = new Reporter();
Ulf Adams633f5392015-09-15 11:13:08 +0000134 this.eventBus = eventBus;
Michael Staib6e5e8fb2016-10-04 21:26:37 +0000135 this.commandThread = commandThread;
Ulf Adamsca2d8d22015-09-16 13:00:45 +0000136 this.blazeModuleEnvironment = new BlazeModuleEnvironment();
Ulf Adamsc73051c62016-03-23 09:18:13 +0000137 this.timestampGranularityMonitor = new TimestampGranularityMonitor(runtime.getClock());
138 // Record the command's starting time again, for use by
139 // TimestampGranularityMonitor.waitForTimestampGranularity().
140 // This should be done as close as possible to the start of
141 // the command's execution.
142 timestampGranularityMonitor.setCommandStartTime();
Ulf Adamsebf1b2e2015-09-29 11:06:53 +0000143
Ulf Adamsc5855302015-10-20 08:46:38 +0000144 // TODO(ulfjack): We don't call beforeCommand() in tests, but rely on workingDirectory being set
145 // in setupPackageCache(). This leads to NPE if we don't set it here.
Ulf Adams94b72db2016-03-30 11:58:37 +0000146 this.workingDirectory = directories.getWorkspace();
Ulf Adams69cc0032016-03-30 13:52:25 +0000147
148 workspace.getSkyframeExecutor().setEventBus(eventBus);
Ulf Adams633f5392015-09-15 11:13:08 +0000149 }
150
151 public BlazeRuntime getRuntime() {
152 return runtime;
153 }
154
Ulf Adamsab43b972016-03-30 12:23:50 +0000155 public BlazeWorkspace getBlazeWorkspace() {
156 return workspace;
157 }
158
Ulf Adams633f5392015-09-15 11:13:08 +0000159 public BlazeDirectories getDirectories() {
Ulf Adams94b72db2016-03-30 11:58:37 +0000160 return directories;
Ulf Adams633f5392015-09-15 11:13:08 +0000161 }
162
163 /**
164 * Returns the reporter for events.
165 */
166 public Reporter getReporter() {
Ulf Adamsca2d8d22015-09-16 13:00:45 +0000167 return reporter;
Ulf Adams633f5392015-09-15 11:13:08 +0000168 }
169
170 public EventBus getEventBus() {
171 return eventBus;
172 }
173
Ulf Adamsca2d8d22015-09-16 13:00:45 +0000174 public BlazeModule.ModuleEnvironment getBlazeModuleEnvironment() {
175 return blazeModuleEnvironment;
176 }
177
Ulf Adams8d2e60d2015-09-17 08:11:24 +0000178 /**
179 * Return an unmodifiable view of the blaze client's environment when it invoked the current
180 * command.
181 */
Ulf Adams633f5392015-09-15 11:13:08 +0000182 public Map<String, String> getClientEnv() {
Ulf Adams8d2e60d2015-09-17 08:11:24 +0000183 return Collections.unmodifiableMap(clientEnv);
184 }
185
Ulf Adams3877ebd2016-11-15 10:22:59 +0000186 public String getCommandName() {
187 return commandName;
188 }
189
190 public OptionsProvider getOptions() {
Ulf Adamsa0e3af42016-10-31 16:52:48 +0000191 return options;
192 }
193
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000194 /**
Klaus Aehligb8d09022016-09-20 13:39:08 +0000195 * Return an ordered version of the client environment restricted to those variables whitelisted
196 * by the command-line options to be inheritable by actions.
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000197 */
Klaus Aehligb8d09022016-09-20 13:39:08 +0000198 public Map<String, String> getWhitelistedClientEnv() {
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000199 Map<String, String> visibleEnv = new TreeMap<>();
200 for (String var : visibleClientEnv) {
201 String value = clientEnv.get(var);
202 if (value != null) {
203 visibleEnv.put(var, value);
204 }
205 }
206 return Collections.unmodifiableMap(visibleEnv);
207 }
208
Ulf Adams8d2e60d2015-09-17 08:11:24 +0000209 @VisibleForTesting
210 void updateClientEnv(List<Map.Entry<String, String>> clientEnvList, boolean ignoreClientEnv) {
211 Preconditions.checkState(clientEnv.isEmpty());
212
213 Collection<Map.Entry<String, String>> env =
214 ignoreClientEnv ? System.getenv().entrySet() : clientEnvList;
215 for (Map.Entry<String, String> entry : env) {
216 clientEnv.put(entry.getKey(), entry.getValue());
217 }
Klaus Aehlig88a590a2016-08-12 08:56:30 +0000218 // Try to set the clientId from the client environment.
219 if (commandId == null) {
220 String uuidString = clientEnv.get("BAZEL_INTERNAL_INVOCATION_ID");
221 if (uuidString != null) {
222 try {
223 commandId = UUID.fromString(uuidString);
224 } catch (IllegalArgumentException e) {
225 // String was malformed, so we will resort to generating a random UUID
226 }
227 }
228 }
229 if (commandId == null) {
230 // We have been provided with the client environment, but it didn't contain
231 // the invocation id; hence generate our own.
232 commandId = UUID.randomUUID();
233 }
234 setCommandIdInCrashData();
Ulf Adams633f5392015-09-15 11:13:08 +0000235 }
Ulf Adams80613022015-09-16 09:11:33 +0000236
Ulf Adamsc73051c62016-03-23 09:18:13 +0000237 public TimestampGranularityMonitor getTimestampGranularityMonitor() {
238 return timestampGranularityMonitor;
239 }
240
Ulf Adams80613022015-09-16 09:11:33 +0000241 public PackageManager getPackageManager() {
Ulf Adamsab43b972016-03-30 12:23:50 +0000242 return getSkyframeExecutor().getPackageManager();
Ulf Adams80613022015-09-16 09:11:33 +0000243 }
244
Ulf Adams08663e62016-02-12 09:59:22 +0000245 public PathFragment getRelativeWorkingDirectory() {
246 return relativeWorkingDirectory;
Ulf Adamsebf1b2e2015-09-29 11:06:53 +0000247 }
248
249 /**
Ulf Adams08663e62016-02-12 09:59:22 +0000250 * Creates and returns a new target pattern parser.
Ulf Adamsebf1b2e2015-09-29 11:06:53 +0000251 */
Ulf Adams08663e62016-02-12 09:59:22 +0000252 public TargetPatternEvaluator newTargetPatternEvaluator() {
253 TargetPatternEvaluator result = getPackageManager().newTargetPatternEvaluator();
254 result.updateOffset(relativeWorkingDirectory);
255 return result;
Ulf Adamsebf1b2e2015-09-29 11:06:53 +0000256 }
257
Ulf Adams5b9009b2015-09-24 09:52:53 +0000258 public PackageRootResolver getPackageRootResolver() {
259 return new SkyframePackageRootResolver(getSkyframeExecutor(), reporter);
260 }
261
Ulf Adams3815b4c2015-09-18 07:34:13 +0000262 /**
263 * Returns the UUID that Blaze uses to identify everything logged from the current build command.
264 * It's also used to invalidate Skyframe nodes that are specific to a certain invocation, such as
265 * the build info.
266 */
Ulf Adams80613022015-09-16 09:11:33 +0000267 public UUID getCommandId() {
Klaus Aehlig88a590a2016-08-12 08:56:30 +0000268 if (commandId == null) {
269 // The commandId should not be requested before the beforeCommand is executed, as the
270 // commandId might be set through the client environment. However, to simplify testing,
271 // we set the id value before we throw the exception.
272 commandId = UUID.randomUUID();
273 throw new IllegalArgumentException("Build Id requested before client environment provided");
274 }
Ulf Adams3815b4c2015-09-18 07:34:13 +0000275 return commandId;
Ulf Adams80613022015-09-16 09:11:33 +0000276 }
277
278 public SkyframeExecutor getSkyframeExecutor() {
Ulf Adamsab43b972016-03-30 12:23:50 +0000279 return workspace.getSkyframeExecutor();
Ulf Adams80613022015-09-16 09:11:33 +0000280 }
281
Ulf Adams3d67e002016-03-29 16:23:01 +0000282 public SkyframeBuildView getSkyframeBuildView() {
Ulf Adamsab43b972016-03-30 12:23:50 +0000283 return getSkyframeExecutor().getSkyframeBuildView();
Ulf Adams3d67e002016-03-29 16:23:01 +0000284 }
285
Ulf Adamsc5855302015-10-20 08:46:38 +0000286 /**
Ulf Adams94b72db2016-03-30 11:58:37 +0000287 * Returns the working directory of the server.
288 *
289 * <p>This is often the first entry on the {@code --package_path}, but not always.
290 * Callers should certainly not make this assumption. The Path returned may be null.
291 */
292 public Path getWorkspace() {
293 return getDirectories().getWorkspace();
294 }
295
296 public String getWorkspaceName() {
297 Path workspace = getDirectories().getWorkspace();
298 if (workspace == null) {
299 return "";
300 }
301 return workspace.getBaseName();
302 }
303
304 /**
305 * Returns if the client passed a valid workspace to be used for the build.
306 */
307 public boolean inWorkspace() {
308 return getDirectories().inWorkspace();
309 }
310
311 /**
312 * Returns the output base directory associated with this Blaze server
313 * process. This is the base directory for shared Blaze state as well as tool
314 * and strategy specific subdirectories.
315 */
316 public Path getOutputBase() {
317 return getDirectories().getOutputBase();
318 }
319
320 /**
Ulf Adams94b72db2016-03-30 11:58:37 +0000321 * Returns the execution root directory associated with this Blaze server
322 * process. This is where all input and output files visible to the actual
323 * build reside.
324 */
325 public Path getExecRoot() {
326 return getDirectories().getExecRoot();
327 }
328
329 /**
Ulf Adamsc5855302015-10-20 08:46:38 +0000330 * Returns the working directory of the {@code blaze} client process.
331 *
332 * <p>This may be equal to {@code BlazeRuntime#getWorkspace()}, or beneath it.
333 *
Ulf Adams94b72db2016-03-30 11:58:37 +0000334 * @see #getWorkspace()
Ulf Adamsc5855302015-10-20 08:46:38 +0000335 */
Ulf Adams80613022015-09-16 09:11:33 +0000336 public Path getWorkingDirectory() {
Ulf Adamsc5855302015-10-20 08:46:38 +0000337 return workingDirectory;
Ulf Adams80613022015-09-16 09:11:33 +0000338 }
339
Ulf Adams706b7f22015-10-20 09:06:45 +0000340 /**
341 * @return the OutputService in use, or null if none.
342 */
343 public OutputService getOutputService() {
344 return outputService;
345 }
346
Ulf Adamsd5500a22016-11-16 09:54:55 +0000347 public ActionInputPrefetcher getActionInputPrefetcher() {
348 return actionInputPrefetcher == null ? ActionInputPrefetcher.NONE : actionInputPrefetcher;
Philipp Wollermann49c20aa2016-08-25 12:59:52 +0000349 }
350
Ulf Adams80613022015-09-16 09:11:33 +0000351 public ActionCache getPersistentActionCache() throws IOException {
Ulf Adamsab43b972016-03-30 12:23:50 +0000352 return workspace.getPersistentActionCache(reporter);
353 }
354
355 /**
Janak Ramakrishnan5e946732016-11-17 17:01:46 +0000356 * An array of String values useful if Blaze crashes. For now, just returns the build id as soon
357 * as it is determined.
Ulf Adamsab43b972016-03-30 12:23:50 +0000358 */
Janak Ramakrishnan5e946732016-11-17 17:01:46 +0000359 String[] getCrashData() {
Klaus Aehlig88a590a2016-08-12 08:56:30 +0000360 if (crashData == null) {
361 String buildId;
362 if (commandId == null) {
363 buildId = " (build id not set yet)";
364 } else {
365 buildId = commandId + " (build id)";
366 }
Janak Ramakrishnan5e946732016-11-17 17:01:46 +0000367 crashData = new String[] {buildId};
Klaus Aehlig88a590a2016-08-12 08:56:30 +0000368 }
369 return crashData;
370 }
371
372 private void setCommandIdInCrashData() {
373 // Update the command id in the crash data, if it is already generated
374 if (crashData != null && crashData.length >= 2) {
375 crashData[1] = getCommandId() + " (build id)";
376 }
Ulf Adamsab43b972016-03-30 12:23:50 +0000377 }
378
Ulf Adamsca2d8d22015-09-16 13:00:45 +0000379 /**
380 * This method only exists for the benefit of InfoCommand, which needs to construct a {@link
381 * BuildConfigurationCollection} without running a full loading phase. Don't add any more clients;
382 * instead, we should change info so that it doesn't need the configuration.
383 */
384 public BuildConfigurationCollection getConfigurations(OptionsProvider optionsProvider)
385 throws InvalidConfigurationException, InterruptedException {
386 BuildOptions buildOptions = runtime.createBuildOptions(optionsProvider);
387 boolean keepGoing = optionsProvider.getOptions(BuildView.Options.class).keepGoing;
Ulf Adamse5aaacf2015-09-24 12:40:30 +0000388 return getSkyframeExecutor().createConfigurations(reporter, runtime.getConfigurationFactory(),
Ulf Adamsf069d45b2016-04-15 07:54:24 +0000389 buildOptions, ImmutableSet.<String>of(), keepGoing);
Ulf Adamsca2d8d22015-09-16 13:00:45 +0000390 }
391
392 /**
Michael Staib6e5e8fb2016-10-04 21:26:37 +0000393 * Prevents any further interruption of this command by modules, and returns the final exit code
394 * from modules, or null if no modules requested an abrupt exit.
395 *
396 * <p>Always returns the same value on subsequent calls.
397 */
398 @Nullable
399 private ExitCode finalizeExitCode() {
400 // Set the pending exception so that further calls to exit(AbruptExitException) don't lead to
401 // unwanted thread interrupts.
402 if (pendingException.compareAndSet(null, new AbruptExitException(null))) {
403 return null;
404 }
405 if (Thread.currentThread() == commandThread) {
406 // We may have interrupted the thread in the process, so clear the interrupted bit.
407 // Whether the command was interrupted or not, it's about to be over, so don't interrupt later
408 // things happening on this thread.
409 Thread.interrupted();
410 }
411 // Extract the exit code (it can be null if someone has already called finalizeExitCode()).
412 return getPendingExitCode();
413 }
414
415 /**
Ulf Adamsca2d8d22015-09-16 13:00:45 +0000416 * Hook method called by the BlazeCommandDispatcher right before the dispatch
417 * of each command ends (while its outcome can still be modified).
418 */
419 ExitCode precompleteCommand(ExitCode originalExit) {
420 eventBus.post(new CommandPrecompleteEvent(originalExit));
421 // If Blaze did not suffer an infrastructure failure, check for errors in modules.
422 ExitCode exitCode = originalExit;
Michael Staib6e5e8fb2016-10-04 21:26:37 +0000423 ExitCode newExitCode = finalizeExitCode();
424 if (!originalExit.isInfrastructureFailure() && newExitCode != null) {
425 exitCode = newExitCode;
Ulf Adamsca2d8d22015-09-16 13:00:45 +0000426 }
427 return exitCode;
428 }
429
430 /**
Michael Staib6e5e8fb2016-10-04 21:26:37 +0000431 * Returns the current exit code requested by modules, or null if no exit has been requested.
432 */
433 @Nullable
434 public ExitCode getPendingExitCode() {
435 AbruptExitException exception = getPendingException();
436 return exception == null ? null : exception.getExitCode();
437 }
438
439 /**
440 * Retrieves the exception currently queued by a Blaze module.
441 *
442 * <p>Prefer getPendingExitCode or throwPendingException where appropriate.
443 */
444 public AbruptExitException getPendingException() {
445 return pendingException.get();
446 }
447
448 /**
Ulf Adamsca2d8d22015-09-16 13:00:45 +0000449 * Throws the exception currently queued by a Blaze module.
450 *
451 * <p>This should be called as often as is practical so that errors are reported as soon as
452 * possible. Ideally, we'd not need this, but the event bus swallows exceptions so we raise
453 * the exception this way.
454 */
455 public void throwPendingException() throws AbruptExitException {
Michael Staib6e5e8fb2016-10-04 21:26:37 +0000456 AbruptExitException exception = getPendingException();
Ulf Adams88f643c2015-09-17 10:55:52 +0000457 if (exception != null) {
Michael Staib6e5e8fb2016-10-04 21:26:37 +0000458 if (Thread.currentThread() == commandThread) {
459 // Throwing this exception counts as the requested interruption. Clear the interrupted bit.
460 Thread.interrupted();
461 }
Ulf Adams88f643c2015-09-17 10:55:52 +0000462 throw exception;
Ulf Adamsca2d8d22015-09-16 13:00:45 +0000463 }
Ulf Adams80613022015-09-16 09:11:33 +0000464 }
Ulf Adams3815b4c2015-09-18 07:34:13 +0000465
466 /**
467 * Initializes the package cache using the given options, and syncs the package cache. Also
468 * injects a defaults package using the options for the {@link BuildConfiguration}.
469 *
470 * @see DefaultsPackage
471 */
Ulf Adamsde14ade2016-10-14 14:20:31 +0000472 public void setupPackageCache(OptionsClassProvider options,
Ulf Adams3815b4c2015-09-18 07:34:13 +0000473 String defaultsPackageContents) throws InterruptedException, AbruptExitException {
Ulf Adamsc5855302015-10-20 08:46:38 +0000474 SkyframeExecutor skyframeExecutor = getSkyframeExecutor();
475 if (!skyframeExecutor.hasIncrementalState()) {
476 skyframeExecutor.resetEvaluator();
477 }
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000478 skyframeExecutor.sync(
479 reporter,
Ulf Adamsde14ade2016-10-14 14:20:31 +0000480 options.getOptions(PackageCacheOptions.class),
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000481 getOutputBase(),
482 getWorkingDirectory(),
483 defaultsPackageContents,
484 getCommandId(),
485 // TODO(bazel-team): this optimization disallows rule-specified additional dependencies
486 // on the client environment!
Klaus Aehligb8d09022016-09-20 13:39:08 +0000487 getWhitelistedClientEnv(),
Ulf Adamsde14ade2016-10-14 14:20:31 +0000488 timestampGranularityMonitor,
489 options);
Ulf Adams3815b4c2015-09-18 07:34:13 +0000490 }
491
Ulf Adamsb5146102015-10-20 08:57:26 +0000492 public void recordLastExecutionTime() {
Ulf Adamsab43b972016-03-30 12:23:50 +0000493 workspace.recordLastExecutionTime(getCommandStartTime());
Ulf Adamsb5146102015-10-20 08:57:26 +0000494 }
495
496 public void recordCommandStartTime(long commandStartTime) {
497 this.commandStartTime = commandStartTime;
498 }
499
Ulf Adams3815b4c2015-09-18 07:34:13 +0000500 public long getCommandStartTime() {
Ulf Adamsb5146102015-10-20 08:57:26 +0000501 return commandStartTime;
Ulf Adams3815b4c2015-09-18 07:34:13 +0000502 }
Ulf Adams47cb9162015-09-18 08:12:30 +0000503
Ulf Adamsc5855302015-10-20 08:46:38 +0000504 void setWorkingDirectory(Path workingDirectory) {
505 this.workingDirectory = workingDirectory;
506 }
507
Ulf Adamsebf1b2e2015-09-29 11:06:53 +0000508 /**
509 * Hook method called by the BlazeCommandDispatcher prior to the dispatch of
510 * each command.
511 *
512 * @param options The CommonCommandOptions used by every command.
513 * @throws AbruptExitException if this command is unsuitable to be run as specified
514 */
515 void beforeCommand(Command command, OptionsParser optionsParser,
Lukacs Berki2896dc02016-07-07 07:55:04 +0000516 CommonCommandOptions options, long execStartTimeNanos, long waitTimeInMs)
Ulf Adamsebf1b2e2015-09-29 11:06:53 +0000517 throws AbruptExitException {
Ulf Adamsb5146102015-10-20 08:57:26 +0000518 commandStartTime -= options.startupTime;
Ulf Adamsde14ade2016-10-14 14:20:31 +0000519 if (runtime.getStartupOptionsProvider().getOptions(BlazeServerStartupOptions.class).watchFS) {
520 try {
521 // TODO(ulfjack): Get rid of the startup option and drop this code.
522 optionsParser.parse("--watchfs");
523 } catch (OptionsParsingException e) {
524 // This should never happen.
525 throw new IllegalStateException(e);
526 }
527 }
Ulf Adams3877ebd2016-11-15 10:22:59 +0000528 this.commandName = command.name();
Ulf Adamsa0e3af42016-10-31 16:52:48 +0000529 this.options = optionsParser;
Ulf Adamsb5146102015-10-20 08:57:26 +0000530
Ulf Adams706b7f22015-10-20 09:06:45 +0000531 eventBus.post(new GotOptionsEvent(runtime.getStartupOptionsProvider(), optionsParser));
532 throwPendingException();
533
534 outputService = null;
535 BlazeModule outputModule = null;
Philipp Wollermann49c20aa2016-08-25 12:59:52 +0000536 ImmutableList.Builder<ActionInputPrefetcher> prefetchersBuilder = ImmutableList.builder();
Ulf Adams706b7f22015-10-20 09:06:45 +0000537 for (BlazeModule module : runtime.getBlazeModules()) {
538 OutputService moduleService = module.getOutputService();
539 if (moduleService != null) {
540 if (outputService != null) {
541 throw new IllegalStateException(String.format(
542 "More than one module (%s and %s) returns an output service",
543 module.getClass(), outputModule.getClass()));
544 }
545 outputService = moduleService;
546 outputModule = module;
547 }
Philipp Wollermann49c20aa2016-08-25 12:59:52 +0000548
549 ActionInputPrefetcher actionInputPrefetcher = module.getPrefetcher();
550 if (actionInputPrefetcher != null) {
551 prefetchersBuilder.add(actionInputPrefetcher);
552 }
Ulf Adams706b7f22015-10-20 09:06:45 +0000553 }
Ulf Adamsd5500a22016-11-16 09:54:55 +0000554 final ImmutableList<ActionInputPrefetcher> actionInputPrefetchers = prefetchersBuilder.build();
555 actionInputPrefetcher =
556 new ActionInputPrefetcher() {
557 @Override
558 public void prefetchFile(ActionInput input) {
559 for (ActionInputPrefetcher prefetcher : actionInputPrefetchers) {
560 prefetcher.prefetchFile(input);
561 }
562 }
563 };
Ulf Adams706b7f22015-10-20 09:06:45 +0000564
Ulf Adams50e7db62015-10-20 09:14:16 +0000565 SkyframeExecutor skyframeExecutor = getSkyframeExecutor();
Eric Fellheimerf3b43af2015-11-13 19:51:20 +0000566 skyframeExecutor.setOutputService(outputService);
Ulf Adams706b7f22015-10-20 09:06:45 +0000567
Ulf Adams50e7db62015-10-20 09:14:16 +0000568 // Ensure that the working directory will be under the workspace directory.
Ulf Adams94b72db2016-03-30 11:58:37 +0000569 Path workspace = getWorkspace();
Ulf Adams50e7db62015-10-20 09:14:16 +0000570 Path workingDirectory;
Ulf Adams94b72db2016-03-30 11:58:37 +0000571 if (inWorkspace()) {
Ulf Adams50e7db62015-10-20 09:14:16 +0000572 workingDirectory = workspace.getRelative(options.clientCwd);
573 } else {
Ulf Adams94b72db2016-03-30 11:58:37 +0000574 workspace = FileSystemUtils.getWorkingDirectory(getDirectories().getFileSystem());
Ulf Adams50e7db62015-10-20 09:14:16 +0000575 workingDirectory = workspace;
576 }
Ulf Adams08663e62016-02-12 09:59:22 +0000577 this.relativeWorkingDirectory = workingDirectory.relativeTo(workspace);
Ulf Adams50e7db62015-10-20 09:14:16 +0000578 this.workingDirectory = workingDirectory;
579
580 updateClientEnv(options.clientEnv, options.ignoreClientEnv);
581
582 // Fail fast in the case where a Blaze command forgets to install the package path correctly.
583 skyframeExecutor.setActive(false);
584 // Let skyframe figure out if it needs to store graph edges for this build.
585 skyframeExecutor.decideKeepIncrementalState(
586 runtime.getStartupOptionsProvider().getOptions(BlazeServerStartupOptions.class).batch,
587 optionsParser.getOptions(BuildView.Options.class));
588
589 // Start the performance and memory profilers.
590 runtime.beforeCommand(this, options, execStartTimeNanos);
591
592 if (command.builds()) {
593 Map<String, String> testEnv = new TreeMap<>();
594 for (Map.Entry<String, String> entry :
595 optionsParser.getOptions(BuildConfiguration.Options.class).testEnvironment) {
596 testEnv.put(entry.getKey(), entry.getValue());
597 }
598
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000599 // Compute the set of environment variables that are whitelisted on the commandline
600 // for inheritence.
601 for (Map.Entry<String, String> entry :
602 optionsParser.getOptions(BuildConfiguration.Options.class).actionEnvironment) {
603 if (entry.getValue() == null) {
604 visibleClientEnv.add(entry.getKey());
605 } else {
606 visibleClientEnv.remove(entry.getKey());
607 }
608 }
609
Ulf Adams50e7db62015-10-20 09:14:16 +0000610 try {
611 for (Map.Entry<String, String> entry : testEnv.entrySet()) {
612 if (entry.getValue() == null) {
613 String clientValue = clientEnv.get(entry.getKey());
614 if (clientValue != null) {
615 optionsParser.parse(OptionPriority.SOFTWARE_REQUIREMENT,
616 "test environment variable from client environment",
617 ImmutableList.of(
618 "--test_env=" + entry.getKey() + "=" + clientEnv.get(entry.getKey())));
619 }
620 }
621 }
622 } catch (OptionsParsingException e) {
623 throw new IllegalStateException(e);
624 }
625 }
Ulf Adams50e7db62015-10-20 09:14:16 +0000626
Lukacs Berki2896dc02016-07-07 07:55:04 +0000627 eventBus.post(new CommandStartEvent(
Klaus Aehlig88a590a2016-08-12 08:56:30 +0000628 command.name(), getCommandId(), getClientEnv(), workingDirectory, getDirectories(),
Lukacs Berki2896dc02016-07-07 07:55:04 +0000629 waitTimeInMs + options.waitTime));
Ulf Adamsebf1b2e2015-09-29 11:06:53 +0000630 }
Ulf Adams706b7f22015-10-20 09:06:45 +0000631
Nathan Harmatadd615202016-04-29 22:17:00 +0000632 /** Returns the name of the file system we are writing output to. */
633 public String determineOutputFileSystem() {
634 // If we have a fancy OutputService, this may be different between consecutive Blaze commands
635 // and so we need to compute it freshly. Otherwise, we can used the immutable value that's
636 // precomputed by our BlazeWorkspace.
Ulf Adams706b7f22015-10-20 09:06:45 +0000637 if (getOutputService() != null) {
Nathan Harmatadd615202016-04-29 22:17:00 +0000638 try (AutoProfiler p = profiled("Finding output file system", ProfilerTask.INFO)) {
639 return getOutputService().getFilesSystemName();
640 }
Ulf Adams706b7f22015-10-20 09:06:45 +0000641 }
Nathan Harmatadd615202016-04-29 22:17:00 +0000642 return workspace.getOutputBaseFilesystemTypeName();
Ulf Adams706b7f22015-10-20 09:06:45 +0000643 }
Ulf Adams633f5392015-09-15 11:13:08 +0000644}