private final BuildConfigurationCollection getConfigurations( BuildOptions buildOptions, Set<String> multiCpu, boolean keepGoing) throws InvalidConfigurationException, InterruptedException { SkyframeExecutor executor = runtime.getSkyframeExecutor(); // TODO(bazel-team): consider a possibility of moving ConfigurationFactory construction into // skyframe. return executor.createConfigurations( runtime.getConfigurationFactory(), buildOptions, runtime.getDirectories(), multiCpu, keepGoing); }
private ExitCode doTest( CommandEnvironment env, OptionsProvider options, AggregatingTestListener testListener) { BlazeRuntime runtime = env.getRuntime(); // Run simultaneous build and test. List<String> targets = ProjectFileSupport.getTargets(runtime, options); BuildRequest request = BuildRequest.create( getClass().getAnnotation(Command.class).name(), options, runtime.getStartupOptionsProvider(), targets, env.getReporter().getOutErr(), env.getCommandId(), env.getCommandStartTime()); request.setRunTests(); BuildResult buildResult = new BuildTool(env).processRequest(request, null); Collection<ConfiguredTarget> testTargets = buildResult.getTestTargets(); // TODO(bazel-team): don't handle isEmpty here or fix up a bunch of tests if (buildResult.getSuccessfulTargets() == null) { // This can happen if there were errors in the target parsing or loading phase // (original exitcode=BUILD_FAILURE) or if there weren't but --noanalyze was given // (original exitcode=SUCCESS). env.getReporter().handle(Event.error("Couldn't start the build. Unable to run tests")); return buildResult.getSuccess() ? ExitCode.PARSING_FAILURE : buildResult.getExitCondition(); } // TODO(bazel-team): the check above shadows NO_TESTS_FOUND, but switching the conditions breaks // more tests if (testTargets.isEmpty()) { env.getReporter() .handle(Event.error(null, "No test targets were found, yet testing was requested")); return buildResult.getSuccess() ? ExitCode.NO_TESTS_FOUND : buildResult.getExitCondition(); } boolean buildSuccess = buildResult.getSuccess(); boolean testSuccess = analyzeTestResults(testTargets, testListener, options); if (testSuccess && !buildSuccess) { // If all tests run successfully, test summary should include warning if // there were build errors not associated with the test targets. printer.printLn( AnsiTerminalPrinter.Mode.ERROR + "One or more non-test targets failed to build.\n" + AnsiTerminalPrinter.Mode.DEFAULT); } return buildSuccess ? (testSuccess ? ExitCode.SUCCESS : ExitCode.TESTS_FAILED) : buildResult.getExitCondition(); }
/** * Stops processing the specified request. * * <p>This logs the build result, cleans up and stops the clock. * * @param request the build request that this build tool is servicing * @param crash Any unexpected RuntimeException or Error. May be null * @param exitCondition A suggested exit condition from either the build logic or a thrown * exception somewhere along the way. */ public void stopRequest( BuildRequest request, BuildResult result, Throwable crash, ExitCode exitCondition) { Preconditions.checkState((crash == null) || (exitCondition != ExitCode.SUCCESS)); result.setUnhandledThrowable(crash); result.setExitCondition(exitCondition); // The stop time has to be captured before we send the BuildCompleteEvent. result.setStopTime(runtime.getClock().currentTimeMillis()); getEventBus().post(new BuildCompleteEvent(request, result)); }
@VisibleForTesting protected final LoadingResult runLoadingPhase( final BuildRequest request, final TargetValidator validator) throws LoadingFailedException, TargetParsingException, InterruptedException, AbruptExitException { Profiler.instance().markPhase(ProfilePhase.LOAD); runtime.throwPendingException(); initializeOutputFilter(request); final boolean keepGoing = request.getViewOptions().keepGoing; Callback callback = new Callback() { @Override public void notifyTargets(Collection<Target> targets) throws LoadingFailedException { if (validator != null) { validator.validateTargets(targets, keepGoing); } } @Override public void notifyVisitedPackages(Set<PackageIdentifier> visitedPackages) { runtime.getSkyframeExecutor().updateLoadedPackageSet(visitedPackages); } }; LoadingResult result = runtime .getLoadingPhaseRunner() .execute( getReporter(), getEventBus(), request.getTargets(), request.getLoadingOptions(), runtime.createBuildOptions(request).getAllLabels(), keepGoing, request.shouldRunTests(), callback); runtime.throwPendingException(); return result; }
@Override public ExitCode exec(CommandEnvironment env, OptionsProvider options) throws ShutdownBlazeServerException { BlazeRuntime runtime = env.getRuntime(); Options cleanOptions = options.getOptions(Options.class); cleanOptions.expunge_async = cleanOptions.cleanStyle.equals("expunge_async"); cleanOptions.expunge = cleanOptions.cleanStyle.equals("expunge"); if (!cleanOptions.expunge && !cleanOptions.expunge_async && !cleanOptions.cleanStyle.isEmpty()) { env.getReporter() .handle(Event.error(null, "Invalid clean_style value '" + cleanOptions.cleanStyle + "'")); return ExitCode.COMMAND_LINE_ERROR; } String cleanBanner = cleanOptions.expunge_async ? "Starting clean." : "Starting clean (this may take a while). " + "Consider using --expunge_async if the clean takes more than several minutes."; env.getReporter().handle(Event.info(null /*location*/, cleanBanner)); try { String symlinkPrefix = options.getOptions(BuildRequest.BuildRequestOptions.class).getSymlinkPrefix(); actuallyClean(env, runtime.getOutputBase(), cleanOptions, symlinkPrefix); return ExitCode.SUCCESS; } catch (IOException e) { env.getReporter().handle(Event.error(e.getMessage())); return ExitCode.LOCAL_ENVIRONMENTAL_ERROR; } catch (CommandException | ExecException e) { env.getReporter().handle(Event.error(e.getMessage())); return ExitCode.RUN_FAILURE; } catch (InterruptedException e) { env.getReporter().handle(Event.error("clean interrupted")); return ExitCode.INTERRUPTED; } }
/** * The crux of the build system. Builds the targets specified in the request using the specified * Executor. * * <p>Performs loading, analysis and execution for the specified set of targets, honoring the * configuration options in the BuildRequest. Returns normally iff successful, throws an exception * otherwise. * * <p>The caller is responsible for setting up and syncing the package cache. * * <p>During this function's execution, the actualTargets and successfulTargets fields of the * request object are set. * * @param request the build request that this build tool is servicing, which specifies various * options; during this method's execution, the actualTargets and successfulTargets fields of * the request object are populated * @param validator target validator * @return the result as a {@link BuildResult} object */ public BuildResult processRequest(BuildRequest request, TargetValidator validator) { BuildResult result = new BuildResult(request.getStartTime()); runtime.getEventBus().register(result); Throwable catastrophe = null; ExitCode exitCode = ExitCode.BLAZE_INTERNAL_ERROR; try { buildTargets(request, result, validator); exitCode = ExitCode.SUCCESS; } catch (BuildFailedException e) { if (e.isErrorAlreadyShown()) { // The actual error has already been reported by the Builder. } else { reportExceptionError(e); } if (e.isCatastrophic()) { result.setCatastrophe(); } exitCode = ExitCode.BUILD_FAILURE; } catch (InterruptedException e) { exitCode = ExitCode.INTERRUPTED; getReporter().handle(Event.error("build interrupted")); getEventBus().post(new BuildInterruptedEvent()); } catch (TargetParsingException | LoadingFailedException | ViewCreationFailedException e) { exitCode = ExitCode.PARSING_FAILURE; reportExceptionError(e); } catch (TestExecException e) { // ExitCode.SUCCESS means that build was successful. Real return code of program // is going to be calculated in TestCommand.doTest(). exitCode = ExitCode.SUCCESS; reportExceptionError(e); } catch (InvalidConfigurationException e) { exitCode = ExitCode.COMMAND_LINE_ERROR; reportExceptionError(e); } catch (AbruptExitException e) { exitCode = e.getExitCode(); reportExceptionError(e); result.setCatastrophe(); } catch (Throwable throwable) { catastrophe = throwable; Throwables.propagate(throwable); } finally { stopRequest(request, result, catastrophe, exitCode); } return result; }
private EventBus getEventBus() { return runtime.getEventBus(); }
private Reporter getReporter() { return runtime.getReporter(); }
public BuildView getView() { return runtime.getView(); }
/** * The crux of the build system. Builds the targets specified in the request using the specified * Executor. * * <p>Performs loading, analysis and execution for the specified set of targets, honoring the * configuration options in the BuildRequest. Returns normally iff successful, throws an exception * otherwise. * * <p>Callers must ensure that {@link #stopRequest} is called after this method, even if it * throws. * * <p>The caller is responsible for setting up and syncing the package cache. * * <p>During this function's execution, the actualTargets and successfulTargets fields of the * request object are set. * * @param request the build request that this build tool is servicing, which specifies various * options; during this method's execution, the actualTargets and successfulTargets fields of * the request object are populated * @param result the build result that is the mutable result of this build * @param validator target validator */ public void buildTargets(BuildRequest request, BuildResult result, TargetValidator validator) throws BuildFailedException, LocalEnvironmentException, InterruptedException, ViewCreationFailedException, TargetParsingException, LoadingFailedException, ExecutorInitException, AbruptExitException, InvalidConfigurationException, TestExecException { validateOptions(request); BuildOptions buildOptions = runtime.createBuildOptions(request); // Sync the package manager before sending the BuildStartingEvent in runLoadingPhase() runtime.setupPackageCache( request.getPackageCacheOptions(), DefaultsPackage.getDefaultsPackageContent(buildOptions)); ExecutionTool executionTool = null; LoadingResult loadingResult = null; BuildConfigurationCollection configurations = null; try { getEventBus().post(new BuildStartingEvent(runtime.getOutputFileSystem(), request)); LOG.info("Build identifier: " + request.getId()); executionTool = new ExecutionTool(runtime, request); if (needsExecutionPhase(request.getBuildOptions())) { // Initialize the execution tool early if we need it. This hides the latency of setting up // the execution backends. executionTool.init(); } // Loading phase. loadingResult = runLoadingPhase(request, validator); // Create the build configurations. if (!request.getMultiCpus().isEmpty()) { getReporter() .handle( Event.warn( "The --experimental_multi_cpu option is _very_ experimental and only intended for " + "internal testing at this time. If you do not work on the build tool, then you " + "should stop now!")); if (!"build".equals(request.getCommandName()) && !"test".equals(request.getCommandName())) { throw new InvalidConfigurationException( "The experimental setting to select multiple CPUs is only supported for 'build' and " + "'test' right now!"); } } configurations = getConfigurations( buildOptions, request.getMultiCpus(), request.getViewOptions().keepGoing); getEventBus().post(new ConfigurationsCreatedEvent(configurations)); runtime.throwPendingException(); if (configurations.getTargetConfigurations().size() == 1) { // TODO(bazel-team): This is not optimal - we retain backwards compatibility in the case // where there's only a single configuration, but we don't send an event in the multi-config // case. Can we do better? [multi-config] getEventBus() .post( new MakeEnvironmentEvent( configurations.getTargetConfigurations().get(0).getMakeEnvironment())); } LOG.info("Configurations created"); // Analysis phase. AnalysisResult analysisResult = runAnalysisPhase(request, loadingResult, configurations); result.setActualTargets(analysisResult.getTargetsToBuild()); result.setTestTargets(analysisResult.getTargetsToTest()); checkTargetEnvironmentRestrictions( analysisResult.getTargetsToBuild(), runtime.getPackageManager()); reportTargets(analysisResult); // Execution phase. if (needsExecutionPhase(request.getBuildOptions())) { runtime.getSkyframeExecutor().injectTopLevelContext(request.getTopLevelArtifactContext()); executionTool.executeBuild( request.getId(), analysisResult, result, runtime.getSkyframeExecutor(), configurations, transformPackageRoots(loadingResult.getPackageRoots())); } String delayedErrorMsg = analysisResult.getError(); if (delayedErrorMsg != null) { throw new BuildFailedException(delayedErrorMsg); } } catch (RuntimeException e) { // Print an error message for unchecked runtime exceptions. This does not concern Error // subclasses such as OutOfMemoryError. request .getOutErr() .printErrLn("Unhandled exception thrown during build; message: " + e.getMessage()); throw e; } finally { // Delete dirty nodes to ensure that they do not accumulate indefinitely. long versionWindow = request.getViewOptions().versionWindowForDirtyNodeGc; if (versionWindow != -1) { runtime.getSkyframeExecutor().deleteOldNodes(versionWindow); } if (executionTool != null) { executionTool.shutdown(); } // The workspace status actions will not run with certain flags, or if an error // occurs early in the build. Tell a lie so that the event is not missing. // If multiple build_info events are sent, only the first is kept, so this does not harm // successful runs (which use the workspace status action). getEventBus() .post( new BuildInfoEvent( runtime.getworkspaceStatusActionFactory().createDummyWorkspaceStatus())); } if (loadingResult != null && loadingResult.hasTargetPatternError()) { throw new BuildFailedException( "execution phase successful, but there were errors " + "parsing the target pattern"); } }
private void actuallyClean( CommandEnvironment env, Path outputBase, Options cleanOptions, String symlinkPrefix) throws IOException, ShutdownBlazeServerException, CommandException, ExecException, InterruptedException { BlazeRuntime runtime = env.getRuntime(); if (env.getOutputService() != null) { env.getOutputService().clean(); } if (cleanOptions.expunge) { LOG.info("Expunging..."); // Delete the big subdirectories with the important content first--this // will take the most time. Then quickly delete the little locks, logs // and links right before we exit. Once the lock file is gone there will // be a small possibility of a server race if a client is waiting, but // all significant files will be gone by then. FileSystemUtils.deleteTreesBelow(outputBase); FileSystemUtils.deleteTree(outputBase); } else if (cleanOptions.expunge_async) { LOG.info("Expunging asynchronously..."); String tempBaseName = outputBase.getBaseName() + "_tmp_" + ProcessUtils.getpid(); // Keeping tempOutputBase in the same directory ensures it remains in the // same file system, and therefore the mv will be atomic and fast. Path tempOutputBase = outputBase.getParentDirectory().getChild(tempBaseName); outputBase.renameTo(tempOutputBase); env.getReporter() .handle(Event.info(null, "Output base moved to " + tempOutputBase + " for deletion")); // Daemonize the shell and use the double-fork idiom to ensure that the shell // exits even while the "rm -rf" command continues. String command = String.format( "exec >&- 2>&- <&- && (/usr/bin/setsid /bin/rm -rf %s &)&", ShellEscaper.escapeString(tempOutputBase.getPathString())); LOG.info("Executing shell commmand " + ShellEscaper.escapeString(command)); // Doesn't throw iff command exited and was successful. new CommandBuilder() .addArg(command) .useShell(true) .setWorkingDir(tempOutputBase.getParentDirectory()) .build() .execute(); } else { LOG.info("Output cleaning..."); runtime.clearCaches(); // In order to be sure that we delete everything, delete the workspace directory both for // --deep_execroot and for --nodeep_execroot. for (String directory : new String[] {runtime.getWorkspaceName(), "execroot/" + runtime.getWorkspaceName()}) { Path child = outputBase.getRelative(directory); if (child.exists()) { LOG.finest("Cleaning " + child); FileSystemUtils.deleteTreesBelow(child); } } } // remove convenience links OutputDirectoryLinksUtils.removeOutputDirectoryLinks( runtime.getWorkspaceName(), runtime.getWorkspace(), env.getReporter(), symlinkPrefix); // shutdown on expunge cleans if (cleanOptions.expunge || cleanOptions.expunge_async) { throw new ShutdownBlazeServerException(0); } }