// 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.pkgcache; import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.google.common.eventbus.EventBus; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.cmdline.ResolvedTargets; import com.google.devtools.build.lib.cmdline.TargetParsingException; import com.google.devtools.build.lib.events.DelegatingEventHandler; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.packages.NoSuchThingException; import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper; import com.google.devtools.build.lib.packages.Package; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.packages.TestSize; import com.google.devtools.build.lib.packages.TestTargetUtils; import com.google.devtools.build.lib.packages.TestTimeout; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.common.options.Converters.CommaSeparatedOptionListConverter; import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionsBase; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import javax.annotation.Nullable; /** * Implements the loading phase; responsible for: * * *

In order to ensure correctness of incremental loading and of full cache hits, this class is * very restrictive about access to its internal state and to its collaborators. In particular, none * of the collaborators of this class may change in incompatible ways, such as changing the relative * working directory for the target pattern parser, without notifying this class. * *

For full caching, this class tracks the exact values of all inputs to the loading phase. To * maximize caching, it is vital that these change as rarely as possible. */ public class LoadingPhaseRunner { /** * Loading phase options. */ public static class Options extends OptionsBase { @Option(name = "loading_phase_threads", defaultValue = "200", category = "undocumented", help = "Number of parallel threads to use for the loading phase.") public int loadingPhaseThreads; @Option(name = "build_tests_only", defaultValue = "false", category = "what", help = "If specified, only *_test and test_suite rules will be built " + "and other targets specified on the command line will be ignored. " + "By default everything that was requested will be built.") public boolean buildTestsOnly; @Option(name = "compile_one_dependency", defaultValue = "false", category = "what", help = "Compile a single dependency of the argument files. " + "This is useful for syntax checking source files in IDEs, " + "for example, by rebuilding a single target that depends on " + "the source file to detect errors as early as possible in the " + "edit/build/test cycle. This argument affects the way all " + "non-flag arguments are interpreted; instead of being targets " + "to build they are source filenames. For each source filename " + "an arbitrary target that depends on it will be built.") public boolean compileOneDependency; @Option(name = "test_tag_filters", converter = CommaSeparatedOptionListConverter.class, defaultValue = "", category = "what", help = "Specifies a comma-separated list of test tags. Each tag can be optionally " + "preceded with '-' to specify excluded tags. Only those test targets will be " + "found that contain at least one included tag and do not contain any excluded " + "tags. This option affects --build_tests_only behavior and the test command." ) public List testTagFilterList; @Option(name = "test_size_filters", converter = TestSize.TestSizeFilterConverter.class, defaultValue = "", category = "what", help = "Specifies a comma-separated list of test sizes. Each size can be optionally " + "preceded with '-' to specify excluded sizes. Only those test targets will be " + "found that contain at least one included size and do not contain any excluded " + "sizes. This option affects --build_tests_only behavior and the test command." ) public Set testSizeFilterSet; @Option(name = "test_timeout_filters", converter = TestTimeout.TestTimeoutFilterConverter.class, defaultValue = "", category = "what", help = "Specifies a comma-separated list of test timeouts. Each timeout can be " + "optionally preceded with '-' to specify excluded timeouts. Only those test " + "targets will be found that contain at least one included timeout and do not " + "contain any excluded timeouts. This option affects --build_tests_only behavior " + "and the test command." ) public Set testTimeoutFilterSet; @Option(name = "test_lang_filters", converter = CommaSeparatedOptionListConverter.class, defaultValue = "", category = "what", help = "Specifies a comma-separated list of test languages. Each language can be " + "optionally preceded with '-' to specify excluded languages. Only those " + "test targets will be found that are written in the specified languages. " + "The name used for each language should be the same as the language prefix in the " + "*_test rule, e.g. one of 'cc', 'java', 'py', etc." + "This option affects --build_tests_only behavior and the test command." ) public List testLangFilterList; } /** * A callback interface to notify the caller about specific events. * TODO(bazel-team): maybe we should use the EventBus instead? */ public interface Callback { /** * Called after the target patterns have been resolved to give the caller a chance to validate * the list before proceeding. */ void notifyTargets(Collection targets) throws LoadingFailedException; /** * Called after loading has finished, to notify the caller about the visited packages. * *

The set of visited packages is the set of packages in the transitive closure of the * union of the top level targets. */ void notifyVisitedPackages(Set visitedPackages); } /** * The result of the loading phase, i.e., whether there were errors, and which targets were * successfully loaded, plus some related metadata. */ public static final class LoadingResult { private final boolean hasTargetPatternError; private final boolean hasLoadingError; private final ImmutableSet targetsToAnalyze; private final ImmutableSet testsToRun; private final ImmutableMap packageRoots; public LoadingResult(boolean hasTargetPatternError, boolean hasLoadingError, Collection targetsToAnalyze, Collection testsToRun, ImmutableMap packageRoots) { this.hasTargetPatternError = hasTargetPatternError; this.hasLoadingError = hasLoadingError; this.targetsToAnalyze = targetsToAnalyze == null ? null : ImmutableSet.copyOf(targetsToAnalyze); this.testsToRun = testsToRun == null ? null : ImmutableSet.copyOf(testsToRun); this.packageRoots = packageRoots; } /** Whether there were errors during target pattern evaluation. */ public boolean hasTargetPatternError() { return hasTargetPatternError; } /** Whether there were errors during the loading phase. */ public boolean hasLoadingError() { return hasLoadingError; } /** Successfully loaded targets that should be built. */ public Collection getTargets() { return targetsToAnalyze; } /** Successfully loaded targets that should be run as tests. Must be a subset of the targets. */ public Collection getTestsToRun() { return testsToRun; } /** * The map from package names to the package root where each package was found; this is used to * set up the symlink tree. */ public ImmutableMap getPackageRoots() { return packageRoots; } } private static final class ParseFailureListenerImpl extends DelegatingEventHandler implements ParseFailureListener { private final EventBus eventBus; private ParseFailureListenerImpl(EventHandler delegate, EventBus eventBus) { super(delegate); this.eventBus = eventBus; } @Override public void parsingError(String targetPattern, String message) { if (eventBus != null) { eventBus.post(new ParsingFailedEvent(targetPattern, message)); } } } private static final Logger LOG = Logger.getLogger(LoadingPhaseRunner.class.getName()); private final PackageManager packageManager; private final TargetPatternEvaluator targetPatternEvaluator; private final Set ruleNames; private final TransitivePackageLoader pkgLoader; public LoadingPhaseRunner(PackageManager packageManager, Set ruleNames) { this.packageManager = packageManager; this.targetPatternEvaluator = packageManager.getTargetPatternEvaluator(); this.ruleNames = ruleNames; this.pkgLoader = packageManager.newTransitiveLoader(); } public TargetPatternEvaluator getTargetPatternEvaluator() { return targetPatternEvaluator; } public void updatePatternEvaluator(PathFragment relativeWorkingDirectory) { targetPatternEvaluator.updateOffset(relativeWorkingDirectory); } /** * This method only exists for the benefit of InfoCommand, which needs to construct * a {@code BuildConfigurationCollection} without running a full loading phase. Don't * add any more clients; instead, we should change info so that it doesn't need the configuration. */ public boolean loadForConfigurations(EventHandler eventHandler, Set

Note that this does not stop us from emitting "target X depends on deprecated target Y" * style warnings for the same target and it is a good thing; depending on a target and * wanting to build it are different things. */ private void maybeReportDeprecation(EventHandler eventHandler, Collection targets) { for (Rule rule : Iterables.filter(targets, Rule.class)) { if (rule.isAttributeValueExplicitlySpecified("deprecation")) { eventHandler.handle(Event.warn(rule.getLocation(), String.format( "target '%s' is deprecated: %s", rule.getLabel(), NonconfigurableAttributeMapper.of(rule).get("deprecation", Type.STRING)))); } } } }