blob: 0251e837720532fbc4d0c1d29981a76f65510221 [file] [log] [blame]
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001// Copyright 2014 Google Inc. All rights reserved.
2//
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
17import static java.nio.charset.StandardCharsets.ISO_8859_1;
18
19import com.google.common.annotations.VisibleForTesting;
20import com.google.common.base.Function;
21import com.google.common.base.Preconditions;
22import com.google.common.base.Predicate;
23import com.google.common.base.Predicates;
24import com.google.common.collect.ImmutableList;
25import com.google.common.collect.ImmutableMap;
26import com.google.common.collect.ImmutableSet;
27import com.google.common.collect.ImmutableSortedSet;
28import com.google.common.collect.Lists;
29import com.google.common.collect.Sets;
30import com.google.common.eventbus.EventBus;
31import com.google.common.eventbus.SubscriberExceptionContext;
32import com.google.common.eventbus.SubscriberExceptionHandler;
33import com.google.common.util.concurrent.Uninterruptibles;
34import com.google.devtools.build.lib.Constants;
35import com.google.devtools.build.lib.actions.cache.ActionCache;
36import com.google.devtools.build.lib.actions.cache.CompactPersistentActionCache;
37import com.google.devtools.build.lib.actions.cache.NullActionCache;
38import com.google.devtools.build.lib.analysis.BlazeDirectories;
39import com.google.devtools.build.lib.analysis.BlazeVersionInfo;
40import com.google.devtools.build.lib.analysis.BuildView;
41import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
42import com.google.devtools.build.lib.analysis.WorkspaceStatusAction;
43import com.google.devtools.build.lib.analysis.config.BinTools;
44import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
45import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
46import com.google.devtools.build.lib.analysis.config.BuildConfigurationKey;
47import com.google.devtools.build.lib.analysis.config.BuildOptions;
48import com.google.devtools.build.lib.analysis.config.ConfigurationFactory;
49import com.google.devtools.build.lib.analysis.config.DefaultsPackage;
50import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
51import com.google.devtools.build.lib.buildtool.BuildTool;
52import com.google.devtools.build.lib.events.Event;
53import com.google.devtools.build.lib.events.OutputFilter;
54import com.google.devtools.build.lib.events.Reporter;
55import com.google.devtools.build.lib.exec.OutputService;
56import com.google.devtools.build.lib.packages.NoSuchThingException;
57import com.google.devtools.build.lib.packages.PackageFactory;
58import com.google.devtools.build.lib.packages.Preprocessor;
59import com.google.devtools.build.lib.packages.RuleClassProvider;
60import com.google.devtools.build.lib.packages.Target;
61import com.google.devtools.build.lib.pkgcache.LoadedPackageProvider;
62import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner;
63import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
64import com.google.devtools.build.lib.pkgcache.PackageManager;
65import com.google.devtools.build.lib.pkgcache.TargetPatternEvaluator;
66import com.google.devtools.build.lib.profiler.MemoryProfiler;
67import com.google.devtools.build.lib.profiler.ProfilePhase;
68import com.google.devtools.build.lib.profiler.Profiler;
69import com.google.devtools.build.lib.profiler.Profiler.ProfiledTaskKinds;
70import com.google.devtools.build.lib.profiler.ProfilerTask;
71import com.google.devtools.build.lib.query2.output.OutputFormatter;
72import com.google.devtools.build.lib.rules.test.CoverageReportActionFactory;
73import com.google.devtools.build.lib.runtime.commands.BuildCommand;
74import com.google.devtools.build.lib.runtime.commands.CanonicalizeCommand;
75import com.google.devtools.build.lib.runtime.commands.CleanCommand;
76import com.google.devtools.build.lib.runtime.commands.HelpCommand;
77import com.google.devtools.build.lib.runtime.commands.InfoCommand;
78import com.google.devtools.build.lib.runtime.commands.ProfileCommand;
79import com.google.devtools.build.lib.runtime.commands.QueryCommand;
80import com.google.devtools.build.lib.runtime.commands.RunCommand;
81import com.google.devtools.build.lib.runtime.commands.ShutdownCommand;
82import com.google.devtools.build.lib.runtime.commands.SkylarkCommand;
83import com.google.devtools.build.lib.runtime.commands.TestCommand;
84import com.google.devtools.build.lib.runtime.commands.VersionCommand;
85import com.google.devtools.build.lib.server.RPCServer;
86import com.google.devtools.build.lib.server.ServerCommand;
87import com.google.devtools.build.lib.server.signal.InterruptSignalHandler;
88import com.google.devtools.build.lib.skyframe.DiffAwareness;
89import com.google.devtools.build.lib.skyframe.PrecomputedValue;
90import com.google.devtools.build.lib.skyframe.SequencedSkyframeExecutorFactory;
91import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
92import com.google.devtools.build.lib.skyframe.SkyframeExecutorFactory;
93import com.google.devtools.build.lib.syntax.Label;
94import com.google.devtools.build.lib.util.AbruptExitException;
95import com.google.devtools.build.lib.util.BlazeClock;
96import com.google.devtools.build.lib.util.Clock;
97import com.google.devtools.build.lib.util.ExitCode;
98import com.google.devtools.build.lib.util.LoggingUtil;
99import com.google.devtools.build.lib.util.OS;
100import com.google.devtools.build.lib.util.OsUtils;
101import com.google.devtools.build.lib.util.ThreadUtils;
102import com.google.devtools.build.lib.util.io.OutErr;
103import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
104import com.google.devtools.build.lib.vfs.FileSystem;
105import com.google.devtools.build.lib.vfs.FileSystemUtils;
106import com.google.devtools.build.lib.vfs.JavaIoFileSystem;
107import com.google.devtools.build.lib.vfs.Path;
108import com.google.devtools.build.lib.vfs.PathFragment;
109import com.google.devtools.build.lib.vfs.UnixFileSystem;
110import com.google.devtools.build.skyframe.SkyFunction;
111import com.google.devtools.build.skyframe.SkyFunctionName;
112import com.google.devtools.common.options.Option;
113import com.google.devtools.common.options.OptionPriority;
114import com.google.devtools.common.options.OptionsBase;
115import com.google.devtools.common.options.OptionsClassProvider;
116import com.google.devtools.common.options.OptionsParser;
117import com.google.devtools.common.options.OptionsParsingException;
118import com.google.devtools.common.options.OptionsProvider;
119import com.google.devtools.common.options.TriState;
120
121import java.io.BufferedOutputStream;
122import java.io.IOException;
123import java.io.OutputStream;
124import java.io.PrintWriter;
125import java.io.StringWriter;
126import java.lang.management.ManagementFactory;
127import java.lang.management.MemoryMXBean;
128import java.lang.reflect.Field;
129import java.util.ArrayList;
130import java.util.Arrays;
131import java.util.Collection;
132import java.util.Collections;
133import java.util.Date;
134import java.util.HashMap;
135import java.util.Iterator;
136import java.util.List;
137import java.util.Map;
138import java.util.Set;
139import java.util.UUID;
140import java.util.concurrent.TimeUnit;
141import java.util.concurrent.atomic.AtomicInteger;
142import java.util.logging.Level;
143import java.util.logging.Logger;
144import java.util.regex.Pattern;
145
146import javax.annotation.Nullable;
147
148/**
149 * The BlazeRuntime class encapsulates the runtime settings and services that
150 * are available to most parts of any Blaze application for the duration of the
151 * batch run or server lifetime. A single instance of this runtime will exist
152 * and will be passed around as needed.
153 */
154public final class BlazeRuntime {
155 /**
156 * The threshold for memory reserved by a 32-bit JVM before trouble may be expected.
157 *
158 * <p>After the JVM starts, it reserves memory for heap (controlled by -Xmx) and non-heap
159 * (code, PermGen, etc.). Furthermore, as Blaze spawns threads, each thread reserves memory
160 * for the stack (controlled by -Xss). Thus even if Blaze starts fine, with high memory settings
161 * it will die from a stack allocation failure in the middle of a build. We prefer failing
162 * upfront by setting a safe threshold.
163 *
164 * <p>This does not apply to 64-bit VMs.
165 */
166 private static final long MAX_BLAZE32_RESERVED_MEMORY = 3400 * 1048576L;
167
168 // Less than this indicates tampering with -Xmx settings.
169 private static final long MIN_BLAZE32_HEAP_SIZE = 3000 * 1000000L;
170
171 public static final String DO_NOT_BUILD_FILE_NAME = "DO_NOT_BUILD_HERE";
172
173 private static final Pattern suppressFromLog = Pattern.compile(".*(auth|pass|cookie).*",
174 Pattern.CASE_INSENSITIVE);
175
176 private static final Logger LOG = Logger.getLogger(BlazeRuntime.class.getName());
177
178 private final BlazeDirectories directories;
179 private Path workingDirectory;
180 private long commandStartTime;
181
182 // Application-specified constants
183 private final PathFragment runfilesPrefix;
184
185 private final SkyframeExecutor skyframeExecutor;
186
187 private final Reporter reporter;
188 private EventBus eventBus;
189 private final LoadingPhaseRunner loadingPhaseRunner;
190 private final PackageFactory packageFactory;
191 private final ConfigurationFactory configurationFactory;
192 private final ConfiguredRuleClassProvider ruleClassProvider;
193 private final BuildView view;
194 private ActionCache actionCache;
195 private final TimestampGranularityMonitor timestampGranularityMonitor;
196 private final Clock clock;
197 private final BuildTool buildTool;
198
199 private OutputService outputService;
200
201 private final Iterable<BlazeModule> blazeModules;
202 private final BlazeModule.ModuleEnvironment blazeModuleEnvironment;
203
204 private UUID commandId; // Unique identifier for the command being run
205
206 private final AtomicInteger storedExitCode = new AtomicInteger();
207
208 private final Map<String, String> clientEnv;
209
210 // We pass this through here to make it available to the MasterLogWriter.
211 private final OptionsProvider startupOptionsProvider;
212
213 private String outputFileSystem;
214 private Map<String, BlazeCommand> commandMap;
215
216 private AbruptExitException pendingException;
217
218 private final SubscriberExceptionHandler eventBusExceptionHandler;
219
220 private final BinTools binTools;
221
222 private final WorkspaceStatusAction.Factory workspaceStatusActionFactory;
223
224 private final ProjectFile.Provider projectFileProvider;
225
226 private class BlazeModuleEnvironment implements BlazeModule.ModuleEnvironment {
227 @Override
228 public Path getFileFromDepot(Label label)
229 throws NoSuchThingException, InterruptedException, IOException {
230 Target target = getPackageManager().getTarget(reporter, label);
231 return (outputService != null)
232 ? outputService.stageTool(target)
233 : target.getPackage().getPackageDirectory().getRelative(target.getName());
234 }
235
236 @Override
237 public void exit(AbruptExitException exception) {
238 Preconditions.checkState(pendingException == null);
239 pendingException = exception;
240 }
241 }
242
243 private BlazeRuntime(BlazeDirectories directories, Reporter reporter,
244 WorkspaceStatusAction.Factory workspaceStatusActionFactory,
245 final SkyframeExecutor skyframeExecutor,
246 PackageFactory pkgFactory, ConfiguredRuleClassProvider ruleClassProvider,
247 ConfigurationFactory configurationFactory, PathFragment runfilesPrefix, Clock clock,
248 OptionsProvider startupOptionsProvider, Iterable<BlazeModule> blazeModules,
249 Map<String, String> clientEnv,
250 TimestampGranularityMonitor timestampGranularityMonitor,
251 SubscriberExceptionHandler eventBusExceptionHandler,
252 BinTools binTools, ProjectFile.Provider projectFileProvider) {
253 this.workspaceStatusActionFactory = workspaceStatusActionFactory;
254 this.directories = directories;
255 this.workingDirectory = directories.getWorkspace();
256 this.reporter = reporter;
257 this.runfilesPrefix = runfilesPrefix;
258 this.packageFactory = pkgFactory;
259 this.binTools = binTools;
260 this.projectFileProvider = projectFileProvider;
261
262 this.skyframeExecutor = skyframeExecutor;
263 this.loadingPhaseRunner = new LoadingPhaseRunner(
264 skyframeExecutor.getPackageManager(),
265 pkgFactory.getRuleClassNames());
266
267 this.clientEnv = clientEnv;
268
269 this.blazeModules = blazeModules;
270 this.ruleClassProvider = ruleClassProvider;
271 this.configurationFactory = configurationFactory;
272 this.view = new BuildView(directories, getPackageManager(), ruleClassProvider,
273 skyframeExecutor, binTools, getCoverageReportActionFactory(blazeModules));
274 this.clock = clock;
275 this.timestampGranularityMonitor = Preconditions.checkNotNull(timestampGranularityMonitor);
276 this.startupOptionsProvider = startupOptionsProvider;
277
278 this.eventBusExceptionHandler = eventBusExceptionHandler;
279 this.blazeModuleEnvironment = new BlazeModuleEnvironment();
280 this.buildTool = new BuildTool(this);
281 initEventBus();
282
283 if (inWorkspace()) {
284 writeOutputBaseReadmeFile();
285 writeOutputBaseDoNotBuildHereFile();
286 }
287 setupExecRoot();
288 }
289
290 @Nullable private CoverageReportActionFactory getCoverageReportActionFactory(
291 Iterable<BlazeModule> blazeModules) {
292 CoverageReportActionFactory firstFactory = null;
293 for (BlazeModule module : blazeModules) {
294 CoverageReportActionFactory factory = module.getCoverageReportFactory();
295 if (factory != null) {
296 Preconditions.checkState(firstFactory == null,
297 "only one Blaze Module can have a Coverage Report Factory");
298 firstFactory = factory;
299 }
300 }
301 return firstFactory;
302 }
303
304 /**
305 * Figures out what file system we are writing output to. Here we use
306 * outputBase instead of outputPath because we need a file system to create the latter.
307 */
308 private String determineOutputFileSystem() {
309 if (getOutputService() != null) {
310 return getOutputService().getFilesSystemName();
311 }
312 long startTime = Profiler.nanoTimeMaybe();
313 String fileSystem = FileSystemUtils.getFileSystem(getOutputBase());
314 Profiler.instance().logSimpleTask(startTime, ProfilerTask.INFO, "Finding output file system");
315 return fileSystem;
316 }
317
318 public String getOutputFileSystem() {
319 return outputFileSystem;
320 }
321
322 @VisibleForTesting
323 public void initEventBus() {
324 setEventBus(new EventBus(eventBusExceptionHandler));
325 }
326
327 private void clearEventBus() {
328 // EventBus does not have an unregister() method, so this is how we release memory associated
329 // with handlers.
330 setEventBus(null);
331 }
332
333 private void setEventBus(EventBus eventBus) {
334 this.eventBus = eventBus;
335 skyframeExecutor.setEventBus(eventBus);
336 }
337
338 /**
339 * Conditionally enable profiling.
340 */
341 private final boolean initProfiler(CommonCommandOptions options,
342 UUID buildID, long execStartTimeNanos) {
343 OutputStream out = null;
344 boolean recordFullProfilerData = false;
345 ProfiledTaskKinds profiledTasks = ProfiledTaskKinds.NONE;
346
347 try {
348 if (options.profilePath != null) {
349 Path profilePath = getWorkspace().getRelative(options.profilePath);
350
351 recordFullProfilerData = options.recordFullProfilerData;
352 out = new BufferedOutputStream(profilePath.getOutputStream(), 1024 * 1024);
353 getReporter().handle(Event.info("Writing profile data to '" + profilePath + "'"));
354 profiledTasks = ProfiledTaskKinds.ALL;
355 } else if (options.alwaysProfileSlowOperations) {
356 recordFullProfilerData = false;
357 out = null;
358 profiledTasks = ProfiledTaskKinds.SLOWEST;
359 }
360 if (profiledTasks != ProfiledTaskKinds.NONE) {
361 Profiler.instance().start(profiledTasks, out,
362 "Blaze profile for " + getOutputBase() + " at " + new Date()
363 + ", build ID: " + buildID,
364 recordFullProfilerData, clock, execStartTimeNanos);
365 return true;
366 }
367 } catch (IOException e) {
368 getReporter().handle(Event.error("Error while creating profile file: " + e.getMessage()));
369 }
370 return false;
371 }
372
373 /**
374 * Generates a README file in the output base directory. This README file
375 * contains the name of the workspace directory, so that users can figure out
376 * which output base directory corresponds to which workspace.
377 */
378 private void writeOutputBaseReadmeFile() {
379 Preconditions.checkNotNull(getWorkspace());
380 Path outputBaseReadmeFile = getOutputBase().getRelative("README");
381 try {
382 FileSystemUtils.writeIsoLatin1(outputBaseReadmeFile, "WORKSPACE: " + getWorkspace(), "",
383 "The first line of this file is intentionally easy to parse for various",
384 "interactive scripting and debugging purposes. But please DO NOT write programs",
385 "that exploit it, as they will be broken by design: it is not possible to",
386 "reverse engineer the set of source trees or the --package_path from the output",
387 "tree, and if you attempt it, you will fail, creating subtle and",
388 "hard-to-diagnose bugs, that will no doubt get blamed on changes made by the",
389 "Blaze team.", "", "This directory was generated by Blaze.",
390 "Do not attempt to modify or delete any files in this directory.",
391 "Among other issues, Blaze's file system caching assumes that",
392 "only Blaze will modify this directory and the files in it,",
393 "so if you change anything here you may mess up Blaze's cache.");
394 } catch (IOException e) {
395 LOG.warning("Couldn't write to '" + outputBaseReadmeFile + "': " + e.getMessage());
396 }
397 }
398
399 private void writeOutputBaseDoNotBuildHereFile() {
400 Preconditions.checkNotNull(getWorkspace());
401 Path filePath = getOutputBase().getRelative(DO_NOT_BUILD_FILE_NAME);
402 try {
403 FileSystemUtils.writeContent(filePath, ISO_8859_1, getWorkspace().toString());
404 } catch (IOException e) {
405 LOG.warning("Couldn't write to '" + filePath + "': " + e.getMessage());
406 }
407 }
408
409 /**
410 * Creates the execRoot dir under outputBase.
411 */
412 private void setupExecRoot() {
413 try {
414 FileSystemUtils.createDirectoryAndParents(directories.getExecRoot());
415 } catch (IOException e) {
416 LOG.warning("failed to create execution root '" + directories.getExecRoot() + "': "
417 + e.getMessage());
418 }
419 }
420
421 public void recordCommandStartTime(long commandStartTime) {
422 this.commandStartTime = commandStartTime;
423 }
424
425 public long getCommandStartTime() {
426 return commandStartTime;
427 }
428
429 public String getWorkspaceName() {
430 Path workspace = directories.getWorkspace();
431 if (workspace == null) {
432 return "";
433 }
434 return workspace.getBaseName();
435 }
436
437 /**
438 * Returns any prefix to be inserted between relative source paths and the runfiles directory.
439 */
440 public PathFragment getRunfilesPrefix() {
441 return runfilesPrefix;
442 }
443
444 /**
445 * Returns the Blaze directories object for this runtime.
446 */
447 public BlazeDirectories getDirectories() {
448 return directories;
449 }
450
451 /**
452 * Returns the working directory of the server.
453 *
454 * <p>This is often the first entry on the {@code --package_path}, but not always.
455 * Callers should certainly not make this assumption. The Path returned may be null.
456 *
457 * @see #getWorkingDirectory()
458 */
459 public Path getWorkspace() {
460 return directories.getWorkspace();
461 }
462
463 /**
464 * Returns the working directory of the {@code blaze} client process.
465 *
466 * <p>This may be equal to {@code getWorkspace()}, or beneath it.
467 *
468 * @see #getWorkspace()
469 */
470 public Path getWorkingDirectory() {
471 return workingDirectory;
472 }
473
474 /**
475 * Returns if the client passed a valid workspace to be used for the build.
476 */
477 public boolean inWorkspace() {
478 return directories.inWorkspace();
479 }
480
481 /**
482 * Returns the output base directory associated with this Blaze server
483 * process. This is the base directory for shared Blaze state as well as tool
484 * and strategy specific subdirectories.
485 */
486 public Path getOutputBase() {
487 return directories.getOutputBase();
488 }
489
490 /**
491 * Returns the output path associated with this Blaze server process..
492 */
493 public Path getOutputPath() {
494 return directories.getOutputPath();
495 }
496
497 /**
498 * The directory in which blaze stores the server state - that is, the socket
499 * file and a log.
500 */
501 public Path getServerDirectory() {
502 return getOutputBase().getChild("server");
503 }
504
505 /**
506 * Returns the execution root directory associated with this Blaze server
507 * process. This is where all input and output files visible to the actual
508 * build reside.
509 */
510 public Path getExecRoot() {
511 return directories.getExecRoot();
512 }
513
514 /**
515 * Returns the reporter for events.
516 */
517 public Reporter getReporter() {
518 return reporter;
519 }
520
521 /**
522 * Returns the current event bus. Only valid within the scope of a single Blaze command.
523 */
524 public EventBus getEventBus() {
525 return eventBus;
526 }
527
528 public BinTools getBinTools() {
529 return binTools;
530 }
531
532 /**
533 * Returns the skyframe executor.
534 */
535 public SkyframeExecutor getSkyframeExecutor() {
536 return skyframeExecutor;
537 }
538
539 /**
540 * Returns the package factory.
541 */
542 public PackageFactory getPackageFactory() {
543 return packageFactory;
544 }
545
546 /**
547 * Returns the build tool.
548 */
549 public BuildTool getBuildTool() {
550 return buildTool;
551 }
552
553 public ImmutableList<OutputFormatter> getQueryOutputFormatters() {
554 ImmutableList.Builder<OutputFormatter> result = ImmutableList.builder();
555 result.addAll(OutputFormatter.getDefaultFormatters());
556 for (BlazeModule module : blazeModules) {
557 result.addAll(module.getQueryOutputFormatters());
558 }
559
560 return result.build();
561 }
562
563 /**
564 * Returns the package manager.
565 */
566 public PackageManager getPackageManager() {
567 return skyframeExecutor.getPackageManager();
568 }
569
570 public WorkspaceStatusAction.Factory getworkspaceStatusActionFactory() {
571 return workspaceStatusActionFactory;
572 }
573
574 public BlazeModule.ModuleEnvironment getBlazeModuleEnvironment() {
575 return blazeModuleEnvironment;
576 }
577
578 /**
579 * Returns the rule class provider.
580 */
581 public ConfiguredRuleClassProvider getRuleClassProvider() {
582 return ruleClassProvider;
583 }
584
585 public LoadingPhaseRunner getLoadingPhaseRunner() {
586 return loadingPhaseRunner;
587 }
588
589 /**
590 * Returns the build view.
591 */
592 public BuildView getView() {
593 return view;
594 }
595
596 public Iterable<BlazeModule> getBlazeModules() {
597 return blazeModules;
598 }
599
600 @SuppressWarnings("unchecked")
601 public <T extends BlazeModule> T getBlazeModule(Class<T> moduleClass) {
602 for (BlazeModule module : blazeModules) {
603 if (module.getClass() == moduleClass) {
604 return (T) module;
605 }
606 }
607
608 return null;
609 }
610
611 public ConfigurationFactory getConfigurationFactory() {
612 return configurationFactory;
613 }
614
615 /**
616 * Returns the target pattern parser.
617 */
618 public TargetPatternEvaluator getTargetPatternEvaluator() {
619 return loadingPhaseRunner.getTargetPatternEvaluator();
620 }
621
622 /**
623 * Returns reference to the lazily instantiated persistent action cache
624 * instance. Note, that method may recreate instance between different build
625 * requests, so return value should not be cached.
626 */
627 public ActionCache getPersistentActionCache() throws IOException {
628 if (actionCache == null) {
629 if (OS.getCurrent() == OS.WINDOWS) {
630 // TODO(bazel-team): Add support for a persistent action cache on Windows.
631 actionCache = new NullActionCache();
632 return actionCache;
633 }
634 long startTime = Profiler.nanoTimeMaybe();
635 try {
636 actionCache = new CompactPersistentActionCache(getCacheDirectory(), clock);
637 } catch (IOException e) {
638 LOG.log(Level.WARNING, "Failed to load action cache: " + e.getMessage(), e);
639 LoggingUtil.logToRemote(Level.WARNING, "Failed to load action cache: "
640 + e.getMessage(), e);
641 getReporter().handle(
642 Event.error("Error during action cache initialization: " + e.getMessage()
643 + ". Corrupted files were renamed to '" + getCacheDirectory() + "/*.bad'. "
644 + "Blaze will now reset action cache data, causing a full rebuild"));
645 actionCache = new CompactPersistentActionCache(getCacheDirectory(), clock);
646 } finally {
647 Profiler.instance().logSimpleTask(startTime, ProfilerTask.INFO, "Loading action cache");
648 }
649 }
650 return actionCache;
651 }
652
653 /**
654 * Removes in-memory caches.
655 */
656 public void clearCaches() throws IOException {
657 clearSkyframeRelevantCaches();
658 actionCache = null;
659 FileSystemUtils.deleteTree(getCacheDirectory());
660 }
661
662 /** Removes skyframe cache and other caches that must be kept synchronized with skyframe. */
663 private void clearSkyframeRelevantCaches() {
664 skyframeExecutor.resetEvaluator();
665 view.clear();
666 }
667
668 /**
669 * Returns the TimestampGranularityMonitor. The same monitor object is used
670 * across multiple Blaze commands, but it doesn't hold any persistent state
671 * across different commands.
672 */
673 public TimestampGranularityMonitor getTimestampGranularityMonitor() {
674 return timestampGranularityMonitor;
675 }
676
677 /**
678 * Returns path to the cache directory. Path must be inside output base to
679 * ensure that users can run concurrent instances of blaze in different
680 * clients without attempting to concurrently write to the same action cache
681 * on disk, which might not be safe.
682 */
683 private Path getCacheDirectory() {
684 return getOutputBase().getChild("action_cache");
685 }
686
687 /**
688 * Returns a provider for project file objects. Can be null if no such provider was set by any of
689 * the modules.
690 */
691 @Nullable
692 public ProjectFile.Provider getProjectFileProvider() {
693 return projectFileProvider;
694 }
695
696 /**
697 * Hook method called by the BlazeCommandDispatcher prior to the dispatch of
698 * each command.
699 *
700 * @param options The CommonCommandOptions used by every command.
701 * @throws AbruptExitException if this command is unsuitable to be run as specified
702 */
703 void beforeCommand(String commandName, OptionsParser optionsParser,
704 CommonCommandOptions options, long execStartTimeNanos)
705 throws AbruptExitException {
706 commandStartTime -= options.startupTime;
707
708 eventBus.post(new GotOptionsEvent(startupOptionsProvider,
709 optionsParser));
710 throwPendingException();
711
712 outputService = null;
713 BlazeModule outputModule = null;
714 for (BlazeModule module : blazeModules) {
715 OutputService moduleService = module.getOutputService();
716 if (moduleService != null) {
717 if (outputService != null) {
718 throw new IllegalStateException(String.format(
719 "More than one module (%s and %s) returns an output service",
720 module.getClass(), outputModule.getClass()));
721 }
722 outputService = moduleService;
723 outputModule = module;
724 }
725 }
726
727 skyframeExecutor.setBatchStatter(outputService == null
728 ? null
729 : outputService.getBatchStatter());
730
731 outputFileSystem = determineOutputFileSystem();
732
733 // Ensure that the working directory will be under the workspace directory.
734 Path workspace = getWorkspace();
735 if (inWorkspace()) {
736 workingDirectory = workspace.getRelative(options.clientCwd);
737 } else {
738 workspace = FileSystemUtils.getWorkingDirectory(directories.getFileSystem());
739 workingDirectory = workspace;
740 }
741 updateClientEnv(options.clientEnv, options.ignoreClientEnv);
742 loadingPhaseRunner.updatePatternEvaluator(workingDirectory.relativeTo(workspace));
743
744 // Fail fast in the case where a Blaze command forgets to install the package path correctly.
745 skyframeExecutor.setActive(false);
746 // Let skyframe figure out if it needs to store graph edges for this build.
747 skyframeExecutor.decideKeepIncrementalState(
748 startupOptionsProvider.getOptions(BlazeServerStartupOptions.class).batch,
749 optionsParser.getOptions(BuildView.Options.class));
750
751 // Conditionally enable profiling
752 // We need to compensate for launchTimeNanos (measurements taken outside of the jvm).
753 long startupTimeNanos = options.startupTime * 1000000L;
754 if (initProfiler(options, this.getCommandId(), execStartTimeNanos - startupTimeNanos)) {
755 Profiler profiler = Profiler.instance();
756
757 // Instead of logEvent() we're calling the low level function to pass the timings we took in
758 // the launcher. We're setting the INIT phase marker so that it follows immediately the LAUNCH
759 // phase.
760 profiler.logSimpleTaskDuration(execStartTimeNanos - startupTimeNanos, 0, ProfilerTask.PHASE,
761 ProfilePhase.LAUNCH.description);
762 profiler.logSimpleTaskDuration(execStartTimeNanos, 0, ProfilerTask.PHASE,
763 ProfilePhase.INIT.description);
764 }
765
766 if (options.memoryProfilePath != null) {
767 Path memoryProfilePath = getWorkingDirectory().getRelative(options.memoryProfilePath);
768 try {
769 MemoryProfiler.instance().start(memoryProfilePath.getOutputStream());
770 } catch (IOException e) {
771 getReporter().handle(
772 Event.error("Error while creating memory profile file: " + e.getMessage()));
773 }
774 }
775
776 eventBus.post(new CommandStartEvent(commandName, commandId, clientEnv, workingDirectory));
777 // Initialize exit code to dummy value for afterCommand.
778 storedExitCode.set(ExitCode.RESERVED.getNumericExitCode());
779 }
780
781 /**
782 * Hook method called by the BlazeCommandDispatcher right before the dispatch
783 * of each command ends (while its outcome can still be modified).
784 */
785 ExitCode precompleteCommand(ExitCode originalExit) {
786 eventBus.post(new CommandPrecompleteEvent(originalExit));
787 // If Blaze did not suffer an infrastructure failure, check for errors in modules.
788 ExitCode exitCode = originalExit;
789 if (!originalExit.isInfrastructureFailure()) {
790 if (pendingException != null) {
791 exitCode = pendingException.getExitCode();
792 }
793 }
794 pendingException = null;
795 return exitCode;
796 }
797
798 /**
799 * Posts the {@link CommandCompleteEvent}, so that listeners can tidy up. Called by {@link
800 * #afterCommand}, and by BugReport when crashing from an exception in an async thread.
801 */
802 public void notifyCommandComplete(int exitCode) {
803 if (!storedExitCode.compareAndSet(ExitCode.RESERVED.getNumericExitCode(), exitCode)) {
804 // This command has already been called, presumably because there is a race between the main
805 // thread and a worker thread that crashed. Don't try to arbitrate the dispute. If the main
806 // thread won the race (unlikely, but possible), this may be incorrectly logged as a success.
807 return;
808 }
809 eventBus.post(new CommandCompleteEvent(exitCode));
810 }
811
812 /**
813 * Hook method called by the BlazeCommandDispatcher after the dispatch of each
814 * command.
815 */
816 @VisibleForTesting
817 public void afterCommand(int exitCode) {
818 // Remove any filters that the command might have added to the reporter.
819 getReporter().setOutputFilter(OutputFilter.OUTPUT_EVERYTHING);
820
821 notifyCommandComplete(exitCode);
822
823 for (BlazeModule module : blazeModules) {
824 module.afterCommand();
825 }
826
827 clearEventBus();
828
829 try {
830 Profiler.instance().stop();
831 MemoryProfiler.instance().stop();
832 } catch (IOException e) {
833 getReporter().handle(Event.error("Error while writing profile file: " + e.getMessage()));
834 }
835 }
836
837 // Make sure we keep a strong reference to this logger, so that the
838 // configuration isn't lost when the gc kicks in.
839 private static Logger templateLogger = Logger.getLogger("com.google.devtools.build");
840
841 /**
842 * Configures "com.google.devtools.build.*" loggers to the given
843 * {@code level}. Note: This code relies on static state.
844 */
845 public static void setupLogging(Level level) {
846 templateLogger.setLevel(level);
847 templateLogger.info("Log level: " + templateLogger.getLevel());
848 }
849
850 /**
851 * Return an unmodifiable view of the blaze client's environment when it
852 * invoked the most recent command. Updates from future requests will be
853 * accessible from this view.
854 */
855 public Map<String, String> getClientEnv() {
856 return Collections.unmodifiableMap(clientEnv);
857 }
858
859 @VisibleForTesting
860 void updateClientEnv(List<Map.Entry<String, String>> clientEnvList, boolean ignoreClientEnv) {
861 clientEnv.clear();
862
863 Collection<Map.Entry<String, String>> env =
864 ignoreClientEnv ? System.getenv().entrySet() : clientEnvList;
865 for (Map.Entry<String, String> entry : env) {
866 clientEnv.put(entry.getKey(), entry.getValue());
867 }
868 }
869
870 /**
871 * Returns the Clock-instance used for the entire build. Before,
872 * individual classes (such as Profiler) used to specify the type
873 * of clock (e.g. EpochClock) they wanted to use. This made it
874 * difficult to get Blaze working on Windows as some of the clocks
875 * available for Linux aren't (directly) available on Windows.
876 * Setting the Blaze-wide clock upon construction of BlazeRuntime
877 * allows injecting whatever Clock instance should be used from
878 * BlazeMain.
879 *
880 * @return The Blaze-wide clock
881 */
882 public Clock getClock() {
883 return clock;
884 }
885
886 public OptionsProvider getStartupOptionsProvider() {
887 return startupOptionsProvider;
888 }
889
890 /**
891 * An array of String values useful if Blaze crashes.
892 * For now, just returns the size of the action cache and the build id.
893 */
894 public String[] getCrashData() {
895 return new String[]{
896 getFileSizeString(CompactPersistentActionCache.cacheFile(getCacheDirectory()),
897 "action cache"),
898 commandIdString(),
899 };
900 }
901
902 private String commandIdString() {
903 UUID uuid = getCommandId();
904 return (uuid == null)
905 ? "no build id"
906 : uuid + " (build id)";
907 }
908
909 /**
910 * @return the OutputService in use, or null if none.
911 */
912 public OutputService getOutputService() {
913 return outputService;
914 }
915
916 private String getFileSizeString(Path path, String type) {
917 try {
918 return String.format("%d bytes (%s)", path.getFileSize(), type);
919 } catch (IOException e) {
920 return String.format("unknown file size (%s)", type);
921 }
922 }
923
924 /**
925 * Returns the UUID that Blaze uses to identify everything
926 * logged from the current build command.
927 */
928 public UUID getCommandId() {
929 return commandId;
930 }
931
932 void setCommandMap(Map<String, BlazeCommand> commandMap) {
933 this.commandMap = ImmutableMap.copyOf(commandMap);
934 }
935
936 public Map<String, BlazeCommand> getCommandMap() {
937 return commandMap;
938 }
939
940 /**
941 * Sets the UUID that Blaze uses to identify everything
942 * logged from the current build command.
943 */
944 @VisibleForTesting
945 public void setCommandId(UUID runId) {
946 commandId = runId;
947 }
948
949 /**
950 * Constructs a build configuration key for the given options.
951 */
952 public BuildConfigurationKey getBuildConfigurationKey(BuildOptions buildOptions,
953 ImmutableSortedSet<String> multiCpu) {
954 return new BuildConfigurationKey(buildOptions, directories, clientEnv, multiCpu);
955 }
956
957 /**
958 * This method only exists for the benefit of InfoCommand, which needs to construct a {@link
959 * BuildConfigurationCollection} without running a full loading phase. Don't add any more clients;
960 * instead, we should change info so that it doesn't need the configuration.
961 */
962 public BuildConfigurationCollection getConfigurations(OptionsProvider optionsProvider)
963 throws InvalidConfigurationException, InterruptedException {
964 BuildConfigurationKey configurationKey = getBuildConfigurationKey(
965 createBuildOptions(optionsProvider), ImmutableSortedSet.<String>of());
966 boolean keepGoing = optionsProvider.getOptions(BuildView.Options.class).keepGoing;
967 LoadedPackageProvider loadedPackageProvider =
968 loadingPhaseRunner.loadForConfigurations(reporter,
969 ImmutableSet.copyOf(configurationKey.getLabelsToLoadUnconditionally().values()),
970 keepGoing);
971 if (loadedPackageProvider == null) {
972 throw new InvalidConfigurationException("Configuration creation failed");
973 }
974 return skyframeExecutor.createConfigurations(keepGoing, configurationFactory,
975 configurationKey);
976 }
977
978 /**
979 * Initializes the package cache using the given options, and syncs the package cache. Also
980 * injects a defaults package using the options for the {@link BuildConfiguration}.
981 *
982 * @see DefaultsPackage
983 */
984 public void setupPackageCache(PackageCacheOptions packageCacheOptions,
985 String defaultsPackageContents) throws InterruptedException, AbruptExitException {
986 if (!skyframeExecutor.hasIncrementalState()) {
987 clearSkyframeRelevantCaches();
988 }
989 skyframeExecutor.sync(packageCacheOptions, getWorkingDirectory(),
990 defaultsPackageContents, getCommandId());
991 }
992
993 public void shutdown() {
994 for (BlazeModule module : blazeModules) {
995 module.blazeShutdown();
996 }
997 }
998
999 /**
1000 * Throws the exception currently queued by a Blaze module.
1001 *
1002 * <p>This should be called as often as is practical so that errors are reported as soon as
1003 * possible. Ideally, we'd not need this, but the event bus swallows exceptions so we raise
1004 * the exception this way.
1005 */
1006 public void throwPendingException() throws AbruptExitException {
1007 if (pendingException != null) {
1008 AbruptExitException exception = pendingException;
1009 pendingException = null;
1010 throw exception;
1011 }
1012 }
1013
1014 /**
1015 * Returns the defaults package for the default settings. Should only be called by commands that
1016 * do <i>not</i> process {@link BuildOptions}, since build options can alter the contents of the
1017 * defaults package, which will not be reflected here.
1018 */
1019 public String getDefaultsPackageContent() {
1020 return ruleClassProvider.getDefaultsPackageContent();
1021 }
1022
1023 /**
1024 * Returns the defaults package for the given options taken from an optionsProvider.
1025 */
1026 public String getDefaultsPackageContent(OptionsClassProvider optionsProvider) {
1027 return ruleClassProvider.getDefaultsPackageContent(optionsProvider);
1028 }
1029
1030 /**
1031 * Creates a BuildOptions class for the given options taken from an optionsProvider.
1032 */
1033 public BuildOptions createBuildOptions(OptionsClassProvider optionsProvider) {
1034 return ruleClassProvider.createBuildOptions(optionsProvider);
1035 }
1036
1037 /**
1038 * An EventBus exception handler that will report the exception to a remote server, if a
1039 * handler is registered.
1040 */
1041 public static final class RemoteExceptionHandler implements SubscriberExceptionHandler {
1042 @Override
1043 public void handleException(Throwable exception, SubscriberExceptionContext context) {
1044 LoggingUtil.logToRemote(Level.SEVERE, "Failure in EventBus subscriber.", exception);
1045 }
1046 }
1047
1048 /**
1049 * An EventBus exception handler that will call BugReport.handleCrash exiting
1050 * the current thread.
1051 */
1052 public static final class BugReportingExceptionHandler implements SubscriberExceptionHandler {
1053 @Override
1054 public void handleException(Throwable exception, SubscriberExceptionContext context) {
1055 BugReport.handleCrash(exception);
1056 }
1057 }
1058
1059 /**
1060 * Main method for the Blaze server startup. Note: This method logs
1061 * exceptions to remote servers. Do not add this to a unittest.
1062 */
1063 public static void main(Iterable<Class<? extends BlazeModule>> moduleClasses, String[] args) {
1064 setupUncaughtHandler(args);
1065 List<BlazeModule> modules = createModules(moduleClasses);
1066 if (args.length >= 1 && args[0].equals("--batch")) {
1067 // Run Blaze in batch mode.
1068 System.exit(batchMain(modules, args));
1069 }
1070 LOG.info("Starting Blaze server with args " + Arrays.toString(args));
1071 try {
1072 // Run Blaze in server mode.
1073 System.exit(serverMain(modules, OutErr.SYSTEM_OUT_ERR, args));
1074 } catch (RuntimeException | Error e) { // A definite bug...
1075 BugReport.printBug(OutErr.SYSTEM_OUT_ERR, e);
1076 BugReport.sendBugReport(e, Arrays.asList(args));
1077 System.exit(ExitCode.BLAZE_INTERNAL_ERROR.getNumericExitCode());
1078 throw e; // Shouldn't get here.
1079 }
1080 }
1081
1082 @VisibleForTesting
1083 public static List<BlazeModule> createModules(
1084 Iterable<Class<? extends BlazeModule>> moduleClasses) {
1085 ImmutableList.Builder<BlazeModule> result = ImmutableList.builder();
1086 for (Class<? extends BlazeModule> moduleClass : moduleClasses) {
1087 try {
1088 BlazeModule module = moduleClass.newInstance();
1089 result.add(module);
1090 } catch (Throwable e) {
1091 throw new IllegalStateException("Cannot instantiate module " + moduleClass.getName(), e);
1092 }
1093 }
1094
1095 return result.build();
1096 }
1097
1098 /**
1099 * Generates a string form of a request to be written to the logs,
1100 * filtering the user environment to remove anything that looks private.
1101 * The current filter criteria removes any variable whose name includes
1102 * "auth", "pass", or "cookie".
1103 *
1104 * @param requestStrings
1105 * @return the filtered request to write to the log.
1106 */
1107 @VisibleForTesting
1108 public static String getRequestLogString(List<String> requestStrings) {
1109 StringBuilder buf = new StringBuilder();
1110 buf.append('[');
1111 String sep = "";
1112 for (String s : requestStrings) {
1113 buf.append(sep);
1114 if (s.startsWith("--client_env")) {
1115 int varStart = "--client_env=".length();
1116 int varEnd = s.indexOf('=', varStart);
1117 String varName = s.substring(varStart, varEnd);
1118 if (suppressFromLog.matcher(varName).matches()) {
1119 buf.append("--client_env=");
1120 buf.append(varName);
1121 buf.append("=__private_value_removed__");
1122 } else {
1123 buf.append(s);
1124 }
1125 } else {
1126 buf.append(s);
1127 }
1128 sep = ", ";
1129 }
1130 buf.append(']');
1131 return buf.toString();
1132 }
1133
1134 /**
1135 * Command line options split in to two parts: startup options and everything else.
1136 */
1137 @VisibleForTesting
1138 static class CommandLineOptions {
1139 private final List<String> startupArgs;
1140 private final List<String> otherArgs;
1141
1142 CommandLineOptions(List<String> startupArgs, List<String> otherArgs) {
1143 this.startupArgs = ImmutableList.copyOf(startupArgs);
1144 this.otherArgs = ImmutableList.copyOf(otherArgs);
1145 }
1146
1147 public List<String> getStartupArgs() {
1148 return startupArgs;
1149 }
1150
1151 public List<String> getOtherArgs() {
1152 return otherArgs;
1153 }
1154 }
1155
1156 /**
1157 * Splits given arguments into two lists - arguments matching options defined in this class
1158 * and everything else, while preserving order in each list.
1159 */
1160 static CommandLineOptions splitStartupOptions(
1161 Iterable<BlazeModule> modules, String... args) {
1162 List<String> prefixes = new ArrayList<>();
1163 List<Field> startupFields = Lists.newArrayList();
1164 for (Class<? extends OptionsBase> defaultOptions
1165 : BlazeCommandUtils.getStartupOptions(modules)) {
1166 startupFields.addAll(ImmutableList.copyOf(defaultOptions.getFields()));
1167 }
1168
1169 for (Field field : startupFields) {
1170 if (field.isAnnotationPresent(Option.class)) {
1171 prefixes.add("--" + field.getAnnotation(Option.class).name());
1172 if (field.getType() == boolean.class || field.getType() == TriState.class) {
1173 prefixes.add("--no" + field.getAnnotation(Option.class).name());
1174 }
1175 }
1176 }
1177
1178 List<String> startupArgs = new ArrayList<>();
1179 List<String> otherArgs = Lists.newArrayList(args);
1180
1181 for (Iterator<String> argi = otherArgs.iterator(); argi.hasNext(); ) {
1182 String arg = argi.next();
1183 if (!arg.startsWith("--")) {
1184 break; // stop at command - all startup options would be specified before it.
1185 }
1186 for (String prefix : prefixes) {
1187 if (arg.startsWith(prefix)) {
1188 startupArgs.add(arg);
1189 argi.remove();
1190 break;
1191 }
1192 }
1193 }
1194 return new CommandLineOptions(startupArgs, otherArgs);
1195 }
1196
1197 private static void captureSigint() {
1198 final Thread mainThread = Thread.currentThread();
1199 final AtomicInteger numInterrupts = new AtomicInteger();
1200
1201 final Runnable interruptWatcher = new Runnable() {
1202 @Override
1203 public void run() {
1204 int count = 0;
1205 // Not an actual infinite loop because it's run in a daemon thread.
1206 while (true) {
1207 count++;
1208 Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
1209 LOG.warning("Slow interrupt number " + count + " in batch mode");
1210 ThreadUtils.warnAboutSlowInterrupt();
1211 }
1212 }
1213 };
1214
1215 new InterruptSignalHandler() {
1216 @Override
1217 public void run() {
1218 LOG.info("User interrupt");
1219 OutErr.SYSTEM_OUT_ERR.printErrLn("Blaze received an interrupt");
1220 mainThread.interrupt();
1221
1222 int curNumInterrupts = numInterrupts.incrementAndGet();
1223 if (curNumInterrupts == 1) {
1224 Thread interruptWatcherThread = new Thread(interruptWatcher, "interrupt-watcher");
1225 interruptWatcherThread.setDaemon(true);
1226 interruptWatcherThread.start();
1227 } else if (curNumInterrupts == 2) {
1228 LOG.warning("Second --batch interrupt: Reverting to JVM SIGINT handler");
1229 uninstall();
1230 }
1231 }
1232 };
1233 }
1234
1235 /**
1236 * A main method that runs blaze commands in batch mode. The return value indicates the desired
1237 * exit status of the program.
1238 */
1239 private static int batchMain(Iterable<BlazeModule> modules, String[] args) {
1240 captureSigint();
1241 CommandLineOptions commandLineOptions = splitStartupOptions(modules, args);
1242 LOG.info("Running Blaze in batch mode with startup args "
1243 + commandLineOptions.getStartupArgs());
1244
1245 String memoryWarning = validateJvmMemorySettings();
1246 if (memoryWarning != null) {
1247 OutErr.SYSTEM_OUT_ERR.printErrLn(memoryWarning);
1248 }
1249
1250 BlazeRuntime runtime;
1251 try {
1252 runtime = newRuntime(modules, parseOptions(modules, commandLineOptions.getStartupArgs()));
1253 } catch (OptionsParsingException e) {
1254 OutErr.SYSTEM_OUT_ERR.printErr(e.getMessage());
1255 return ExitCode.COMMAND_LINE_ERROR.getNumericExitCode();
1256 } catch (AbruptExitException e) {
1257 OutErr.SYSTEM_OUT_ERR.printErr(e.getMessage());
1258 return e.getExitCode().getNumericExitCode();
1259 }
1260
1261 BlazeCommandDispatcher dispatcher =
1262 new BlazeCommandDispatcher(runtime, getBuiltinCommandList());
1263
1264 try {
1265 LOG.info(getRequestLogString(commandLineOptions.getOtherArgs()));
1266 return dispatcher.exec(commandLineOptions.getOtherArgs(), OutErr.SYSTEM_OUT_ERR,
1267 runtime.getClock().currentTimeMillis());
1268 } catch (BlazeCommandDispatcher.ShutdownBlazeServerException e) {
1269 return e.getExitStatus();
1270 } finally {
1271 runtime.shutdown();
1272 dispatcher.shutdown();
1273 }
1274 }
1275
1276 /**
1277 * A main method that does not send email. The return value indicates the desired exit status of
1278 * the program.
1279 */
1280 private static int serverMain(Iterable<BlazeModule> modules, OutErr outErr, String[] args) {
1281 try {
1282 createBlazeRPCServer(modules, Arrays.asList(args)).serve();
1283 return ExitCode.SUCCESS.getNumericExitCode();
1284 } catch (OptionsParsingException e) {
1285 outErr.printErr(e.getMessage());
1286 return ExitCode.COMMAND_LINE_ERROR.getNumericExitCode();
1287 } catch (IOException e) {
1288 outErr.printErr("I/O Error: " + e.getMessage());
1289 return ExitCode.BUILD_FAILURE.getNumericExitCode();
1290 } catch (AbruptExitException e) {
1291 outErr.printErr(e.getMessage());
1292 return e.getExitCode().getNumericExitCode();
1293 }
1294 }
1295
1296 private static FileSystem fileSystemImplementation() {
1297 // The JNI-based UnixFileSystem is faster, but on Windows it is not available.
1298 return OS.getCurrent() == OS.WINDOWS ? new JavaIoFileSystem() : new UnixFileSystem();
1299 }
1300
1301 /**
1302 * Creates and returns a new Blaze RPCServer. Call {@link RPCServer#serve()} to start the server.
1303 */
1304 private static RPCServer createBlazeRPCServer(Iterable<BlazeModule> modules, List<String> args)
1305 throws IOException, OptionsParsingException, AbruptExitException {
1306 OptionsProvider options = parseOptions(modules, args);
1307 BlazeServerStartupOptions startupOptions = options.getOptions(BlazeServerStartupOptions.class);
1308
1309 final BlazeRuntime runtime = newRuntime(modules, options);
1310 final BlazeCommandDispatcher dispatcher =
1311 new BlazeCommandDispatcher(runtime, getBuiltinCommandList());
1312 final String memoryWarning = validateJvmMemorySettings();
1313
1314 final ServerCommand blazeCommand;
1315
1316 // Adaptor from RPC mechanism to BlazeCommandDispatcher:
1317 blazeCommand = new ServerCommand() {
1318 private boolean shutdown = false;
1319
1320 @Override
1321 public int exec(List<String> args, OutErr outErr, long firstContactTime) {
1322 LOG.info(getRequestLogString(args));
1323 if (memoryWarning != null) {
1324 outErr.printErrLn(memoryWarning);
1325 }
1326
1327 try {
1328 return dispatcher.exec(args, outErr, firstContactTime);
1329 } catch (BlazeCommandDispatcher.ShutdownBlazeServerException e) {
1330 if (e.getCause() != null) {
1331 StringWriter message = new StringWriter();
1332 message.write("Shutting down due to exception:\n");
1333 PrintWriter writer = new PrintWriter(message, true);
1334 e.printStackTrace(writer);
1335 writer.flush();
1336 LOG.severe(message.toString());
1337 }
1338 shutdown = true;
1339 runtime.shutdown();
1340 dispatcher.shutdown();
1341 return e.getExitStatus();
1342 }
1343 }
1344
1345 @Override
1346 public boolean shutdown() {
1347 return shutdown;
1348 }
1349 };
1350
1351 RPCServer server = RPCServer.newServerWith(runtime.getClock(), blazeCommand,
1352 runtime.getServerDirectory(), runtime.getWorkspace(), startupOptions.maxIdleSeconds);
1353 return server;
1354 }
1355
1356 private static Function<String, String> sourceFunctionForMap(final Map<String, String> map) {
1357 return new Function<String, String>() {
1358 @Override
1359 public String apply(String input) {
1360 if (!map.containsKey(input)) {
1361 return "default";
1362 }
1363
1364 if (map.get(input).isEmpty()) {
1365 return "command line";
1366 }
1367
1368 return map.get(input);
1369 }
1370 };
1371 }
1372
1373 /**
1374 * Parses the command line arguments into a {@link OptionsParser} object.
1375 *
1376 * <p>This function needs to parse the --option_sources option manually so that the real option
1377 * parser can set the source for every option correctly. If that cannot be parsed or is missing,
1378 * we just report an unknown source for every startup option.
1379 */
1380 private static OptionsProvider parseOptions(
1381 Iterable<BlazeModule> modules, List<String> args) throws OptionsParsingException {
1382 Set<Class<? extends OptionsBase>> optionClasses = Sets.newHashSet();
1383 optionClasses.addAll(BlazeCommandUtils.getStartupOptions(modules));
1384 // First parse the command line so that we get the option_sources argument
1385 OptionsParser parser = OptionsParser.newOptionsParser(optionClasses);
1386 parser.setAllowResidue(false);
1387 parser.parse(OptionPriority.COMMAND_LINE, null, args);
1388 Function<? super String, String> sourceFunction =
1389 sourceFunctionForMap(parser.getOptions(BlazeServerStartupOptions.class).optionSources);
1390
1391 // Then parse the command line again, this time with the correct option sources
1392 parser = OptionsParser.newOptionsParser(optionClasses);
1393 parser.setAllowResidue(false);
1394 parser.parseWithSourceFunction(OptionPriority.COMMAND_LINE, sourceFunction, args);
1395 return parser;
1396 }
1397
1398 /**
1399 * Creates a new blaze runtime, given the install and output base directories.
1400 *
1401 * <p>Note: This method can and should only be called once per startup, as it also creates the
1402 * filesystem object that will be used for the runtime. So it should only ever be called from the
1403 * main method of the Blaze program.
1404 *
1405 * @param options Blaze startup options.
1406 *
1407 * @return a new BlazeRuntime instance initialized with the given filesystem and directories, and
1408 * an error string that, if not null, describes a fatal initialization failure that makes
1409 * this runtime unsuitable for real commands
1410 */
1411 private static BlazeRuntime newRuntime(
1412 Iterable<BlazeModule> blazeModules, OptionsProvider options) throws AbruptExitException {
1413 for (BlazeModule module : blazeModules) {
1414 module.globalInit(options);
1415 }
1416
1417 BlazeServerStartupOptions startupOptions = options.getOptions(BlazeServerStartupOptions.class);
1418 PathFragment workspaceDirectory = startupOptions.workspaceDirectory;
1419 PathFragment installBase = startupOptions.installBase;
1420 PathFragment outputBase = startupOptions.outputBase;
1421
1422 OsUtils.maybeForceJNI(installBase); // Must be before first use of JNI.
1423
1424 // From the point of view of the Java program --install_base and --output_base
1425 // are mandatory options, despite the comment in their declarations.
1426 if (installBase == null || !installBase.isAbsolute()) { // (includes "" default case)
1427 throw new IllegalArgumentException(
1428 "Bad --install_base option specified: '" + installBase + "'");
1429 }
1430 if (outputBase != null && !outputBase.isAbsolute()) { // (includes "" default case)
1431 throw new IllegalArgumentException(
1432 "Bad --output_base option specified: '" + outputBase + "'");
1433 }
1434
1435 PathFragment outputPathFragment = BlazeDirectories.outputPathFromOutputBase(
1436 outputBase, workspaceDirectory);
1437 FileSystem fs = null;
1438 for (BlazeModule module : blazeModules) {
1439 FileSystem moduleFs = module.getFileSystem(options, outputPathFragment);
1440 if (moduleFs != null) {
1441 Preconditions.checkState(fs == null, "more than one module returns a file system");
1442 fs = moduleFs;
1443 }
1444 }
1445
1446 if (fs == null) {
1447 fs = fileSystemImplementation();
1448 }
1449 Path.setFileSystemForSerialization(fs);
1450
1451 Path installBasePath = fs.getPath(installBase);
1452 Path outputBasePath = fs.getPath(outputBase);
1453 Path workspaceDirectoryPath = null;
1454 if (!workspaceDirectory.equals(PathFragment.EMPTY_FRAGMENT)) {
1455 workspaceDirectoryPath = fs.getPath(workspaceDirectory);
1456 }
1457
1458 BlazeDirectories directories =
1459 new BlazeDirectories(installBasePath, outputBasePath, workspaceDirectoryPath);
1460
1461 Clock clock = BlazeClock.instance();
1462
1463 BinTools binTools;
1464 try {
1465 binTools = BinTools.forProduction(directories);
1466 } catch (IOException e) {
1467 throw new AbruptExitException(
1468 "Cannot enumerate embedded binaries: " + e.getMessage(),
1469 ExitCode.LOCAL_ENVIRONMENTAL_ERROR);
1470 }
1471
1472 BlazeRuntime.Builder runtimeBuilder = new BlazeRuntime.Builder().setDirectories(directories)
1473 .setStartupOptionsProvider(options)
1474 .setBinTools(binTools)
1475 .setClock(clock)
1476 // TODO(bazel-team): Make BugReportingExceptionHandler the default.
1477 // See bug "Make exceptions in EventBus subscribers fatal"
1478 .setEventBusExceptionHandler(
1479 startupOptions.fatalEventBusExceptions || !BlazeVersionInfo.instance().isReleasedBlaze()
1480 ? new BlazeRuntime.BugReportingExceptionHandler()
1481 : new BlazeRuntime.RemoteExceptionHandler());
1482
1483 runtimeBuilder.setRunfilesPrefix(new PathFragment(Constants.RUNFILES_PREFIX));
1484 for (BlazeModule blazeModule : blazeModules) {
1485 runtimeBuilder.addBlazeModule(blazeModule);
1486 }
1487
1488 BlazeRuntime runtime = runtimeBuilder.build();
1489 BugReport.setRuntime(runtime);
1490 return runtime;
1491 }
1492
1493 /**
1494 * Returns null if JVM memory settings are considered safe, and an error string otherwise.
1495 */
1496 private static String validateJvmMemorySettings() {
1497 boolean is64BitVM = "64".equals(System.getProperty("sun.arch.data.model"));
1498 if (is64BitVM) {
1499 return null;
1500 }
1501 MemoryMXBean mem = ManagementFactory.getMemoryMXBean();
1502 long heapSize = mem.getHeapMemoryUsage().getMax();
1503 long nonHeapSize = mem.getNonHeapMemoryUsage().getMax();
1504 if (heapSize == -1 || nonHeapSize == -1) {
1505 return null;
1506 }
1507
1508 if (heapSize + nonHeapSize > MAX_BLAZE32_RESERVED_MEMORY) {
1509 return String.format(
1510 "WARNING: JVM reserved %d MB of virtual memory (above threshold of %d MB). "
1511 + "This may result in OOMs at runtime. Use lower values of MaxPermSize "
1512 + "or switch to blaze64.",
1513 (heapSize + nonHeapSize) >> 20, MAX_BLAZE32_RESERVED_MEMORY >> 20);
1514 } else if (heapSize < MIN_BLAZE32_HEAP_SIZE) {
1515 return String.format(
1516 "WARNING: JVM heap size is %d MB. You probably have a custom -Xmx setting in your "
1517 + "local Blaze configuration. This may result in OOMs. Removing overrides of -Xmx "
1518 + "settings is advised.",
1519 heapSize >> 20);
1520 } else {
1521 return null;
1522 }
1523 }
1524
1525 /**
1526 * Make sure async threads cannot be orphaned. This method makes sure bugs are reported to
1527 * telemetry and the proper exit code is reported.
1528 */
1529 private static void setupUncaughtHandler(final String[] args) {
1530 Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
1531 @Override
1532 public void uncaughtException(Thread thread, Throwable throwable) {
1533 BugReport.handleCrash(throwable, args);
1534 }
1535 });
1536 }
1537
1538
1539 /**
1540 * Returns an immutable list containing new instances of each Blaze command.
1541 */
1542 @VisibleForTesting
1543 public static List<BlazeCommand> getBuiltinCommandList() {
1544 return ImmutableList.of(
1545 new BuildCommand(),
1546 new CanonicalizeCommand(),
1547 new CleanCommand(),
1548 new HelpCommand(),
1549 new SkylarkCommand(),
1550 new InfoCommand(),
1551 new ProfileCommand(),
1552 new QueryCommand(),
1553 new RunCommand(),
1554 new ShutdownCommand(),
1555 new TestCommand(),
1556 new VersionCommand());
1557 }
1558
1559 /**
1560 * A builder for {@link BlazeRuntime} objects. The only required fields are the {@link
1561 * BlazeDirectories}, and the {@link RuleClassProvider} (except for testing). All other fields
1562 * have safe default values.
1563 *
1564 * <p>If a {@link ConfigurationFactory} is set, then the builder ignores the host system flag.
1565 * <p>The default behavior of the BlazeRuntime's EventBus is to exit when a subscriber throws
1566 * an exception. Please plan appropriately.
1567 */
1568 public static class Builder {
1569
1570 private PathFragment runfilesPrefix = PathFragment.EMPTY_FRAGMENT;
1571 private BlazeDirectories directories;
1572 private Reporter reporter;
1573 private ConfigurationFactory configurationFactory;
1574 private Clock clock;
1575 private OptionsProvider startupOptionsProvider;
1576 private final List<BlazeModule> blazeModules = Lists.newArrayList();
1577 private SubscriberExceptionHandler eventBusExceptionHandler =
1578 new RemoteExceptionHandler();
1579 private BinTools binTools;
1580 private UUID instanceId;
1581
1582 public BlazeRuntime build() throws AbruptExitException {
1583 Preconditions.checkNotNull(directories);
1584 Preconditions.checkNotNull(startupOptionsProvider);
1585 Reporter reporter = (this.reporter == null) ? new Reporter() : this.reporter;
1586
1587 Clock clock = (this.clock == null) ? BlazeClock.instance() : this.clock;
1588 UUID instanceId = (this.instanceId == null) ? UUID.randomUUID() : this.instanceId;
1589
1590 Preconditions.checkNotNull(clock);
1591 Map<String, String> clientEnv = new HashMap<>();
1592 TimestampGranularityMonitor timestampMonitor = new TimestampGranularityMonitor(clock);
1593
1594 Preprocessor.Factory.Supplier preprocessorFactorySupplier = null;
1595 SkyframeExecutorFactory skyframeExecutorFactory = null;
1596 for (BlazeModule module : blazeModules) {
1597 module.blazeStartup(startupOptionsProvider,
1598 BlazeVersionInfo.instance(), instanceId, directories, clock);
1599 Preprocessor.Factory.Supplier modulePreprocessorFactorySupplier =
1600 module.getPreprocessorFactorySupplier();
1601 if (modulePreprocessorFactorySupplier != null) {
1602 Preconditions.checkState(preprocessorFactorySupplier == null,
1603 "more than one module defines a preprocessor factory supplier");
1604 preprocessorFactorySupplier = modulePreprocessorFactorySupplier;
1605 }
1606 SkyframeExecutorFactory skyFactory = module.getSkyframeExecutorFactory();
1607 if (skyFactory != null) {
1608 Preconditions.checkState(skyframeExecutorFactory == null,
1609 "At most one skyframe factory supported. But found two: %s and %s", skyFactory,
1610 skyframeExecutorFactory);
1611 skyframeExecutorFactory = skyFactory;
1612 }
1613 }
1614 if (skyframeExecutorFactory == null) {
1615 skyframeExecutorFactory = new SequencedSkyframeExecutorFactory();
1616 }
1617 if (preprocessorFactorySupplier == null) {
1618 preprocessorFactorySupplier = Preprocessor.Factory.Supplier.NullSupplier.INSTANCE;
1619 }
1620
1621 ConfiguredRuleClassProvider.Builder ruleClassBuilder =
1622 new ConfiguredRuleClassProvider.Builder();
1623 for (BlazeModule module : blazeModules) {
1624 module.initializeRuleClasses(ruleClassBuilder);
1625 }
1626
1627 Map<String, String> platformRegexps = null;
1628 {
1629 ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
1630 for (BlazeModule module : blazeModules) {
1631 builder.putAll(module.getPlatformSetRegexps());
1632 }
1633 platformRegexps = builder.build();
1634 if (platformRegexps.isEmpty()) {
1635 platformRegexps = null; // Use the default.
1636 }
1637 }
1638
1639 Set<Path> immutableDirectories = null;
1640 {
1641 ImmutableSet.Builder<Path> builder = new ImmutableSet.Builder<>();
1642 for (BlazeModule module : blazeModules) {
1643 builder.addAll(module.getImmutableDirectories());
1644 }
1645 immutableDirectories = builder.build();
1646 }
1647
1648 Iterable<DiffAwareness.Factory> diffAwarenessFactories = null;
1649 {
1650 ImmutableList.Builder<DiffAwareness.Factory> builder = new ImmutableList.Builder<>();
1651 boolean watchFS = startupOptionsProvider != null
1652 && startupOptionsProvider.getOptions(BlazeServerStartupOptions.class).watchFS;
1653 for (BlazeModule module : blazeModules) {
1654 builder.addAll(module.getDiffAwarenessFactories(watchFS));
1655 }
1656 diffAwarenessFactories = builder.build();
1657 }
1658
1659 // Merge filters from Blaze modules that allow some action inputs to be missing.
1660 Predicate<PathFragment> allowedMissingInputs = null;
1661 for (BlazeModule module : blazeModules) {
1662 Predicate<PathFragment> modulePredicate = module.getAllowedMissingInputs();
1663 if (modulePredicate != null) {
1664 Preconditions.checkArgument(allowedMissingInputs == null,
1665 "More than one Blaze module allows missing inputs.");
1666 allowedMissingInputs = modulePredicate;
1667 }
1668 }
1669 if (allowedMissingInputs == null) {
1670 allowedMissingInputs = Predicates.alwaysFalse();
1671 }
1672
1673 ConfiguredRuleClassProvider ruleClassProvider = ruleClassBuilder.build();
1674 WorkspaceStatusAction.Factory workspaceStatusActionFactory = null;
1675 for (BlazeModule module : blazeModules) {
1676 WorkspaceStatusAction.Factory candidate = module.getWorkspaceStatusActionFactory();
1677 if (candidate != null) {
1678 Preconditions.checkState(workspaceStatusActionFactory == null,
1679 "more than one module defines a workspace status action factory");
1680 workspaceStatusActionFactory = candidate;
1681 }
1682 }
1683
1684 List<PackageFactory.EnvironmentExtension> extensions = new ArrayList<>();
1685 for (BlazeModule module : blazeModules) {
1686 extensions.add(module.getPackageEnvironmentExtension());
1687 }
1688
1689 // We use an immutable map builder for the nice side effect that it throws if a duplicate key
1690 // is inserted.
1691 ImmutableMap.Builder<SkyFunctionName, SkyFunction> skyFunctions = ImmutableMap.builder();
1692 for (BlazeModule module : blazeModules) {
1693 skyFunctions.putAll(module.getSkyFunctions(directories));
1694 }
1695
1696 ImmutableList.Builder<PrecomputedValue.Injected> precomputedValues = ImmutableList.builder();
1697 for (BlazeModule module : blazeModules) {
1698 precomputedValues.addAll(module.getPrecomputedSkyframeValues());
1699 }
1700
1701 final PackageFactory pkgFactory =
1702 new PackageFactory(ruleClassProvider, platformRegexps, extensions);
1703 SkyframeExecutor skyframeExecutor = skyframeExecutorFactory.create(reporter, pkgFactory,
1704 timestampMonitor, directories, workspaceStatusActionFactory,
1705 ruleClassProvider.getBuildInfoFactories(), immutableDirectories, diffAwarenessFactories,
1706 allowedMissingInputs, preprocessorFactorySupplier, skyFunctions.build(),
1707 precomputedValues.build());
1708
1709 if (configurationFactory == null) {
1710 configurationFactory = new ConfigurationFactory(
1711 ruleClassProvider.getConfigurationCollectionFactory(),
1712 ruleClassProvider.getConfigurationFragments());
1713 }
1714
1715 ProjectFile.Provider projectFileProvider = null;
1716 for (BlazeModule module : blazeModules) {
1717 ProjectFile.Provider candidate = module.createProjectFileProvider();
1718 if (candidate != null) {
1719 Preconditions.checkState(projectFileProvider == null,
1720 "more than one module defines a project file provider");
1721 projectFileProvider = candidate;
1722 }
1723 }
1724
1725 return new BlazeRuntime(directories, reporter, workspaceStatusActionFactory, skyframeExecutor,
1726 pkgFactory, ruleClassProvider, configurationFactory,
1727 runfilesPrefix == null ? PathFragment.EMPTY_FRAGMENT : runfilesPrefix,
1728 clock, startupOptionsProvider, ImmutableList.copyOf(blazeModules),
1729 clientEnv, timestampMonitor,
1730 eventBusExceptionHandler, binTools, projectFileProvider);
1731 }
1732
1733 public Builder setRunfilesPrefix(PathFragment prefix) {
1734 this.runfilesPrefix = prefix;
1735 return this;
1736 }
1737
1738 public Builder setBinTools(BinTools binTools) {
1739 this.binTools = binTools;
1740 return this;
1741 }
1742
1743 public Builder setDirectories(BlazeDirectories directories) {
1744 this.directories = directories;
1745 return this;
1746 }
1747
1748 /**
1749 * Creates and sets a new {@link BlazeDirectories} instance with the given
1750 * parameters.
1751 */
1752 public Builder setDirectories(Path installBase, Path outputBase,
1753 Path workspace) {
1754 this.directories = new BlazeDirectories(installBase, outputBase, workspace);
1755 return this;
1756 }
1757
1758 public Builder setReporter(Reporter reporter) {
1759 this.reporter = reporter;
1760 return this;
1761 }
1762
1763 public Builder setConfigurationFactory(ConfigurationFactory configurationFactory) {
1764 this.configurationFactory = configurationFactory;
1765 return this;
1766 }
1767
1768 public Builder setClock(Clock clock) {
1769 this.clock = clock;
1770 return this;
1771 }
1772
1773 public Builder setStartupOptionsProvider(OptionsProvider startupOptionsProvider) {
1774 this.startupOptionsProvider = startupOptionsProvider;
1775 return this;
1776 }
1777
1778 public Builder addBlazeModule(BlazeModule blazeModule) {
1779 blazeModules.add(blazeModule);
1780 return this;
1781 }
1782
1783 public Builder setInstanceId(UUID id) {
1784 instanceId = id;
1785 return this;
1786 }
1787
1788 @VisibleForTesting
1789 public Builder setEventBusExceptionHandler(
1790 SubscriberExceptionHandler eventBusExceptionHandler) {
1791 this.eventBusExceptionHandler = eventBusExceptionHandler;
1792 return this;
1793 }
1794 }
1795}