| // 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.google.common.base.Optional; | |
| import com.google.common.cache.CacheBuilder; | |
| import com.google.common.cache.CacheLoader; | |
| import com.google.common.cache.LoadingCache; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableSortedSet; | |
| import com.google.devtools.build.lib.analysis.BuildView; | |
| import com.google.devtools.build.lib.analysis.OutputGroupProvider; | |
| import com.google.devtools.build.lib.analysis.TopLevelArtifactContext; | |
| import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; | |
| import com.google.devtools.build.lib.exec.ExecutionOptions; | |
| import com.google.devtools.build.lib.pkgcache.LoadingOptions; | |
| import com.google.devtools.build.lib.pkgcache.PackageCacheOptions; | |
| import com.google.devtools.build.lib.runtime.BlazeCommandEventHandler; | |
| import com.google.devtools.build.lib.util.OptionsUtils; | |
| import com.google.devtools.build.lib.util.Preconditions; | |
| import com.google.devtools.build.lib.util.io.OutErr; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import com.google.devtools.common.options.Converters; | |
| import com.google.devtools.common.options.Converters.RangeConverter; | |
| import com.google.devtools.common.options.Option; | |
| import com.google.devtools.common.options.OptionsBase; | |
| import com.google.devtools.common.options.OptionsClassProvider; | |
| import com.google.devtools.common.options.OptionsProvider; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import java.util.UUID; | |
| import java.util.concurrent.ExecutionException; | |
| import java.util.regex.Pattern; | |
| /** | |
| * 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 OptionsClassProvider { | |
| /** | |
| * Options interface--can be used to parse command-line arguments. | |
| * | |
| * <p>See also ExecutionOptions; from the user's point of view, there's no | |
| * qualitative difference between these two sets of options. | |
| */ | |
| public static class BuildRequestOptions extends OptionsBase { | |
| /* "Execution": options related to the execution of a build: */ | |
| @Option(name = "jobs", | |
| abbrev = 'j', | |
| defaultValue = "200", | |
| category = "strategy", | |
| help = "The number of concurrent jobs to run. " | |
| + "0 means build sequentially. Values above " + MAX_JOBS | |
| + " are not allowed, and values above " | |
| + JOBS_TOO_HIGH_WARNING + " may cause memory issues.") | |
| public int jobs; | |
| @Option(name = "progress_report_interval", | |
| defaultValue = "0", | |
| category = "verbosity", | |
| converter = ProgressReportIntervalConverter.class, | |
| help = "The number of seconds to wait between two reports on" | |
| + " still running jobs. The default value 0 means to use" | |
| + " the default 10:30:60 incremental algorithm.") | |
| public int progressReportInterval; | |
| @Option(name = "explain", | |
| defaultValue = "null", | |
| category = "verbosity", | |
| converter = OptionsUtils.PathFragmentConverter.class, | |
| help = "Causes the build system to explain each executed step of the " | |
| + "build. The explanation is written to the specified log file.") | |
| public PathFragment explanationPath; | |
| @Option(name = "verbose_explanations", | |
| defaultValue = "false", | |
| category = "verbosity", | |
| help = "Increases the verbosity of the explanations issued if --explain is enabled. " | |
| + "Has no effect if --explain is not enabled.") | |
| public boolean verboseExplanations; | |
| @Option(name = "output_filter", | |
| converter = Converters.RegexPatternConverter.class, | |
| defaultValue = "null", | |
| category = "flags", | |
| help = "Only shows warnings for rules with a name matching the provided regular " | |
| + "expression.") | |
| public Pattern outputFilter; | |
| @Deprecated | |
| @Option(name = "dump_makefile", | |
| defaultValue = "false", | |
| category = "undocumented", | |
| help = "this flag has no effect.") | |
| public boolean dumpMakefile; | |
| @Deprecated | |
| @Option(name = "dump_action_graph", | |
| defaultValue = "false", | |
| category = "undocumented", | |
| help = "this flag has no effect.") | |
| public boolean dumpActionGraph; | |
| @Deprecated | |
| @Option(name = "dump_action_graph_for_package", | |
| allowMultiple = true, | |
| defaultValue = "", | |
| category = "undocumented", | |
| help = "this flag has no effect.") | |
| public List<String> dumpActionGraphForPackage = new ArrayList<>(); | |
| @Deprecated | |
| @Option(name = "dump_action_graph_with_middlemen", | |
| defaultValue = "true", | |
| category = "undocumented", | |
| help = "this flag has no effect.") | |
| public boolean dumpActionGraphWithMiddlemen; | |
| @Deprecated | |
| @Option(name = "dump_providers", | |
| defaultValue = "false", | |
| category = "undocumented", | |
| help = "This is a no-op.") | |
| public boolean dumpProviders; | |
| @Deprecated | |
| @Option(name = "dump_targets", | |
| defaultValue = "null", | |
| category = "undocumented", | |
| help = "this flag has no effect.") | |
| public String dumpTargets; | |
| @Deprecated | |
| @Option(name = "dump_host_deps", | |
| defaultValue = "true", | |
| category = "undocumented", | |
| help = "Deprecated") | |
| public boolean dumpHostDeps; | |
| @Deprecated | |
| @Option(name = "dump_to_stdout", | |
| defaultValue = "false", | |
| category = "undocumented", | |
| help = "Deprecated") | |
| public boolean dumpToStdout; | |
| @Option(name = "analyze", | |
| defaultValue = "true", | |
| category = "undocumented", | |
| help = "Execute the analysis phase; this is the usual behaviour. " | |
| + "Specifying --noanalyze causes the build to stop before starting the " | |
| + "analysis phase, returning zero iff the package loading completed " | |
| + "successfully; this mode is useful for testing.") | |
| public boolean performAnalysisPhase; | |
| @Option(name = "build", | |
| defaultValue = "true", | |
| category = "what", | |
| help = "Execute the build; this is the usual behaviour. " | |
| + "Specifying --nobuild causes the build to stop before executing the " | |
| + "build actions, returning zero iff the package loading and analysis " | |
| + "phases completed successfully; this mode is useful for testing " | |
| + "those phases.") | |
| public boolean performExecutionPhase; | |
| @Option(name = "output_groups", | |
| converter = Converters.CommaSeparatedOptionListConverter.class, | |
| allowMultiple = true, | |
| defaultValue = "", | |
| category = "undocumented", | |
| help = "Specifies which output groups of the top-level targets to build. " | |
| + "If omitted, a default set of output groups are built." | |
| + "When specified the default set is overridden." | |
| + "However you may use --output_groups=+<output_group> " | |
| + "or --output_groups=-<output_group> " | |
| + "to instead modify the set of output groups.") | |
| public List<String> outputGroups; | |
| @Option(name = "show_result", | |
| defaultValue = "1", | |
| category = "verbosity", | |
| help = "Show the results of the build. For each " | |
| + "target, state whether or not it was brought up-to-date, and if " | |
| + "so, a list of output files that were built. The printed files " | |
| + "are convenient strings for copy+pasting to the shell, to " | |
| + "execute them.\n" | |
| + "This option requires an integer argument, which " | |
| + "is the threshold number of targets above which result " | |
| + "information is not printed. " | |
| + "Thus zero causes suppression of the message and MAX_INT " | |
| + "causes printing of the result to occur always. The default is one.") | |
| public int maxResultTargets; | |
| @Option(name = "experimental_show_artifacts", | |
| defaultValue = "false", | |
| category = "undocumented", | |
| help = "Output a list of all top level artifacts produced by this build." | |
| + "Use output format suitable for tool consumption. " | |
| + "This flag is temporary and intended to facilitate Android Studio integration. " | |
| + "This output format will likely change in the future or disappear completely." | |
| ) | |
| public boolean showArtifacts; | |
| @Option(name = "announce", | |
| defaultValue = "false", | |
| category = "verbosity", | |
| help = "Deprecated. No-op.", | |
| deprecationWarning = "This option is now deprecated and is a no-op") | |
| public boolean announce; | |
| @Option(name = "symlink_prefix", | |
| defaultValue = "null", | |
| category = "misc", | |
| help = "The prefix that is prepended to any of the convenience symlinks that are created " | |
| + "after a build. If '/' is passed, then no symlinks are created and no warning is " | |
| + "emitted. If omitted, the default value is the name of the build tool." | |
| ) | |
| public String symlinkPrefix; | |
| @Option(name = "experimental_multi_cpu", | |
| converter = Converters.CommaSeparatedOptionListConverter.class, | |
| allowMultiple = true, | |
| defaultValue = "", | |
| category = "semantics", | |
| help = "This flag allows specifying multiple target CPUs. If this is specified, " | |
| + "the --cpu option is ignored.") | |
| public List<String> multiCpus; | |
| @Option(name = "output_tree_tracking", | |
| oldName = "experimental_output_tree_tracking", | |
| defaultValue = "true", | |
| category = "undocumented", | |
| help = "If set, tell the output service (if any) to track when files in the output " | |
| + "tree have been modified externally (not by the build system). " | |
| + "This should improve incremental build speed when an appropriate output service " | |
| + "is enabled.") | |
| public boolean finalizeActions; | |
| @Option( | |
| name = "aspects", | |
| converter = Converters.CommaSeparatedOptionListConverter.class, | |
| defaultValue = "", | |
| category = "undocumented", // for now | |
| help = "List of top-level aspects" | |
| ) | |
| public List<String> aspects; | |
| public String getSymlinkPrefix(String productName) { | |
| return symlinkPrefix == null ? productName + "-" : symlinkPrefix; | |
| } | |
| } | |
| /** | |
| * Converter for progress_report_interval: [0, 3600]. | |
| */ | |
| public static class ProgressReportIntervalConverter extends RangeConverter { | |
| public ProgressReportIntervalConverter() { | |
| super(0, 3600); | |
| } | |
| } | |
| private static final int MAX_JOBS = 2000; | |
| private static final int JOBS_TOO_HIGH_WARNING = 1000; | |
| private final UUID id; | |
| private final LoadingCache<Class<? extends OptionsBase>, Optional<OptionsBase>> optionsCache; | |
| /** 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 long startTimeMillis = 0; // milliseconds since UNIX epoch. | |
| private boolean runningInEmacs = false; | |
| private boolean runTests = false; | |
| private static final List<Class<? extends OptionsBase>> MANDATORY_OPTIONS = ImmutableList.of( | |
| BuildRequestOptions.class, | |
| PackageCacheOptions.class, | |
| LoadingOptions.class, | |
| BuildView.Options.class, | |
| ExecutionOptions.class); | |
| private BuildRequest(String commandName, | |
| final OptionsProvider options, | |
| final OptionsProvider startupOptions, | |
| List<String> targets, | |
| OutErr outErr, | |
| UUID id, | |
| long startTimeMillis) { | |
| this.commandName = commandName; | |
| this.optionsDescription = OptionsUtils.asShellEscapedString(options); | |
| this.outErr = outErr; | |
| this.targets = targets; | |
| this.id = id; | |
| this.startTimeMillis = startTimeMillis; | |
| this.optionsCache = CacheBuilder.newBuilder() | |
| .build(new CacheLoader<Class<? extends OptionsBase>, Optional<OptionsBase>>() { | |
| @Override | |
| public Optional<OptionsBase> load(Class<? extends OptionsBase> key) throws Exception { | |
| OptionsBase result = options.getOptions(key); | |
| if (result == null && startupOptions != null) { | |
| result = startupOptions.getOptions(key); | |
| } | |
| return Optional.fromNullable(result); | |
| } | |
| }); | |
| for (Class<? extends OptionsBase> optionsClass : MANDATORY_OPTIONS) { | |
| Preconditions.checkNotNull(getOptions(optionsClass)); | |
| } | |
| } | |
| /** | |
| * 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; | |
| } | |
| /** | |
| * Set to true if this build request was initiated by Emacs. | |
| * (Certain output formatting may be necessary.) | |
| */ | |
| public void setRunningInEmacs() { | |
| runningInEmacs = true; | |
| } | |
| boolean isRunningInEmacs() { | |
| return runningInEmacs; | |
| } | |
| /** | |
| * Enables test execution for this build request. | |
| */ | |
| public void setRunTests() { | |
| runTests = true; | |
| } | |
| /** | |
| * 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) { | |
| try { | |
| return (T) optionsCache.get(clazz).orNull(); | |
| } catch (ExecutionException e) { | |
| throw new IllegalStateException(e); | |
| } | |
| } | |
| /** | |
| * 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 PackageCacheOptions getPackageCacheOptions() { | |
| return getOptions(PackageCacheOptions.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 BuildView.Options getViewOptions() { | |
| return getOptions(BuildView.Options.class); | |
| } | |
| /** | |
| * 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; | |
| } | |
| /** | |
| * 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() throws InvalidConfigurationException { | |
| List<String> warnings = new ArrayList<>(); | |
| // Validate "jobs". | |
| int jobs = getBuildOptions().jobs; | |
| if (jobs < 0 || jobs > MAX_JOBS) { | |
| throw new InvalidConfigurationException(String.format( | |
| "Invalid parameter for --jobs: %d. Only values 0 <= jobs <= %d are allowed.", jobs, | |
| MAX_JOBS)); | |
| } | |
| int localTestJobs = getExecutionOptions().localTestJobs; | |
| if (localTestJobs < 0) { | |
| throw new InvalidConfigurationException(String.format( | |
| "Invalid parameter for --local_test_jobs: %d. Only values 0 or greater are " | |
| + "allowed.", localTestJobs)); | |
| } | |
| 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() { | |
| return new TopLevelArtifactContext( | |
| getOptions(ExecutionOptions.class).testStrategy.equals("exclusive"), | |
| OutputGroupProvider.determineOutputGroups(getBuildOptions().outputGroups)); | |
| } | |
| public ImmutableSortedSet<String> getMultiCpus() { | |
| return ImmutableSortedSet.copyOf(getBuildOptions().multiCpus); | |
| } | |
| public ImmutableList<String> getAspects() { | |
| return ImmutableList.copyOf(getBuildOptions().aspects); | |
| } | |
| public static BuildRequest create(String commandName, OptionsProvider options, | |
| OptionsProvider startupOptions, | |
| List<String> targets, OutErr outErr, UUID commandId, long commandStartTime) { | |
| BuildRequest request = new BuildRequest(commandName, options, startupOptions, targets, outErr, | |
| commandId, commandStartTime); | |
| // All this, just to pass a global boolean from the client to the server. :( | |
| if (options.getOptions(BlazeCommandEventHandler.Options.class).runningInEmacs) { | |
| request.setRunningInEmacs(); | |
| } | |
| return request; | |
| } | |
| } |