// Copyright 2014 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.TargetPattern; import com.google.devtools.build.lib.cmdline.TargetPatternResolver; import com.google.devtools.build.lib.events.Event; 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.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; import com.google.devtools.build.skyframe.SkyFunction; import com.google.devtools.build.skyframe.SkyFunctionException; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.SkyValue; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; /** * TargetPatternFunction translates a target pattern (eg, "foo/...") into a set of resolved * Targets. */ public class TargetPatternFunction implements SkyFunction { private final AtomicReference pkgPath; public TargetPatternFunction(AtomicReference pkgPath) { this.pkgPath = pkgPath; } @Override public SkyValue compute(SkyKey key, Environment env) throws TargetPatternFunctionException, InterruptedException { TargetPatternValue.TargetPattern patternKey = ((TargetPatternValue.TargetPattern) key.argument()); TargetPattern.Parser parser = new TargetPattern.Parser(patternKey.getOffset()); try { Resolver resolver = new Resolver(env, patternKey.getPolicy(), pkgPath); TargetPattern resolvedPattern = parser.parse(patternKey.getPattern()); return new TargetPatternValue(resolvedPattern.eval(resolver)); } catch (TargetParsingException e) { throw new TargetPatternFunctionException(e); } catch (TargetPatternResolver.MissingDepException e) { return null; } } @Nullable @Override public String extractTag(SkyKey skyKey) { return null; } private static class Resolver implements TargetPatternResolver { private final Environment env; private final FilteringPolicy policy; private final AtomicReference pkgPath; public Resolver(Environment env, FilteringPolicy policy, AtomicReference pkgPath) { this.policy = policy; this.env = env; this.pkgPath = pkgPath; } @Override public void warn(String msg) { env.getListener().handle(Event.warn(msg)); } /** * Gets a Package via the Skyframe env. May return a Package that has errors. */ private Package getPackage(PackageIdentifier pkgIdentifier) throws MissingDepException, NoSuchThingException { SkyKey pkgKey = PackageValue.key(pkgIdentifier); Package pkg; try { PackageValue pkgValue = (PackageValue) env.getValueOrThrow(pkgKey, NoSuchPackageException.class); if (pkgValue == null) { throw new MissingDepException(); } pkg = pkgValue.getPackage(); } catch (NoSuchPackageException e) { pkg = e.getPackage(); if (pkg == null) { throw e; } } return pkg; } @Override public Target getTargetOrNull(String targetName) throws InterruptedException, MissingDepException { try { Label label = Label.parseAbsolute(targetName); if (!isPackage(label.getPackageName())) { return null; } Package pkg = getPackage(label.getPackageIdentifier()); return pkg.getTarget(label.getName()); } catch (Label.SyntaxException | NoSuchThingException e) { return null; } } @Override public ResolvedTargets getExplicitTarget(String targetName) throws TargetParsingException, InterruptedException, MissingDepException { Label label = TargetPatternResolverUtil.label(targetName); try { Package pkg = getPackage(label.getPackageIdentifier()); Target target = pkg.getTarget(label.getName()); return policy.shouldRetain(target, true) ? ResolvedTargets.of(target) : ResolvedTargets.empty(); } catch (NoSuchThingException e) { throw new TargetParsingException(e.getMessage(), e); } } @Override public ResolvedTargets getTargetsInPackage(String originalPattern, String packageName, boolean rulesOnly) throws TargetParsingException, InterruptedException, MissingDepException { FilteringPolicy actualPolicy = rulesOnly ? FilteringPolicies.and(FilteringPolicies.RULES_ONLY, policy) : policy; return getTargetsInPackage(originalPattern, packageName, actualPolicy); } private ResolvedTargets getTargetsInPackage(String originalPattern, String packageName, FilteringPolicy policy) throws TargetParsingException, MissingDepException { // Normalise, e.g "foo//bar" -> "foo/bar"; "foo/" -> "foo": PathFragment packageNameFragment = new PathFragment(packageName); 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. // (BTW I tried duplicating that validation logic in Label but it was // extremely tricky.) 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.toString())); 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) throws MissingDepException { SkyKey packageLookupKey; packageLookupKey = PackageLookupValue.key(new PathFragment(packageName)); PackageLookupValue packageLookupValue = (PackageLookupValue) env.getValue(packageLookupKey); if (packageLookupValue == null) { throw new MissingDepException(); } return packageLookupValue.packageExists(); } @Override public String getTargetKind(Target target) { return target.getTargetKind(); } @Override public ResolvedTargets findTargetsBeneathDirectory( String originalPattern, String pathPrefix, boolean rulesOnly) throws TargetParsingException, MissingDepException { 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 builder = ResolvedTargets.builder(); List lookupValues = new ArrayList<>(); for (Path root : pkgPath.get().getPathEntries()) { SkyKey key = RecursivePkgValue.key(RootedPath.toRootedPath(root, directory)); RecursivePkgValue lookup = (RecursivePkgValue) env.getValue(key); if (lookup != null) { lookupValues.add(lookup); } } if (env.valuesMissing()) { throw new MissingDepException(); } for (RecursivePkgValue value : lookupValues) { for (String pkg : value.getPackages()) { 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 intermediateResult = builder.build(); ResolvedTargets.Builder filteredBuilder = ResolvedTargets.builder(); if (intermediateResult.hasError()) { filteredBuilder.setError(); } for (Target target : intermediateResult.getTargets()) { if (actualPolicy.shouldRetain(target, false)) { filteredBuilder.add(target); } } return filteredBuilder.build(); } } /** * Used to declare all the exception types that can be wrapped in the exception thrown by * {@link TargetPatternFunction#compute}. */ private static final class TargetPatternFunctionException extends SkyFunctionException { public TargetPatternFunctionException(TargetParsingException e) { super(e, Transience.PERSISTENT); } } }