// 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.PackageIdentifier; import com.google.devtools.build.lib.concurrent.BlazeInterners; import com.google.devtools.build.lib.packages.BuildFileName; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; 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 javax.annotation.Nullable; /** * A value that represents a package lookup result. * *

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. * *

Implementation detail: we use inheritance here to optimize for memory usage. */ public abstract class PackageLookupValue implements SkyValue { @AutoCodec public static final NoBuildFilePackageLookupValue NO_BUILD_FILE_VALUE = new NoBuildFilePackageLookupValue(); @AutoCodec public static final DeletedPackageLookupValue DELETED_PACKAGE_VALUE = new DeletedPackageLookupValue(); @AutoCodec public static final NoRepositoryPackageLookupValue NO_SUCH_REPOSITORY_VALUE = new NoRepositoryPackageLookupValue(); 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 } protected PackageLookupValue() { } public static PackageLookupValue success( RepositoryValue repository, Root root, BuildFileName buildFileName) { return new SuccessfulPackageLookupValue(repository, root, buildFileName); } public static PackageLookupValue success(Root root, BuildFileName buildFileName) { return new SuccessfulPackageLookupValue(null, root, buildFileName); } 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(); /** * 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) { Preconditions.checkArgument(!pkgIdentifier.getRepository().isDefault()); return Key.create(pkgIdentifier); } @AutoCodec.VisibleForSerialization @AutoCodec static class Key extends AbstractSkyKey { private static final Interner interner = BlazeInterners.newWeakInterner(); private Key(PackageIdentifier arg) { super(arg); } @AutoCodec.VisibleForSerialization @AutoCodec.Instantiator static Key create(PackageIdentifier arg) { return interner.intern(new Key(arg)); } @Override public SkyFunctionName functionName() { return SkyFunctions.PACKAGE_LOOKUP; } } /** Successful lookup value. */ @AutoCodec public static class SuccessfulPackageLookupValue extends PackageLookupValue { /** * The repository value the meaning of the path depends on (e.g., an external repository * controlling a symbolic link the path goes trough). Can be {@code null}, if does not depend * on such a repository; will always be {@code null} for packages in the main repository. */ @Nullable private final RepositoryValue repository; private final Root root; private final BuildFileName buildFileName; @AutoCodec.Instantiator @AutoCodec.VisibleForSerialization SuccessfulPackageLookupValue( @Nullable RepositoryValue repository, Root root, BuildFileName buildFileName) { this.repository = repository; this.root = root; this.buildFileName = buildFileName; } SuccessfulPackageLookupValue(Root root, BuildFileName buildFileName) { this.repository = null; this.root = root; this.buildFileName = buildFileName; } @Nullable public RepositoryValue repository() { return repository; } @Override public boolean packageExists() { return true; } @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)) { return false; } SuccessfulPackageLookupValue other = (SuccessfulPackageLookupValue) obj; return root.equals(other.root) && buildFileName == other.buildFileName && Objects.equal(repository, other.repository); } @Override public int hashCode() { return Objects.hashCode(root.hashCode(), buildFileName.hashCode(), repository); } } private abstract static class UnsuccessfulPackageLookupValue extends PackageLookupValue { @Override public boolean packageExists() { 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"; } } /** Value indicating the package name was in error. */ @AutoCodec public static class InvalidNamePackageLookupValue extends UnsuccessfulPackageLookupValue { private final String errorMsg; @AutoCodec.Instantiator @AutoCodec.VisibleForSerialization 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)) { return false; } InvalidNamePackageLookupValue other = (InvalidNamePackageLookupValue) obj; 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. */ @AutoCodec public static class IncorrectRepositoryReferencePackageLookupValue extends UnsuccessfulPackageLookupValue { private final PackageIdentifier invalidPackageIdentifier; private final PackageIdentifier correctedPackageIdentifier; @AutoCodec.Instantiator @AutoCodec.VisibleForSerialization IncorrectRepositoryReferencePackageLookupValue( PackageIdentifier invalidPackageIdentifier, PackageIdentifier correctedPackageIdentifier) { this.invalidPackageIdentifier = invalidPackageIdentifier; this.correctedPackageIdentifier = correctedPackageIdentifier; } public PackageIdentifier getInvalidPackageIdentifier() { return invalidPackageIdentifier; } public 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)) { return false; } IncorrectRepositoryReferencePackageLookupValue other = (IncorrectRepositoryReferencePackageLookupValue) obj; 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: invalidPackageIdenfitier: %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"; } } /** * Marker 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 NoRepositoryPackageLookupValue() {} @Override ErrorReason getErrorReason() { return ErrorReason.REPOSITORY_NOT_FOUND; } @Override public String getErrorMsg() { return "The repository could not be resolved"; } } }