// 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.skyframe; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.packages.BuildFileNotFoundException; import com.google.devtools.build.lib.packages.NoSuchPackageException; import com.google.devtools.build.lib.packages.NoSuchTargetException; import com.google.devtools.build.lib.packages.Package; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.vfs.PathFragment; 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 javax.annotation.Nullable; /** * A SkyFunction for {@link TargetMarkerValue}s. Returns a {@link * TargetMarkerValue#TARGET_MARKER_INSTANCE} if the {@link Label} in the {@link SkyKey} * specifies a {@link Package} that exists and a {@link Target} that exists in that package. The * package may have errors. */ public final class TargetMarkerFunction implements SkyFunction { @Override public SkyValue compute(SkyKey key, Environment env) throws TargetMarkerFunctionException, InterruptedException { try { return computeTargetMarkerValue(key, env); } catch (NoSuchTargetException e) { throw new TargetMarkerFunctionException(e); } catch (NoSuchPackageException e) { // Re-throw this exception with our key because root causes should be targets, not packages. throw new TargetMarkerFunctionException(e); } } @Nullable static TargetMarkerValue computeTargetMarkerValue(SkyKey key, Environment env) throws NoSuchTargetException, NoSuchPackageException, InterruptedException { Label label = (Label) key.argument(); PathFragment pkgForLabel = label.getPackageFragment(); if (label.getName().contains("/")) { // This target is in a subdirectory, therefore it could potentially be invalidated by // a new BUILD file appearing in the hierarchy. PathFragment containingDirectory = label.toPathFragment().getParentDirectory(); ContainingPackageLookupValue containingPackageLookupValue; try { PackageIdentifier newPkgId = PackageIdentifier.create( label.getPackageIdentifier().getRepository(), containingDirectory); containingPackageLookupValue = (ContainingPackageLookupValue) env.getValueOrThrow( ContainingPackageLookupValue.key(newPkgId), BuildFileNotFoundException.class, InconsistentFilesystemException.class); } catch (InconsistentFilesystemException e) { throw new NoSuchTargetException(label, e.getMessage()); } if (containingPackageLookupValue == null) { return null; } if (!containingPackageLookupValue.hasContainingPackage()) { // This means the label's package doesn't exist. E.g. there is no package 'a' and we are // trying to build the target for label 'a:b/foo'. throw new BuildFileNotFoundException( label.getPackageIdentifier(), "BUILD file not found on package path for '" + pkgForLabel.getPathString() + "'"); } if (!containingPackageLookupValue.getContainingPackageName().equals( label.getPackageIdentifier())) { throw new NoSuchTargetException( label, String.format( "Label '%s' crosses boundary of subpackage '%s'", label, containingPackageLookupValue.getContainingPackageName())); } } SkyKey pkgSkyKey = PackageValue.key(label.getPackageIdentifier()); PackageValue value = (PackageValue) env.getValueOrThrow(pkgSkyKey, NoSuchPackageException.class); if (value == null) { return null; } Package pkg = value.getPackage(); Target target = pkg.getTarget(label.getName()); if (pkg.containsErrors()) { // There is a target, but its package is in error. We rethrow so that the root cause is the // target, not the package. Note that targets are only in error when their package is // "in error" (because a package is in error if there was an error evaluating the package, or // if one of its targets was in error). throw new NoSuchTargetException(target); } return TargetMarkerValue.TARGET_MARKER_INSTANCE; } @Override public String extractTag(SkyKey skyKey) { return Label.print((Label) skyKey.argument()); } /** * Used to declare all the exception types that can be wrapped in the exception thrown by * {@link TargetMarkerFunction#compute}. */ private static final class TargetMarkerFunctionException extends SkyFunctionException { public TargetMarkerFunctionException(NoSuchTargetException e) { super(e, Transience.PERSISTENT); } public TargetMarkerFunctionException(NoSuchPackageException e) { super(e, Transience.PERSISTENT); } } }