On-the-fly target pattern resolution in SkyQueryEnvironment
Moves pattern resolving logic from TargetPatternFunction.Resolver to
a top level class. Adds a layer of abstraction to the Resolver
implementation enabling it to be backed by either an Environment or
a Graph, for use in SkyFunction evaluation or on-the-fly evaluation,
respectively. Finally, SkyQueryEnvironment#preloadOrThrow now checks
to see if each target pattern exists in the graph, and any that
don't will be resolved on-the-fly.
--
MOS_MIGRATED_REVID=88861201
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RecursivePackageProviderBackedTargetPatternResolver.java b/src/main/java/com/google/devtools/build/lib/skyframe/RecursivePackageProviderBackedTargetPatternResolver.java
new file mode 100644
index 0000000..a1b16f0
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RecursivePackageProviderBackedTargetPatternResolver.java
@@ -0,0 +1,206 @@
+// Copyright 2015 Google Inc. 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.skyframe;
+
+import com.google.devtools.build.lib.cmdline.LabelValidator;
+import com.google.devtools.build.lib.cmdline.ResolvedTargets;
+import com.google.devtools.build.lib.cmdline.TargetParsingException;
+import com.google.devtools.build.lib.cmdline.TargetPatternResolver;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
+import com.google.devtools.build.lib.packages.NoSuchThingException;
+import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.packages.PackageIdentifier;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.pkgcache.FilteringPolicies;
+import com.google.devtools.build.lib.pkgcache.FilteringPolicy;
+import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
+import com.google.devtools.build.lib.pkgcache.RecursivePackageProvider;
+import com.google.devtools.build.lib.pkgcache.TargetPatternResolverUtil;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.RootedPath;
+
+/**
+ * A {@link TargetPatternResolver} backed by a {@link RecursivePackageProvider}.
+ */
+public class RecursivePackageProviderBackedTargetPatternResolver
+ implements TargetPatternResolver<Target> {
+
+ private final RecursivePackageProvider recursivePackageProvider;
+ private final EventHandler eventHandler;
+ private final FilteringPolicy policy;
+ private final PathPackageLocator pkgPath;
+
+ public RecursivePackageProviderBackedTargetPatternResolver(
+ final RecursivePackageProvider recursivePackageProvider,
+ EventHandler eventHandler,
+ FilteringPolicy policy,
+ PathPackageLocator pkgPath) {
+ this.recursivePackageProvider = recursivePackageProvider;
+ this.eventHandler = eventHandler;
+ this.policy = policy;
+ this.pkgPath = pkgPath;
+ }
+
+ @Override
+ public void warn(String msg) {
+ eventHandler.handle(Event.warn(msg));
+ }
+
+ /**
+ * Gets a {@link Package} from the {@link RecursivePackageProvider}. May return a {@link Package}
+ * that has errors.
+ */
+ private Package getPackage(PackageIdentifier pkgIdentifier)
+ throws NoSuchPackageException, InterruptedException {
+ Package pkg;
+ try {
+ pkg = recursivePackageProvider.getPackage(eventHandler, pkgIdentifier);
+ } catch (NoSuchPackageException e) {
+ pkg = e.getPackage();
+ if (pkg == null) {
+ throw e;
+ }
+ }
+ return pkg;
+ }
+
+ @Override
+ public Target getTargetOrNull(String targetName) throws InterruptedException {
+ try {
+ Label label = Label.parseAbsolute(targetName);
+ if (!isPackage(label.getPackageName())) {
+ return null;
+ }
+ return recursivePackageProvider.getTarget(eventHandler, label);
+ } catch (Label.SyntaxException | NoSuchThingException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public ResolvedTargets<Target> getExplicitTarget(String targetName)
+ throws TargetParsingException, InterruptedException {
+ Label label = TargetPatternResolverUtil.label(targetName);
+ try {
+ Target target = recursivePackageProvider.getTarget(eventHandler, label);
+ return policy.shouldRetain(target, true)
+ ? ResolvedTargets.of(target)
+ : ResolvedTargets.<Target>empty();
+ } catch (NoSuchThingException e) {
+ throw new TargetParsingException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public ResolvedTargets<Target> getTargetsInPackage(String originalPattern, String packageName,
+ boolean rulesOnly)
+ throws TargetParsingException, InterruptedException {
+ FilteringPolicy actualPolicy = rulesOnly
+ ? FilteringPolicies.and(FilteringPolicies.RULES_ONLY, policy)
+ : policy;
+ return getTargetsInPackage(originalPattern, new PathFragment(packageName), actualPolicy);
+ }
+
+ private ResolvedTargets<Target> getTargetsInPackage(String originalPattern,
+ PathFragment packageNameFragment, FilteringPolicy policy)
+ throws TargetParsingException, InterruptedException {
+ String packageName = packageNameFragment.toString();
+
+ // It's possible for this check to pass, but for
+ // Label.validatePackageNameFull to report an error because the
+ // package name is illegal. That's a little weird, but we can live with
+ // that for now--see test case: testBadPackageNameButGoodEnoughForALabel.
+ if (LabelValidator.validatePackageName(packageName) != null) {
+ throw new TargetParsingException("'" + packageName + "' is not a valid package name");
+ }
+ if (!isPackage(packageName)) {
+ throw new TargetParsingException(
+ TargetPatternResolverUtil.getParsingErrorMessage(
+ "no such package '" + packageName + "': BUILD file not found on package path",
+ originalPattern));
+ }
+
+ try {
+ Package pkg = getPackage(PackageIdentifier.createInDefaultRepo(packageNameFragment));
+ return TargetPatternResolverUtil.resolvePackageTargets(pkg, policy);
+ } catch (NoSuchThingException e) {
+ String message = TargetPatternResolverUtil.getParsingErrorMessage(
+ "package contains errors", originalPattern);
+ throw new TargetParsingException(message, e);
+ }
+ }
+
+ @Override
+ public boolean isPackage(String packageName) {
+ return recursivePackageProvider.isPackage(eventHandler, packageName);
+ }
+
+ @Override
+ public String getTargetKind(Target target) {
+ return target.getTargetKind();
+ }
+
+ @Override
+ public ResolvedTargets<Target> findTargetsBeneathDirectory(
+ String originalPattern, String pathPrefix, boolean rulesOnly)
+ throws TargetParsingException, InterruptedException {
+ FilteringPolicy actualPolicy = rulesOnly
+ ? FilteringPolicies.and(FilteringPolicies.RULES_ONLY, policy)
+ : policy;
+
+ PathFragment directory = new PathFragment(pathPrefix);
+ if (directory.containsUplevelReferences()) {
+ throw new TargetParsingException("up-level references are not permitted: '"
+ + directory.getPathString() + "'");
+ }
+ if (!pathPrefix.isEmpty() && (LabelValidator.validatePackageName(pathPrefix) != null)) {
+ throw new TargetParsingException("'" + pathPrefix + "' is not a valid package name");
+ }
+
+ ResolvedTargets.Builder<Target> builder = ResolvedTargets.builder();
+
+ for (Path root : pkgPath.getPathEntries()) {
+ RootedPath rootedPath = RootedPath.toRootedPath(root, directory);
+ Iterable<PathFragment> packagesUnderDirectory = recursivePackageProvider
+ .getPackagesUnderDirectory(rootedPath);
+ for (PathFragment pkg : packagesUnderDirectory) {
+ builder.merge(getTargetsInPackage(originalPattern, pkg, FilteringPolicies.NO_FILTER));
+ }
+ }
+
+ if (builder.isEmpty()) {
+ throw new TargetParsingException("no targets found beneath '" + directory + "'");
+ }
+
+ // Apply the transform after the check so we only return the
+ // error if the tree really contains no targets.
+ ResolvedTargets<Target> intermediateResult = builder.build();
+ ResolvedTargets.Builder<Target> filteredBuilder = ResolvedTargets.builder();
+ if (intermediateResult.hasError()) {
+ filteredBuilder.setError();
+ }
+ for (Target target : intermediateResult.getTargets()) {
+ if (actualPolicy.shouldRetain(target, false)) {
+ filteredBuilder.add(target);
+ }
+ }
+ return filteredBuilder.build();
+ }
+
+}
+