// 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:
*
*
target pattern evaluation
*
test suite expansion
*
loading the labels needed to construct the build configuration
*
loading the labels needed for the analysis with the build configuration
*
loading the transitive closure of the targets and the configuration labels
*
*
*
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