blob: d2e1f51dcaee3b407146019d4082439188e8e6e1 [file] [log] [blame]
// 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.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Interner;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.concurrent.BlazeInterners;
import com.google.devtools.build.lib.packages.BuildFileName;
import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryValue;
import com.google.devtools.build.lib.skyframe.serialization.VisibleForSerialization;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.skyframe.AbstractSkyKey;
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import java.util.function.Predicate;
import javax.annotation.Nullable;
/**
* A value that represents a package lookup result.
*
* <p>Package lookups will always produce a value. On success, the {@code #getRoot} returns the
* package path root under which the package resides and the package's BUILD file is guaranteed to
* exist (unless this is looking up a WORKSPACE file, in which case the underlying file may or may
* not exist. On failure, {@code #getErrorReason} and {@code #getErrorMsg} describe why the package
* doesn't exist.
*
* <p>Implementation detail: we use inheritance here to optimize for memory usage.
*/
public abstract class PackageLookupValue implements SkyValue {
@SerializationConstant
public static final NoBuildFilePackageLookupValue NO_BUILD_FILE_VALUE =
new NoBuildFilePackageLookupValue();
@SerializationConstant
public static final DeletedPackageLookupValue DELETED_PACKAGE_VALUE =
new DeletedPackageLookupValue();
@SerializationConstant
public static final InvalidProjectLookupValue INVALID_PROJECT_VALUE =
new InvalidProjectLookupValue();
enum ErrorReason {
/** There is no BUILD file. */
NO_BUILD_FILE,
/** The package name is invalid. */
INVALID_PACKAGE_NAME,
/** The package is considered deleted because of --deleted_packages. */
DELETED_PACKAGE,
/** The repository was not found. */
REPOSITORY_NOT_FOUND,
/** The project file isn't actually a file. */
INVALID_PROJECT_FILE
}
protected PackageLookupValue() {}
public static PackageLookupValue successfulExternalPackageLookup(
RepositoryDirectoryValue repository, Root root, BuildFileName buildFileName) {
return SuccessfulExternalPackageLookupValue.create(repository, root, buildFileName);
}
public static PackageLookupValue success(Root root, BuildFileName buildFileName) {
return success(root, buildFileName, /* hasProjectFile= */ false);
}
public static PackageLookupValue success(
Root root, BuildFileName buildFileName, boolean hasProjectFile) {
return SuccessfulPackageLookupValue.create(root, buildFileName, hasProjectFile);
}
public static PackageLookupValue invalidPackageName(String errorMsg) {
return new InvalidNamePackageLookupValue(errorMsg);
}
public static PackageLookupValue incorrectRepositoryReference(
PackageIdentifier invalidPackage, PackageIdentifier correctPackage) {
return new IncorrectRepositoryReferencePackageLookupValue(invalidPackage, correctPackage);
}
/**
* For a successful package lookup, returns the root (package path entry) that the package resides
* in.
*/
public abstract Root getRoot();
/** For a successful package lookup, returns the build file name that the package uses. */
public abstract BuildFileName getBuildFileName();
/** Returns whether the package lookup was successful. */
public abstract boolean packageExists();
public abstract boolean hasProjectFile();
/**
* For a successful package lookup, returns the {@link RootedPath} for the build file that defines
* the package.
*/
public RootedPath getRootedPath(PackageIdentifier packageIdentifier) {
return RootedPath.toRootedPath(
getRoot(), getBuildFileName().getBuildFileFragment(packageIdentifier));
}
/**
* For an unsuccessful package lookup, gets the reason why {@link #packageExists} returns {@code
* false}.
*/
abstract ErrorReason getErrorReason();
/**
* For an unsuccessful package lookup, gets a detailed error message for {@link #getErrorReason}
* that is suitable for reporting to a user.
*/
public abstract String getErrorMsg();
public static SkyKey key(PathFragment directory) {
Preconditions.checkArgument(!directory.isAbsolute(), directory);
return key(PackageIdentifier.createInMainRepo(directory));
}
public static Key key(PackageIdentifier pkgIdentifier) {
return Key.create(pkgIdentifier);
}
static boolean appliesToKey(SkyKey key, Predicate<PackageIdentifier> identifierPredicate) {
return SkyFunctions.PACKAGE_LOOKUP.equals(key.functionName())
&& identifierPredicate.test((PackageIdentifier) key.argument());
}
/** {@link SkyKey} for {@link PackageLookupValue} computation. */
@VisibleForSerialization
@AutoCodec
static class Key extends AbstractSkyKey<PackageIdentifier> {
private static final SkyKeyInterner<Key> interner = SkyKey.newInterner();
private Key(PackageIdentifier arg) {
super(arg);
}
private static Key create(PackageIdentifier arg) {
return interner.intern(new Key(arg));
}
@VisibleForSerialization
@AutoCodec.Interner
static Key intern(Key key) {
return interner.intern(key);
}
@Override
public SkyFunctionName functionName() {
return SkyFunctions.PACKAGE_LOOKUP;
}
@Override
public SkyKeyInterner<Key> getSkyKeyInterner() {
return interner;
}
}
/** Successful lookup value for a package in the main repo. */
@AutoCodec
public static class SuccessfulPackageLookupValue extends PackageLookupValue {
private static final Interner<SuccessfulPackageLookupValue> INTERNER =
BlazeInterners.newWeakInterner();
private final Root root;
private final BuildFileName buildFileName;
private final boolean hasProjectFile;
SuccessfulPackageLookupValue(
Root root,
BuildFileName buildFileName,
boolean hasProjectFile) {
this.root = root;
this.buildFileName = buildFileName;
this.hasProjectFile = hasProjectFile;
}
@AutoCodec.Instantiator
@VisibleForSerialization
static SuccessfulPackageLookupValue create(
Root root, BuildFileName buildFileName, boolean hasProjectFile) {
// In practice there will be very few unique values. Most successful package lookups succeed
// against the first root (maybe there's only a single root!), there are only a few possible
// build file names (for Blaze there's just one!), and hasProjectFile is just a bit of
// information.
return INTERNER.intern(new SuccessfulPackageLookupValue(root, buildFileName, hasProjectFile));
}
@Override
public boolean packageExists() {
return true;
}
@Override
public boolean hasProjectFile() {
return hasProjectFile;
}
@Override
public Root getRoot() {
return root;
}
@Override
public BuildFileName getBuildFileName() {
return buildFileName;
}
@Override
ErrorReason getErrorReason() {
throw new IllegalStateException();
}
@Override
public String getErrorMsg() {
throw new IllegalStateException();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof SuccessfulPackageLookupValue other)) {
return false;
}
return root.equals(other.root)
&& buildFileName == other.buildFileName
&& hasProjectFile == other.hasProjectFile;
}
@Override
public int hashCode() {
return Objects.hashCode(root.hashCode(), buildFileName.hashCode(), hasProjectFile);
}
}
/** Successful lookup value for a package in an external repo. */
@AutoCodec
public static class SuccessfulExternalPackageLookupValue extends SuccessfulPackageLookupValue {
private static final Interner<SuccessfulExternalPackageLookupValue> INTERNER =
BlazeInterners.newWeakInterner();
/**
* The repository value, so as to be sensitive to changes to realpaths of external repositories.
*/
private final RepositoryDirectoryValue repository;
SuccessfulExternalPackageLookupValue(
RepositoryDirectoryValue repository,
Root root,
BuildFileName buildFileName,
boolean hasProjectFile) {
super(root, buildFileName, hasProjectFile);
this.repository = repository;
}
@AutoCodec.Instantiator
@VisibleForSerialization
static SuccessfulExternalPackageLookupValue create(
RepositoryDirectoryValue repository, Root root, BuildFileName buildFileName) {
return INTERNER.intern(
new SuccessfulExternalPackageLookupValue(
repository, root, buildFileName, /* hasProjectFile= */ false));
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof SuccessfulExternalPackageLookupValue other)) {
return false;
}
if (!super.equals(obj)) {
return false;
}
return repository.equals(other.repository);
}
@Override
public int hashCode() {
return Objects.hashCode(super.hashCode(), repository);
}
}
private abstract static class UnsuccessfulPackageLookupValue extends PackageLookupValue {
@Override
public boolean packageExists() {
return false;
}
@Override
public boolean hasProjectFile() {
return false;
}
@Override
public Root getRoot() {
throw new IllegalStateException();
}
@Override
public BuildFileName getBuildFileName() {
throw new IllegalStateException();
}
}
/** Marker value for no build file found. */
public static class NoBuildFilePackageLookupValue extends UnsuccessfulPackageLookupValue {
private NoBuildFilePackageLookupValue() {}
@Override
ErrorReason getErrorReason() {
return ErrorReason.NO_BUILD_FILE;
}
@Override
public String getErrorMsg() {
return "BUILD file not found on package path";
}
}
/** Marker for project file not actually being a file. */
public static class InvalidProjectLookupValue extends UnsuccessfulPackageLookupValue {
private InvalidProjectLookupValue() {}
@Override
ErrorReason getErrorReason() {
return ErrorReason.INVALID_PROJECT_FILE;
}
@Override
public String getErrorMsg() {
return "PROJECT.scl not a file";
}
}
/** Value indicating the package name was in error. */
public static class InvalidNamePackageLookupValue extends UnsuccessfulPackageLookupValue {
private final String errorMsg;
InvalidNamePackageLookupValue(String errorMsg) {
this.errorMsg = errorMsg;
}
@Override
ErrorReason getErrorReason() {
return ErrorReason.INVALID_PACKAGE_NAME;
}
@Override
public String getErrorMsg() {
return errorMsg;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof InvalidNamePackageLookupValue other)) {
return false;
}
return errorMsg.equals(other.errorMsg);
}
@Override
public int hashCode() {
return errorMsg.hashCode();
}
@Override
public String toString() {
return String.format("%s: %s", this.getClass().getSimpleName(), this.errorMsg);
}
}
/** Value indicating the package name was in error. */
public static class IncorrectRepositoryReferencePackageLookupValue
extends UnsuccessfulPackageLookupValue {
private final PackageIdentifier invalidPackageIdentifier;
private final PackageIdentifier correctedPackageIdentifier;
IncorrectRepositoryReferencePackageLookupValue(
PackageIdentifier invalidPackageIdentifier, PackageIdentifier correctedPackageIdentifier) {
this.invalidPackageIdentifier = invalidPackageIdentifier;
this.correctedPackageIdentifier = correctedPackageIdentifier;
}
PackageIdentifier getInvalidPackageIdentifier() {
return invalidPackageIdentifier;
}
PackageIdentifier getCorrectedPackageIdentifier() {
return correctedPackageIdentifier;
}
@Override
ErrorReason getErrorReason() {
return ErrorReason.INVALID_PACKAGE_NAME;
}
@Override
public String getErrorMsg() {
return String.format(
"Invalid package reference %s crosses into repository %s:"
+ " did you mean to use %s instead?",
invalidPackageIdentifier,
correctedPackageIdentifier.getRepository(),
correctedPackageIdentifier);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof IncorrectRepositoryReferencePackageLookupValue other)) {
return false;
}
return Objects.equal(invalidPackageIdentifier, other.invalidPackageIdentifier)
&& Objects.equal(correctedPackageIdentifier, other.correctedPackageIdentifier);
}
@Override
public int hashCode() {
return Objects.hashCode(invalidPackageIdentifier, correctedPackageIdentifier);
}
@Override
public String toString() {
return String.format(
"%s: invalidPackageIdentifier: %s, corrected: %s",
this.getClass().getSimpleName(),
this.invalidPackageIdentifier,
this.correctedPackageIdentifier);
}
}
/** Marker value for a deleted package. */
public static class DeletedPackageLookupValue extends UnsuccessfulPackageLookupValue {
private DeletedPackageLookupValue() {}
@Override
ErrorReason getErrorReason() {
return ErrorReason.DELETED_PACKAGE;
}
@Override
public String getErrorMsg() {
return "Package is considered deleted due to --deleted_packages";
}
}
/**
* Value for repository we could not find. This can happen when looking for a label that specifies
* a non-existent repository.
*/
public static class NoRepositoryPackageLookupValue extends UnsuccessfulPackageLookupValue {
private final RepositoryName repositoryName;
private final String reason;
NoRepositoryPackageLookupValue(RepositoryName repositoryName, String reason) {
this.repositoryName = repositoryName;
this.reason = reason;
}
@Override
ErrorReason getErrorReason() {
return ErrorReason.REPOSITORY_NOT_FOUND;
}
@Override
public String getErrorMsg() {
return String.format("The repository '%s' could not be resolved: %s", repositoryName, reason);
}
}
/**
* Creates the error message for the input {@linkplain Label label} has a subpackage crossing
* boundary.
*
* <p>Returns {@code null} if no subpackage is discovered or the subpackage is marked as DELETED.
*/
@Nullable
static String getErrorMessageForLabelCrossingPackageBoundary(
Root pkgRoot,
Label label,
PackageIdentifier subpackageIdentifier,
PackageLookupValue packageLookupValue) {
String message = null;
if (packageLookupValue.packageExists()) {
message =
String.format(
"Label '%s' is invalid because '%s' is a subpackage", label, subpackageIdentifier);
Root subPackageRoot = packageLookupValue.getRoot();
if (pkgRoot.equals(subPackageRoot)) {
PathFragment labelRootPathFragment = label.getPackageIdentifier().getSourceRoot();
PathFragment subpackagePathFragment = subpackageIdentifier.getSourceRoot();
if (subpackagePathFragment.startsWith(labelRootPathFragment)) {
PathFragment labelNameInSubpackage =
PathFragment.create(label.getName())
.subFragment(
subpackagePathFragment.segmentCount() - labelRootPathFragment.segmentCount());
message += "; perhaps you meant to put the" + " colon here: '";
if (subpackageIdentifier.getRepository().isMain()) {
message += "//";
}
message += subpackageIdentifier + ":" + labelNameInSubpackage + "'?";
} else {
// TODO: Is this a valid case? How do we handle this case?
}
} else {
message +=
"; have you deleted "
+ subpackageIdentifier
+ "/BUILD? "
+ "If so, use the --deleted_packages="
+ subpackageIdentifier
+ " option";
}
} else if (packageLookupValue instanceof IncorrectRepositoryReferencePackageLookupValue) {
message =
String.format(
"Label '%s' is invalid because '%s' is a subpackage",
label,
((IncorrectRepositoryReferencePackageLookupValue) packageLookupValue)
.correctedPackageIdentifier);
}
return message;
}
}