| // 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 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.ImmutableSortedSet; |
| import com.google.devtools.build.lib.analysis.AnalysisOptions; |
| import com.google.devtools.build.lib.analysis.OutputGroupInfo; |
| import com.google.devtools.build.lib.analysis.TopLevelArtifactContext; |
| 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.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 java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.UUID; |
| |
| /** |
| * 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 { |
| public static final String VALIDATION_ASPECT_NAME = "ValidateTarget"; |
| |
| 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 Builder() {} |
| |
| public Builder setId(UUID id) { |
| this.id = id; |
| return this; |
| } |
| |
| public Builder setOptions(OptionsParsingResult options) { |
| this.options = options; |
| return this; |
| } |
| |
| public Builder setStartupOptions(OptionsParsingResult startupOptions) { |
| this.startupOptions = startupOptions; |
| return this; |
| } |
| |
| public Builder setCommandName(String commandName) { |
| this.commandName = commandName; |
| return this; |
| } |
| |
| public Builder setOutErr(OutErr outErr) { |
| this.outErr = outErr; |
| return this; |
| } |
| |
| public Builder setTargets(List<String> targets) { |
| this.targets = targets; |
| return this; |
| } |
| |
| public Builder setStartTimeMillis(long startTimeMillis) { |
| this.startTimeMillis = startTimeMillis; |
| return this; |
| } |
| |
| public Builder setNeedsInstrumentationFilter(boolean needsInstrumentationFilter) { |
| this.needsInstrumentationFilter = needsInstrumentationFilter; |
| return this; |
| } |
| |
| public Builder setRunTests(boolean runTests) { |
| this.runTests = runTests; |
| return this; |
| } |
| |
| public Builder setCheckforActionConflicts(boolean checkForActionConflicts) { |
| this.checkForActionConflicts = checkForActionConflicts; |
| return this; |
| } |
| |
| public BuildRequest build() { |
| return new BuildRequest( |
| commandName, |
| options, |
| startupOptions, |
| targets, |
| outErr, |
| id, |
| startTimeMillis, |
| needsInstrumentationFilter, |
| runTests, |
| checkForActionConflicts); |
| } |
| } |
| |
| 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 BuildRequest( |
| String commandName, |
| final OptionsParsingResult options, |
| final OptionsParsingResult startupOptions, |
| List<String> targets, |
| OutErr outErr, |
| UUID id, |
| long startTimeMillis, |
| boolean needsInstrumentationFilter, |
| boolean runTests, |
| boolean checkForActionConflicts) { |
| this.commandName = commandName; |
| this.optionsDescription = OptionsUtils.asShellEscapedString(options); |
| this.outErr = outErr; |
| this.targets = targets; |
| this.id = id; |
| this.startTimeMillis = startTimeMillis; |
| 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; |
| |
| 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; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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 ImmutableSortedSet<String> getMultiCpus() { |
| return ImmutableSortedSet.copyOf(getBuildOptions().multiCpus); |
| } |
| |
| public ImmutableList<String> getAspects() { |
| ImmutableList.Builder<String> result = |
| ImmutableList.<String>builder().addAll(getBuildOptions().aspects); |
| if (useValidationAspect()) { |
| result.add(VALIDATION_ASPECT_NAME); |
| } |
| return result.build(); |
| } |
| |
| /** Whether {@value #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; |
| } |
| } |