| // 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 static com.google.common.base.Preconditions.checkNotNull; | |
| import static com.google.devtools.build.lib.cmdline.Label.labelCodec; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.base.MoreObjects; | |
| import com.google.common.base.Preconditions; | |
| import com.google.common.collect.ImmutableTable; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.cmdline.RepositoryName; | |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; | |
| import com.google.devtools.build.lib.packages.BzlVisibility; | |
| import com.google.devtools.build.lib.skyframe.serialization.LeafDeserializationContext; | |
| import com.google.devtools.build.lib.skyframe.serialization.LeafObjectCodec; | |
| import com.google.devtools.build.lib.skyframe.serialization.LeafSerializationContext; | |
| import com.google.devtools.build.lib.skyframe.serialization.SerializationException; | |
| import com.google.devtools.build.lib.skyframe.serialization.VisibleForSerialization; | |
| import com.google.devtools.build.lib.util.HashCodes; | |
| import com.google.devtools.build.lib.vfs.Root; | |
| import com.google.devtools.build.lib.vfs.RootedPath; | |
| import com.google.devtools.build.skyframe.SkyFunctionName; | |
| import com.google.devtools.build.skyframe.SkyKey; | |
| import com.google.devtools.build.skyframe.SkyKey.SkyKeyInterner; | |
| import com.google.devtools.build.skyframe.SkyValue; | |
| import com.google.errorprone.annotations.Keep; | |
| import com.google.protobuf.CodedInputStream; | |
| import com.google.protobuf.CodedOutputStream; | |
| import java.io.IOException; | |
| import net.starlark.java.eval.Module; | |
| /** | |
| * A value that represents the .bzl (or .scl) module loaded by a Starlark {@code load()} statement. | |
| * | |
| * <p>Note: Historically, all modules had the .bzl suffix, but this is no longer true now that Bazel | |
| * supports the .scl dialect. In identifiers, code comments, and documentation, you should generally | |
| * assume any "bzl" term could mean a .scl file as well. | |
| * | |
| * <p>The key consists of an absolute {@link Label} and the context in which the load occurs. The | |
| * Label should not reference the special {@code external} package. | |
| * | |
| * <p>This value is also used to represent the special prelude file that may be implicitly loaded | |
| * and sourced by BUILD files. The prelude file need not end in ".bzl". | |
| */ | |
| public class BzlLoadValue implements SkyValue { | |
| private final Module module; // .bzl module (and indirectly, the entire load DAG) | |
| // TODO(brandjon): Is this field redundant with BazelModuleContext#bzlTransitiveDigest, accessible | |
| // from the Module as client data? | |
| private final byte[] transitiveDigest; // of .bzl file and load dependencies | |
| private final BzlVisibility bzlVisibility; | |
| private final ImmutableTable<RepositoryName, String, RepositoryName> recordedRepoMappings; | |
| @VisibleForTesting | |
| public BzlLoadValue( | |
| Module module, | |
| byte[] transitiveDigest, | |
| BzlVisibility bzlVisibility, | |
| ImmutableTable<RepositoryName, String, RepositoryName> recordedRepoMappings) { | |
| this.module = checkNotNull(module); | |
| this.transitiveDigest = checkNotNull(transitiveDigest); | |
| this.bzlVisibility = checkNotNull(bzlVisibility); | |
| this.recordedRepoMappings = checkNotNull(recordedRepoMappings); | |
| } | |
| /** Returns the .bzl module. */ | |
| public Module getModule() { | |
| return module; | |
| } | |
| /** Returns the digest of the .bzl module and its transitive load dependencies. */ | |
| public byte[] getTransitiveDigest() { | |
| return transitiveDigest; | |
| } | |
| /** Returns the visibility of this module for the purpose of {@code load()} statements. */ | |
| public BzlVisibility getBzlVisibility() { | |
| return bzlVisibility; | |
| } | |
| /** | |
| * Returns the repo mapping entries used to laod this bzl file. Stored for correctness across | |
| * Bazel server restarts. | |
| */ | |
| public ImmutableTable<RepositoryName, String, RepositoryName> getRecordedRepoMappings() { | |
| return recordedRepoMappings; | |
| } | |
| private static final SkyKeyInterner<Key> keyInterner = SkyKey.newInterner(); | |
| /** SkyKey for a Starlark load. */ | |
| public abstract static sealed class Key implements SkyKey | |
| permits KeyForBuild, KeyForWorkspace, KeyForBuiltins, KeyForBzlmod { | |
| // Closed, for class-based equals()/hashCode(). | |
| private Key() {} | |
| /** | |
| * Returns the absolute label of the .bzl file to be loaded. | |
| * | |
| * <p>For {@link KeyForBuiltins}, it must begin with {@code @_builtins//:}. (It is legal for | |
| * other keys to use {@code @_builtins}, but since no real repo by that name may be defined, | |
| * they won't evaluate to a successful result.) | |
| */ | |
| public abstract Label getLabel(); | |
| /** Returns true if this is a request for the special BUILD prelude file. */ | |
| boolean isBuildPrelude() { | |
| return false; | |
| } | |
| /** Returns true if this is a request for a builtins bzl file. */ | |
| boolean isBuiltins() { | |
| return false; | |
| } | |
| /** Returns true if the requested file follows the .scl dialect. */ | |
| // Note: Just as with .bzl, the same .scl file can be referred to from multiple key types, for | |
| // instance if a BUILD file and a module rule both load foo.scl. Conceptually, .scl files | |
| // shouldn't depend on what kind of top-level file caused them to load, but in practice, this | |
| // implementation quirk means that the .scl file will be loaded twice as separate copies. | |
| // | |
| // This shouldn't matter except in rare edge cases, such as if a Starlark function is loaded | |
| // from both copies and compared for equality. Performance wise, it also means that all | |
| // transitive .scl files will be double-loaded, but we don't expect that to be significant. | |
| // | |
| // The alternative is to use a separate key type just for .scl, but that complicates repo logic; | |
| // see BzlLoadFunction#getRepositoryMapping. | |
| final boolean isSclDialect() { | |
| return getLabel().getName().endsWith(".scl"); | |
| } | |
| /** | |
| * Constructs a new key suitable for evaluating a {@code load()} dependency of this key's .bzl | |
| * file. | |
| * | |
| * <p>The new key uses the given label but the same contextual information -- whether the | |
| * top-level requesting value is a BUILD or WORKSPACE file, and if it's a WORKSPACE, its | |
| * chunking info. | |
| */ | |
| abstract Key getKeyForLoad(Label loadLabel); | |
| /** | |
| * Constructs a BzlCompileValue key suitable for retrieving the Starlark code for this .bzl, | |
| * given the Root in which to find its file. | |
| */ | |
| abstract BzlCompileValue.Key getCompileKey(Root root); | |
| @Override | |
| public SkyFunctionName functionName() { | |
| return SkyFunctions.BZL_LOAD; | |
| } | |
| @Override | |
| public final boolean valueIsShareable() { | |
| // We don't guarantee that all constructs implement equality, meaning we can't correctly | |
| // compare deserialized instances. This is currently the case for attribute descriptors. | |
| return false; | |
| } | |
| @SuppressWarnings("EqualsGetClass") // All subclasses are known. | |
| @Override | |
| public boolean equals(Object obj) { | |
| if (this == obj) { | |
| return true; | |
| } | |
| if (obj == null) { | |
| return false; | |
| } | |
| if (!this.getClass().equals(obj.getClass())) { | |
| return false; | |
| } | |
| Key that = (Key) obj; | |
| return this.getLabel().equals(that.getLabel()) | |
| && (this.isBuildPrelude() == that.isBuildPrelude()) | |
| && (this.isBuiltins() == that.isBuiltins()); | |
| } | |
| @Override | |
| public int hashCode() { | |
| int result = HashCodes.hashObjects(getClass(), getLabel()); | |
| result = 31 * result + Boolean.hashCode(isBuildPrelude()); | |
| result = 31 * result + Boolean.hashCode(isBuiltins()); | |
| return result; | |
| } | |
| protected final MoreObjects.ToStringHelper toStringHelper() { | |
| return MoreObjects.toStringHelper(this) | |
| .add("label", getLabel()) | |
| .add("isBuildPrelude", isBuildPrelude()); | |
| } | |
| @Override | |
| public String toString() { | |
| return toStringHelper().toString(); | |
| } | |
| @Override | |
| public SkyKeyInterner<Key> getSkyKeyInterner() { | |
| return keyInterner; | |
| } | |
| } | |
| /** A key for loading a .bzl during package loading (BUILD evaluation). */ | |
| @Immutable | |
| @VisibleForSerialization | |
| static final class KeyForBuild extends Key { | |
| private final Label label; | |
| /** | |
| * True if this is the special prelude file, whose declarations are implicitly loaded by all | |
| * BUILD files. | |
| */ | |
| private final boolean isBuildPrelude; | |
| private KeyForBuild(Label label, boolean isBuildPrelude) { | |
| this.label = checkNotNull(label); | |
| this.isBuildPrelude = isBuildPrelude; | |
| } | |
| @Override | |
| public Label getLabel() { | |
| return label; | |
| } | |
| @Override | |
| boolean isBuildPrelude() { | |
| return isBuildPrelude; | |
| } | |
| @Override | |
| Key getKeyForLoad(Label loadLabel) { | |
| // Note that the returned key always has !isBuildPrelude. I.e., if the prelude file loads | |
| // another .bzl, the loaded .bzl is processed as normal with no special prelude magic. This is | |
| // because 1) only the prelude file, not its dependencies, should automatically re-export its | |
| // loaded symbols; and 2) we don't want prelude-loaded modules to end up cloned if they're | |
| // also loaded through normal means. | |
| return keyForBuild(loadLabel); | |
| } | |
| @Override | |
| BzlCompileValue.Key getCompileKey(Root root) { | |
| if (isBuildPrelude) { | |
| return BzlCompileValue.keyForBuildPrelude(root, label); | |
| } else { | |
| return BzlCompileValue.key(root, label); | |
| } | |
| } | |
| } | |
| /** | |
| * A key for loading a .bzl during WORKSPACE evaluation. | |
| * | |
| * <p>This needs to track "chunking" information, i.e. a sequence number indicating which segment | |
| * of the WORKSPACE file we are in the process of evaluating. This helps determine the appropriate | |
| * repository remapping value to use. | |
| */ | |
| // TODO(brandjon): Question: It looks like the chunk number doesn't play any role in deciding | |
| // whether or not a repo is available for load()ing. Are we tracking incremental dependencies | |
| // correctly? For instance, if a repository declaration moves from one workspace chunk to another, | |
| // are we reevaluating whether its loads are still valid? AI: fix if broken, improve this comment | |
| // if not broken. | |
| @Immutable | |
| @VisibleForSerialization | |
| static final class KeyForWorkspace extends Key { | |
| private final Label label; | |
| private final int workspaceChunk; | |
| private final RootedPath workspacePath; | |
| private KeyForWorkspace(Label label, int workspaceChunk, RootedPath workspacePath) { | |
| this.label = checkNotNull(label); | |
| this.workspaceChunk = workspaceChunk; | |
| this.workspacePath = checkNotNull(workspacePath); | |
| } | |
| @Override | |
| public Label getLabel() { | |
| return label; | |
| } | |
| int getWorkspaceChunk() { | |
| return workspaceChunk; | |
| } | |
| RootedPath getWorkspacePath() { | |
| return workspacePath; | |
| } | |
| @Override | |
| Key getKeyForLoad(Label loadLabel) { | |
| return keyForWorkspace(loadLabel, workspaceChunk, workspacePath); | |
| } | |
| @Override | |
| BzlCompileValue.Key getCompileKey(Root root) { | |
| return BzlCompileValue.key(root, label); | |
| } | |
| @Override | |
| public boolean equals(Object obj) { | |
| if (!super.equals(obj)) { | |
| return false; | |
| } | |
| KeyForWorkspace other = (KeyForWorkspace) obj; | |
| return workspaceChunk == other.workspaceChunk && workspacePath.equals(other.workspacePath); | |
| } | |
| @Override | |
| public int hashCode() { | |
| int result = super.hashCode(); | |
| result = 31 * result + Integer.hashCode(workspaceChunk); | |
| result = 31 * result + workspacePath.hashCode(); | |
| return result; | |
| } | |
| } | |
| /** | |
| * A key for loading a .bzl during {@code @_builtins} evaluation. | |
| * | |
| * <p>This kind of key is only requested by {@link StarlarkBuiltinsFunction} and its transitively | |
| * loaded {@link BzlLoadFunction} calls. | |
| * | |
| * <p>The label must have {@link StarlarkBuiltinsValue#BUILTINS_REPO} as its repository component. | |
| * (It is valid for other key types to use that repo name, but since it is not a real repository | |
| * and cannot be fetched, any attempt to resolve such a key would fail.) | |
| */ | |
| // TODO(#11437): Prevent users from trying to declare a repo named "@_builtins". | |
| @Immutable | |
| @VisibleForSerialization | |
| static final class KeyForBuiltins extends Key { | |
| private final Label label; | |
| private KeyForBuiltins(Label label) { | |
| this.label = checkNotNull(label); | |
| if (!StarlarkBuiltinsValue.isBuiltinsRepo(label.getRepository())) { | |
| throw new IllegalArgumentException("repository name for builtins key must be '@_builtins'"); | |
| } | |
| } | |
| @Override | |
| public Label getLabel() { | |
| return label; | |
| } | |
| @Override | |
| boolean isBuiltins() { | |
| return true; | |
| } | |
| @Override | |
| Key getKeyForLoad(Label label) { | |
| return keyForBuiltins(label); | |
| } | |
| @Override | |
| BzlCompileValue.Key getCompileKey(Root root) { | |
| return BzlCompileValue.keyForBuiltins(root, label); | |
| } | |
| } | |
| /** A key for loading a .bzl to get the repo rule required by Bzlmod generated repositories. */ | |
| @Immutable | |
| @VisibleForSerialization | |
| static sealed class KeyForBzlmod extends Key permits KeyForBzlmodBootstrap { | |
| private final Label label; | |
| private KeyForBzlmod(Label label) { | |
| this.label = checkNotNull(label); | |
| } | |
| @Override | |
| public Label getLabel() { | |
| return label; | |
| } | |
| @Override | |
| Key getKeyForLoad(Label loadLabel) { | |
| return keyForBzlmod(loadLabel); | |
| } | |
| @Override | |
| BzlCompileValue.Key getCompileKey(Root root) { | |
| return BzlCompileValue.key(root, label); | |
| } | |
| } | |
| @Immutable | |
| @VisibleForSerialization | |
| static final class KeyForBzlmodBootstrap extends KeyForBzlmod { | |
| private KeyForBzlmodBootstrap(Label label) { | |
| super(label); | |
| } | |
| @Override | |
| Key getKeyForLoad(Label loadLabel) { | |
| return keyForBzlmodBootstrap(loadLabel); | |
| } | |
| } | |
| /** Constructs a key for loading a regular (non-workspace) .bzl file, from the .bzl's label. */ | |
| public static Key keyForBuild(Label label) { | |
| return keyInterner.intern(new KeyForBuild(label, /* isBuildPrelude= */ false)); | |
| } | |
| /** | |
| * Constructs a key for loading a .bzl file from the context of evaluating the WORKSPACE file. | |
| * | |
| * @param label the label of the bzl file being loaded | |
| * @param workspaceChunk the workspace chunk that the load statement originated from. If the bzl | |
| * file is loaded more than once, this is the chunk that it was first loaded from | |
| * @param workspacePath the path of the workspace file for the project | |
| */ | |
| static Key keyForWorkspace(Label label, int workspaceChunk, RootedPath workspacePath) { | |
| return keyInterner.intern(new KeyForWorkspace(label, workspaceChunk, workspacePath)); | |
| } | |
| /** Constructs a key for loading a .bzl file within the {@code @_builtins} pseudo-repository. */ | |
| public static Key keyForBuiltins(Label label) { | |
| return keyInterner.intern(new KeyForBuiltins(label)); | |
| } | |
| /** Constructs a key for loading the special prelude .bzl. */ | |
| static Key keyForBuildPrelude(Label label) { | |
| return keyInterner.intern(new KeyForBuild(label, /* isBuildPrelude= */ true)); | |
| } | |
| /** Constructs a key for loading a .bzl for Bzlmod repos */ | |
| public static Key keyForBzlmod(Label label) { | |
| return keyInterner.intern(new KeyForBzlmod(label)); | |
| } | |
| public static Key keyForBzlmodBootstrap(Label label) { | |
| Preconditions.checkArgument( | |
| label.getRepository().equals(RepositoryName.BAZEL_TOOLS), | |
| "keyForBzlmodBootstrap must be called with a label in the bazel_tools repository"); | |
| return keyInterner.intern(new KeyForBzlmodBootstrap(label)); | |
| } | |
| public static KeyCodec bzlLoadKeyCodec() { | |
| return KeyCodec.INSTANCE; | |
| } | |
| @Keep | |
| private static final class KeyCodec extends LeafObjectCodec<Key> { | |
| private static final KeyCodec INSTANCE = new KeyCodec(); | |
| @Override | |
| public boolean autoRegister() { | |
| return false; // used via BzlLoadValue.bzlLoadKeyCodec only | |
| } | |
| @Override | |
| public Class<Key> getEncodedClass() { | |
| return Key.class; | |
| } | |
| @Override | |
| public void serialize(LeafSerializationContext context, Key obj, CodedOutputStream codedOut) | |
| throws SerializationException, IOException { | |
| switch (obj) { | |
| case KeyForBuild forBuild -> { | |
| context.serializeLeaf(obj.getLabel(), labelCodec(), codedOut); | |
| codedOut.writeBoolNoTag(false); | |
| } | |
| case KeyForBuiltins forBuiltins -> { | |
| context.serializeLeaf(obj.getLabel(), labelCodec(), codedOut); | |
| codedOut.writeBoolNoTag(true); | |
| } | |
| // This codec is only used serialize global Starlark symbols which isn't expected for the | |
| // following types. | |
| case KeyForWorkspace forWorkspace -> | |
| throw new UnsupportedOperationException( | |
| "KeyForWorkspace not expected " + obj.toString()); | |
| case KeyForBzlmod forBzlmod -> | |
| throw new UnsupportedOperationException("KeyForBzlmod not expected " + obj.toString()); | |
| } | |
| } | |
| @Override | |
| public Key deserialize(LeafDeserializationContext context, CodedInputStream codedIn) | |
| throws SerializationException, IOException { | |
| Label label = context.deserializeLeaf(codedIn, labelCodec()); | |
| return codedIn.readBool() ? keyForBuiltins(label) : keyForBuild(label); | |
| } | |
| } | |
| } |