| // Copyright 2014 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| package com.google.devtools.build.lib.buildtool; |
| |
| import static com.google.common.collect.ImmutableMap.toImmutableMap; |
| |
| import com.github.benmanes.caffeine.cache.Caffeine; |
| import com.github.benmanes.caffeine.cache.LoadingCache; |
| import com.google.common.base.Optional; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.analysis.AnalysisOptions; |
| import com.google.devtools.build.lib.analysis.AspectCollection; |
| import com.google.devtools.build.lib.analysis.OutputGroupInfo; |
| import com.google.devtools.build.lib.analysis.TopLevelArtifactContext; |
| import com.google.devtools.build.lib.analysis.ViewCreationFailedException; |
| import com.google.devtools.build.lib.analysis.config.BuildOptions; |
| import com.google.devtools.build.lib.buildeventstream.BuildEventProtocolOptions; |
| import com.google.devtools.build.lib.exec.ExecutionOptions; |
| import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions; |
| import com.google.devtools.build.lib.pkgcache.LoadingOptions; |
| import com.google.devtools.build.lib.pkgcache.PackageOptions; |
| import com.google.devtools.build.lib.runtime.KeepGoingOption; |
| import com.google.devtools.build.lib.runtime.LoadingPhaseThreadsOption; |
| import com.google.devtools.build.lib.runtime.UiOptions; |
| import com.google.devtools.build.lib.server.FailureDetails.Analysis; |
| import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; |
| import com.google.devtools.build.lib.util.OptionsUtils; |
| import com.google.devtools.build.lib.util.io.OutErr; |
| import com.google.devtools.common.options.OptionsBase; |
| import com.google.devtools.common.options.OptionsParsingResult; |
| import com.google.devtools.common.options.OptionsProvider; |
| import com.google.devtools.common.options.ParsedOptionDescription; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.UUID; |
| import java.util.function.Predicate; |
| import javax.annotation.Nullable; |
| |
| /** |
| * A BuildRequest represents a single invocation of the build tool by a user. A request specifies a |
| * list of targets to be built for a single configuration, a pair of output/error streams, and |
| * additional options such as --keep_going, --jobs, etc. |
| */ |
| public class BuildRequest implements OptionsProvider { |
| |
| private static final ImmutableList<Class<? extends OptionsBase>> MANDATORY_OPTIONS = |
| ImmutableList.of( |
| BuildRequestOptions.class, |
| PackageOptions.class, |
| BuildLanguageOptions.class, |
| LoadingOptions.class, |
| AnalysisOptions.class, |
| ExecutionOptions.class, |
| KeepGoingOption.class, |
| LoadingPhaseThreadsOption.class); |
| |
| /** Returns a new Builder instance. */ |
| public static Builder builder() { |
| return new Builder(); |
| } |
| |
| /** A Builder class to help create instances of BuildRequest. */ |
| public static final class Builder { |
| private UUID id; |
| private OptionsParsingResult options; |
| private OptionsParsingResult startupOptions; |
| private String commandName; |
| private OutErr outErr; |
| private List<String> targets; |
| private long startTimeMillis; // milliseconds since UNIX epoch. |
| private boolean needsInstrumentationFilter; |
| private boolean runTests; |
| private boolean checkForActionConflicts = true; |
| private boolean reportIncompatibleTargets = true; |
| |
| private Builder() {} |
| |
| @CanIgnoreReturnValue |
| public Builder setId(UUID id) { |
| this.id = id; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setOptions(OptionsParsingResult options) { |
| this.options = options; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setStartupOptions(OptionsParsingResult startupOptions) { |
| this.startupOptions = startupOptions; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setCommandName(String commandName) { |
| this.commandName = commandName; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setOutErr(OutErr outErr) { |
| this.outErr = outErr; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setTargets(List<String> targets) { |
| this.targets = targets; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setStartTimeMillis(long startTimeMillis) { |
| this.startTimeMillis = startTimeMillis; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setNeedsInstrumentationFilter(boolean needsInstrumentationFilter) { |
| this.needsInstrumentationFilter = needsInstrumentationFilter; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setRunTests(boolean runTests) { |
| this.runTests = runTests; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setCheckforActionConflicts(boolean checkForActionConflicts) { |
| this.checkForActionConflicts = checkForActionConflicts; |
| return this; |
| } |
| |
| /** |
| * If true, build status depends on whether or not requested targets are platform-compatible |
| * ({@link com.google.devtools.build.lib.analysis.IncompatiblePlatformProvider}). If false, this |
| * doesn't matter. |
| * |
| * <p>This should be true for builds (where users care if their targets produce meaningful |
| * output) and false for queries (where users want to understand target relationships or |
| * diagnose why incompatible targets are incompatible). |
| */ |
| @CanIgnoreReturnValue |
| public Builder setReportIncompatibleTargets(boolean report) { |
| this.reportIncompatibleTargets = report; |
| return this; |
| } |
| |
| public BuildRequest build() { |
| return new BuildRequest( |
| commandName, |
| options, |
| startupOptions, |
| targets, |
| outErr, |
| id, |
| startTimeMillis, |
| needsInstrumentationFilter, |
| runTests, |
| checkForActionConflicts, |
| reportIncompatibleTargets); |
| } |
| } |
| |
| private final UUID id; |
| private final LoadingCache<Class<? extends OptionsBase>, Optional<OptionsBase>> optionsCache; |
| private final Map<String, Object> starlarkOptions; |
| |
| /** A human-readable description of all the non-default option settings. */ |
| private final String optionsDescription; |
| |
| /** The name of the Blaze command that the user invoked. Used for --announce. */ |
| private final String commandName; |
| |
| private final OutErr outErr; |
| private final List<String> targets; |
| |
| private final long startTimeMillis; // milliseconds since UNIX epoch. |
| |
| private final boolean needsInstrumentationFilter; |
| private final boolean runningInEmacs; |
| private final boolean runTests; |
| private final boolean checkForActionConflicts; |
| private final boolean reportIncompatibleTargets; |
| private final ImmutableSet<String> userOptions; |
| |
| private BuildRequest( |
| String commandName, |
| final OptionsParsingResult options, |
| final OptionsParsingResult startupOptions, |
| List<String> targets, |
| OutErr outErr, |
| UUID id, |
| long startTimeMillis, |
| boolean needsInstrumentationFilter, |
| boolean runTests, |
| boolean checkForActionConflicts, |
| boolean reportIncompatibleTargets) { |
| this.commandName = commandName; |
| this.optionsDescription = OptionsUtils.asShellEscapedString(options); |
| this.outErr = outErr; |
| this.targets = targets; |
| this.id = id; |
| this.startTimeMillis = startTimeMillis; |
| this.userOptions = |
| options.getUserOptions() == null ? ImmutableSet.of() : options.getUserOptions(); |
| this.optionsCache = |
| Caffeine.newBuilder() |
| .build( |
| key -> { |
| OptionsBase result = options.getOptions(key); |
| if (result == null && startupOptions != null) { |
| result = startupOptions.getOptions(key); |
| } |
| |
| return Optional.fromNullable(result); |
| }); |
| this.starlarkOptions = options.getStarlarkOptions(); |
| this.needsInstrumentationFilter = needsInstrumentationFilter; |
| this.runTests = runTests; |
| this.checkForActionConflicts = checkForActionConflicts; |
| this.reportIncompatibleTargets = reportIncompatibleTargets; |
| |
| for (Class<? extends OptionsBase> optionsClass : MANDATORY_OPTIONS) { |
| Preconditions.checkNotNull(getOptions(optionsClass)); |
| } |
| |
| // All this, just to pass a global boolean from the client to the server. :( |
| this.runningInEmacs = options.getOptions(UiOptions.class).runningInEmacs; |
| } |
| |
| /** |
| * Return whether this BuildRequest contains multiple top-level configs |
| * |
| * <p>Note: The ability to have a multi-top-level-config build is currently completely disabled. |
| * However, certain parts of the infra would fail horribly if it was ever enabled at all so |
| * keeping this flag for those parts to check as a sort of mild future-proofing. |
| */ |
| public boolean isMultiConfigBuild() { |
| return false; |
| } |
| |
| /** |
| * Since the OptionsProvider interface is used by many teams, this method is String-keyed even |
| * though it should always contain labels for our purposes. Consumers of this method should |
| * probably use the {@link BuildOptions#labelizeStarlarkOptions} method before doing meaningful |
| * work with the results. |
| */ |
| @Override |
| public Map<String, Object> getStarlarkOptions() { |
| return starlarkOptions; |
| } |
| |
| @Override |
| public Map<String, Object> getExplicitStarlarkOptions( |
| Predicate<? super ParsedOptionDescription> filter) { |
| throw new UnsupportedOperationException("No known callers to this implementation"); |
| } |
| |
| /** |
| * Returns the list of options that were parsed from either a user blazerc file or the command |
| * line. |
| */ |
| @Override |
| public ImmutableSet<String> getUserOptions() { |
| return userOptions; |
| } |
| |
| /** Returns a unique identifier that universally identifies this build. */ |
| public UUID getId() { |
| return id; |
| } |
| |
| /** Returns the name of the Blaze command that the user invoked. */ |
| public String getCommandName() { |
| return commandName; |
| } |
| |
| boolean isRunningInEmacs() { |
| return runningInEmacs; |
| } |
| |
| /** Returns true if tests should be run by the build tool. */ |
| public boolean shouldRunTests() { |
| return runTests; |
| } |
| |
| /** Returns the (immutable) list of targets to build in commandline form. */ |
| public List<String> getTargets() { |
| return targets; |
| } |
| |
| /** |
| * Returns the output/error streams to which errors and progress messages should be sent during |
| * the fulfillment of this request. |
| */ |
| public OutErr getOutErr() { |
| return outErr; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T extends OptionsBase> T getOptions(Class<T> clazz) { |
| return (T) optionsCache.get(clazz).orNull(); |
| } |
| |
| /** Returns the set of command-line options specified for this request. */ |
| public BuildRequestOptions getBuildOptions() { |
| return getOptions(BuildRequestOptions.class); |
| } |
| |
| /** Returns the set of options related to the loading phase. */ |
| public PackageOptions getPackageOptions() { |
| return getOptions(PackageOptions.class); |
| } |
| |
| /** Returns the set of options related to the loading phase. */ |
| public LoadingOptions getLoadingOptions() { |
| return getOptions(LoadingOptions.class); |
| } |
| |
| /** Returns the set of command-line options related to the view specified for this request. */ |
| public AnalysisOptions getViewOptions() { |
| return getOptions(AnalysisOptions.class); |
| } |
| |
| /** Returns the value of the --keep_going option. */ |
| public boolean getKeepGoing() { |
| return getOptions(KeepGoingOption.class).keepGoing; |
| } |
| |
| /** Returns the value of the --loading_phase_threads option. */ |
| int getLoadingPhaseThreadCount() { |
| return getOptions(LoadingPhaseThreadsOption.class).threads; |
| } |
| |
| /** Returns the set of execution options specified for this request. */ |
| public ExecutionOptions getExecutionOptions() { |
| return getOptions(ExecutionOptions.class); |
| } |
| |
| /** Returns the human-readable description of the non-default options for this build request. */ |
| public String getOptionsDescription() { |
| return optionsDescription; |
| } |
| |
| /** |
| * Return the time (according to System.currentTimeMillis()) at which the service of this request |
| * was started. |
| */ |
| public long getStartTime() { |
| return startTimeMillis; |
| } |
| |
| public boolean needsInstrumentationFilter() { |
| return needsInstrumentationFilter; |
| } |
| |
| /** |
| * Validates the options for this BuildRequest. |
| * |
| * <p>Issues warnings or throws {@code InvalidConfigurationException} for option settings that |
| * conflict. |
| * |
| * @return list of warnings |
| */ |
| public List<String> validateOptions() { |
| List<String> warnings = new ArrayList<>(); |
| |
| int localTestJobs = getExecutionOptions().localTestJobs; |
| int jobs = getBuildOptions().jobs; |
| if (localTestJobs > jobs) { |
| warnings.add( |
| String.format( |
| "High value for --local_test_jobs: %d. This exceeds the value for --jobs: " |
| + "%d. Only up to %d local tests will run concurrently.", |
| localTestJobs, jobs, jobs)); |
| } |
| |
| // Validate other BuildRequest options. |
| if (getBuildOptions().verboseExplanations && getBuildOptions().explanationPath == null) { |
| warnings.add("--verbose_explanations has no effect when --explain=<file> is not enabled"); |
| } |
| |
| return warnings; |
| } |
| |
| /** Creates a new TopLevelArtifactContext from this build request. */ |
| public TopLevelArtifactContext getTopLevelArtifactContext() { |
| BuildRequestOptions buildOptions = getBuildOptions(); |
| return new TopLevelArtifactContext( |
| getOptions(ExecutionOptions.class).testStrategy.equals("exclusive"), |
| getOptions(BuildEventProtocolOptions.class).expandFilesets, |
| getOptions(BuildEventProtocolOptions.class).fullyResolveFilesetSymlinks, |
| OutputGroupInfo.determineOutputGroups( |
| buildOptions.outputGroups, validationMode(), /* shouldRunTests= */ shouldRunTests())); |
| } |
| |
| public ImmutableList<String> getAspects() { |
| List<String> aspects = getBuildOptions().aspects; |
| ImmutableList.Builder<String> result = ImmutableList.<String>builder().addAll(aspects); |
| if (!aspects.contains(AspectCollection.VALIDATION_ASPECT_NAME) && useValidationAspect()) { |
| result.add(AspectCollection.VALIDATION_ASPECT_NAME); |
| } |
| return result.build(); |
| } |
| |
| @Nullable |
| public ImmutableMap<String, String> getAspectsParameters() throws ViewCreationFailedException { |
| List<Map.Entry<String, String>> aspectsParametersList = getBuildOptions().aspectsParameters; |
| try { |
| return aspectsParametersList.stream() |
| .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); |
| } catch (IllegalArgumentException e) { |
| String errorMessage = "Error in top-level aspects parameters"; |
| throw new ViewCreationFailedException( |
| errorMessage, |
| FailureDetail.newBuilder() |
| .setMessage(errorMessage) |
| .setAnalysis(Analysis.newBuilder().setCode(Analysis.Code.ASPECT_CREATION_FAILED)) |
| .build(), |
| e); |
| } |
| } |
| |
| /** Whether {@value AspectCollection#VALIDATION_ASPECT_NAME} is in use. */ |
| public boolean useValidationAspect() { |
| return validationMode() == OutputGroupInfo.ValidationMode.ASPECT; |
| } |
| |
| private OutputGroupInfo.ValidationMode validationMode() { |
| BuildRequestOptions buildOptions = getBuildOptions(); |
| if (!buildOptions.runValidationActions) { |
| return OutputGroupInfo.ValidationMode.OFF; |
| } |
| return buildOptions.useValidationAspect |
| ? OutputGroupInfo.ValidationMode.ASPECT |
| : OutputGroupInfo.ValidationMode.OUTPUT_GROUP; |
| } |
| |
| public boolean getCheckForActionConflicts() { |
| return checkForActionConflicts; |
| } |
| |
| public boolean reportIncompatibleTargets() { |
| return reportIncompatibleTargets; |
| } |
| } |