blob: df527b31de764f3793c46b8a691f629b023581df [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.analysis;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType.ABSTRACT;
import static com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType.TEST;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.ByteStreams;
import com.google.devtools.build.lib.actions.ActionEnvironment;
import com.google.devtools.build.lib.analysis.RuleContext.PrerequisiteValidator;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.Fragment;
import com.google.devtools.build.lib.analysis.config.FragmentClassSet;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.analysis.config.FragmentRegistry;
import com.google.devtools.build.lib.analysis.config.SymlinkDefinition;
import com.google.devtools.build.lib.analysis.config.transitions.ComposingTransitionFactory;
import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition;
import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
import com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics;
import com.google.devtools.build.lib.analysis.constraints.RuleContextConstraintSemantics;
import com.google.devtools.build.lib.analysis.starlark.StarlarkGlobalsImpl;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.graph.Digraph;
import com.google.devtools.build.lib.graph.Node;
import com.google.devtools.build.lib.packages.BazelStarlarkEnvironment;
import com.google.devtools.build.lib.packages.NativeAspectClass;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.packages.RuleFactory;
import com.google.devtools.build.lib.packages.RuleTransitionData;
import com.google.devtools.build.lib.packages.WorkspaceFactory;
import com.google.devtools.build.lib.starlarkbuildapi.core.Bootstrap;
import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
import com.google.devtools.common.options.OptionDefinition;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.annotation.Nullable;
import net.starlark.java.annot.StarlarkAnnotations;
import net.starlark.java.annot.StarlarkBuiltin;
/**
* Knows about every rule Blaze supports and the associated configuration options.
*
* <p>This class is initialized on server startup and the set of rules, build info factories and
* configuration options is guaranteed not to change over the life time of the Blaze server.
*/
// This class has no subclasses except those created by the evil that is mockery.
public /*final*/ class ConfiguredRuleClassProvider
implements RuleClassProvider, BuildConfigurationValue.GlobalStateProvider {
/**
* A coherent set of options, fragments, aspects and rules; each of these may declare a dependency
* on other such sets.
*/
public interface RuleSet {
/** Add stuff to the configured rule class provider builder. */
void init(ConfiguredRuleClassProvider.Builder builder);
/** List of required modules. */
default ImmutableList<RuleSet> requires() {
return ImmutableList.of();
}
}
/** An InMemoryFileSystem for bundled builtins .bzl files. */
public static class BundledFileSystem extends InMemoryFileSystem {
public BundledFileSystem() {
super(DigestHashFunction.SHA256);
}
// Pretend the digest of a bundled file is uniquely determined by its name, not its contents.
//
// The contents bundled files are guaranteed to not change throughout the lifetime of the Bazel
// server, we do not need to detect changes to a bundled file's contents. Not needing to worry
// about get the actual digest and detect changes to that digest helps avoid peculiarities in
// the interaction of InMemoryFileSystem and Skyframe. See cl/354809138 for further discussion,
// including of possible (but unlikely) future caveats of this approach.
//
// On the other hand, we do need to want different bundled files to have different digests. That
// way the bzl environment hashes for bzl rule classes defined in two different bundled files
// are guaranteed to be different, even if their set of transitive load statements is the same.
// This is important because it's possible for bzl rule classes defined in different files to
// have the same name string, and various part of Blaze rely on the pair of
// "rule class name string" and "bzl environment hash" to uniquely identify a bzl rule class.
// See b/226379109 for details.
@Override
protected synchronized byte[] getFastDigest(PathFragment path) {
return getDigest(path);
}
@Override
protected synchronized byte[] getDigest(PathFragment path) {
return getDigestFunction().getHashFunction().hashString(path.toString(), UTF_8).asBytes();
}
}
/** Builder for {@link ConfiguredRuleClassProvider}. */
public static final class Builder implements RuleDefinitionEnvironment {
private final StringBuilder defaultWorkspaceFilePrefix = new StringBuilder();
private final StringBuilder defaultWorkspaceFileSuffix = new StringBuilder();
private Label preludeLabel;
private String runfilesPrefix;
private RepositoryName toolsRepository;
@Nullable private String builtinsBzlZipResource;
private boolean useDummyBuiltinsBzlInsteadOfResource = false;
@Nullable private String builtinsBzlPackagePathInSource;
private final List<Class<? extends Fragment>> configurationFragmentClasses = new ArrayList<>();
private final List<Class<? extends FragmentOptions>> configurationOptions = new ArrayList<>();
private final Map<String, RuleClass> ruleClassMap = new HashMap<>();
private final Map<String, RuleDefinition> ruleDefinitionMap = new HashMap<>();
private final Map<String, NativeAspectClass> nativeAspectClassMap = new HashMap<>();
private final Map<Class<? extends RuleDefinition>, RuleClass> ruleMap = new HashMap<>();
private final Digraph<Class<? extends RuleDefinition>> dependencyGraph = new Digraph<>();
private final List<Class<? extends Fragment>> universalFragments = new ArrayList<>();
@Nullable private TransitionFactory<RuleTransitionData> trimmingTransitionFactory = null;
@Nullable private PatchTransition toolchainTaggedTrimmingTransition = null;
private OptionsDiffPredicate shouldInvalidateCacheForOptionDiff =
OptionsDiffPredicate.ALWAYS_INVALIDATE;
private PrerequisiteValidator prerequisiteValidator;
private final ImmutableMap.Builder<String, Object> buildFileToplevels = ImmutableMap.builder();
private final ImmutableList.Builder<Bootstrap> starlarkBootstraps = ImmutableList.builder();
private final ImmutableMap.Builder<String, Object> starlarkAccessibleTopLevels =
ImmutableMap.builder();
private final ImmutableMap.Builder<String, Object> starlarkBuiltinsInternals =
ImmutableMap.builder();
private final ImmutableList.Builder<SymlinkDefinition> symlinkDefinitions =
ImmutableList.builder();
private final Set<String> reservedActionMnemonics = new TreeSet<>();
private Function<BuildOptions, ActionEnvironment> actionEnvironmentProvider =
(BuildOptions options) -> ActionEnvironment.EMPTY;
private ConstraintSemantics<RuleContext> constraintSemantics =
new RuleContextConstraintSemantics();
// TODO(b/192694287): Remove once we migrate all tests from the allowlist
@Nullable private Label networkAllowlistForTests;
@CanIgnoreReturnValue
public Builder addWorkspaceFilePrefix(String contents) {
defaultWorkspaceFilePrefix.append(contents);
return this;
}
@CanIgnoreReturnValue
@VisibleForTesting
public Builder clearWorkspaceFilePrefixForTesting() {
defaultWorkspaceFilePrefix.delete(0, defaultWorkspaceFilePrefix.length());
return this;
}
@CanIgnoreReturnValue
public Builder addWorkspaceFileSuffix(String contents) {
defaultWorkspaceFileSuffix.append(contents);
return this;
}
@CanIgnoreReturnValue
@VisibleForTesting
public Builder clearWorkspaceFileSuffixForTesting() {
defaultWorkspaceFileSuffix.delete(0, defaultWorkspaceFileSuffix.length());
return this;
}
@CanIgnoreReturnValue
public Builder setPrelude(String preludeLabelString) {
Preconditions.checkArgument(
preludeLabelString.startsWith("//"),
"Prelude label '%s' must start with '//'",
preludeLabelString);
try {
// We're parsing this label as if it's in the main repository but it will actually get
// massaged into a label in the repository where the package being loaded resides.
this.preludeLabel = Label.parseCanonical(preludeLabelString);
} catch (LabelSyntaxException e) {
String errorMsg =
String.format("Prelude label '%s' is invalid: %s", preludeLabelString, e.getMessage());
throw new IllegalArgumentException(errorMsg);
}
return this;
}
@CanIgnoreReturnValue
public Builder setRunfilesPrefix(String runfilesPrefix) {
this.runfilesPrefix = runfilesPrefix;
return this;
}
@CanIgnoreReturnValue
public Builder setToolsRepository(RepositoryName toolsRepository) {
this.toolsRepository = toolsRepository;
return this;
}
/**
* Sets the resource path to the builtins_bzl.zip resource.
*
* <p>This value is required for production uses. For uses in tests, this may be left null, but
* the resulting rule class provider will not work with {@code
* --experimental_builtins_bzl_path=%bundled%}. Alternatively, tests may call {@link
* #useDummyBuiltinsBzl} if they do not rely on any native rules that may be migratable to
* Starlark.
*/
@CanIgnoreReturnValue
public Builder setBuiltinsBzlZipResource(String name) {
this.builtinsBzlZipResource = name;
this.useDummyBuiltinsBzlInsteadOfResource = false;
return this;
}
/**
* Instructs the rule class provider to use a set of dummy builtins definitions that inject no
* symbols.
*
* <p>This is only suitable for use in tests, and only when the test does not depend (even
* implicitly) on native rules. For example, pure tests of package loading behavior may call
* this method, but not tests that use AnalysisMock. Otherwise the test may break when a native
* rule is migrated to Starlark via builtins injection.
*/
@CanIgnoreReturnValue
public Builder useDummyBuiltinsBzl() {
this.builtinsBzlZipResource = null;
this.useDummyBuiltinsBzlInsteadOfResource = true;
return this;
}
/**
* Sets the relative location of the builtins_bzl directory within a Bazel source tree.
*
* <p>This is required if the rule class provider will be used with {@code
* --experimental_builtins_bzl_path=%workspace%}, but can be skipped in unit tests.
*/
@CanIgnoreReturnValue
public Builder setBuiltinsBzlPackagePathInSource(String path) {
this.builtinsBzlPackagePathInSource = path;
return this;
}
@CanIgnoreReturnValue
public Builder setPrerequisiteValidator(PrerequisiteValidator prerequisiteValidator) {
this.prerequisiteValidator = prerequisiteValidator;
return this;
}
@CanIgnoreReturnValue
public Builder addRuleDefinition(RuleDefinition ruleDefinition) {
Class<? extends RuleDefinition> ruleDefinitionClass = ruleDefinition.getClass();
ruleDefinitionMap.put(ruleDefinitionClass.getName(), ruleDefinition);
dependencyGraph.createNode(ruleDefinitionClass);
for (Class<? extends RuleDefinition> ancestor : ruleDefinition.getMetadata().ancestors()) {
dependencyGraph.addEdge(ancestor, ruleDefinitionClass);
}
return this;
}
@CanIgnoreReturnValue
public Builder addNativeAspectClass(NativeAspectClass aspectFactoryClass) {
nativeAspectClassMap.put(aspectFactoryClass.getName(), aspectFactoryClass);
return this;
}
/**
* Adds a configuration fragment and all build options required by its fragment.
*
* <p>Note that configuration fragments annotated with a Starlark name must have a unique name;
* no two different configuration fragments can share the same name.
*/
@CanIgnoreReturnValue
public Builder addConfigurationFragment(Class<? extends Fragment> fragmentClass) {
configurationFragmentClasses.add(fragmentClass);
return this;
}
/**
* Adds configuration options that aren't required by configuration fragments.
*
* <p>If {@link #addConfigurationFragment} adds a fragment that also requires these options,
* this method is redundant.
*/
@CanIgnoreReturnValue
public Builder addConfigurationOptions(Class<? extends FragmentOptions> configurationOptions) {
this.configurationOptions.add(configurationOptions);
return this;
}
@CanIgnoreReturnValue
public Builder addUniversalConfigurationFragment(Class<? extends Fragment> fragment) {
this.universalFragments.add(fragment);
addConfigurationFragment(fragment);
return this;
}
/**
* Registers a new top-level symbol for BUILD files.
*
* <p>The symbol will also be available in BUILD-loaded .bzl files under the {@code native}
* module.
*/
@CanIgnoreReturnValue
public Builder addBuildFileToplevel(String name, Object object) {
this.buildFileToplevels.put(name, object);
return this;
}
/**
* Registers all symbols contained in the {@code Bootstrap} as top-level symbols for .bzl files.
*/
@CanIgnoreReturnValue
public Builder addStarlarkBootstrap(Bootstrap bootstrap) {
this.starlarkBootstraps.add(bootstrap);
return this;
}
/** Registers a new top-level symbol for .bzl files. */
@CanIgnoreReturnValue
public Builder addBzlToplevel(String name, Object object) {
this.starlarkAccessibleTopLevels.put(name, object);
return this;
}
/**
* Registers a new symbol for {@code @_builtins} .bzl files, to be made available under the
* {@code _builtins.internal} object.
*/
@CanIgnoreReturnValue
public Builder addStarlarkBuiltinsInternal(String name, Object object) {
this.starlarkBuiltinsInternals.put(name, object);
return this;
}
@CanIgnoreReturnValue
public Builder addSymlinkDefinition(SymlinkDefinition symlinkDefinition) {
this.symlinkDefinitions.add(symlinkDefinition);
return this;
}
@CanIgnoreReturnValue
public Builder addReservedActionMnemonic(String mnemonic) {
this.reservedActionMnemonics.add(mnemonic);
return this;
}
@CanIgnoreReturnValue
public Builder setActionEnvironmentProvider(
Function<BuildOptions, ActionEnvironment> actionEnvironmentProvider) {
this.actionEnvironmentProvider = actionEnvironmentProvider;
return this;
}
/**
* Sets the logic that lets rules declare which environments they support and validates rules
* don't depend on rules that aren't compatible with the same environments. Defaults to {@link
* ConstraintSemantics}. See {@link ConstraintSemantics} for more details.
*/
@CanIgnoreReturnValue
public Builder setConstraintSemantics(ConstraintSemantics<RuleContext> constraintSemantics) {
this.constraintSemantics = constraintSemantics;
return this;
}
/**
* Adds a transition factory that produces a trimming transition to be run over all targets
* after other transitions.
*
* <p>Transitions are run in the order they're added.
*
* <p>This is a temporary measure for supporting trimming of test rules and manual trimming of
* feature flags, and support for this transition factory will likely be removed at some point
* in the future (whenever automatic trimming is sufficiently workable).
*/
@CanIgnoreReturnValue
public Builder addTrimmingTransitionFactory(TransitionFactory<RuleTransitionData> factory) {
Preconditions.checkNotNull(factory);
Preconditions.checkArgument(!factory.isSplit());
if (trimmingTransitionFactory == null) {
trimmingTransitionFactory = factory;
} else {
trimmingTransitionFactory =
ComposingTransitionFactory.of(trimmingTransitionFactory, factory);
}
return this;
}
/** Sets the transition manual feature flag trimming should apply to toolchain deps. */
@CanIgnoreReturnValue
public Builder setToolchainTaggedTrimmingTransition(PatchTransition transition) {
Preconditions.checkNotNull(transition);
Preconditions.checkState(toolchainTaggedTrimmingTransition == null);
this.toolchainTaggedTrimmingTransition = transition;
return this;
}
/**
* Overrides the transition factory run over all targets.
*
* @see #addTrimmingTransitionFactory(TransitionFactory)
*/
@VisibleForTesting(/* for testing trimming transition factories without relying on prod use */ )
public Builder overrideTrimmingTransitionFactoryForTesting(
TransitionFactory<RuleTransitionData> factory) {
trimmingTransitionFactory = null;
return this.addTrimmingTransitionFactory(factory);
}
/**
* Sets the predicate which determines whether the analysis cache should be invalidated for the
* given options diff.
*/
@CanIgnoreReturnValue
public Builder setShouldInvalidateCacheForOptionDiff(
OptionsDiffPredicate shouldInvalidateCacheForOptionDiff) {
Preconditions.checkState(
this.shouldInvalidateCacheForOptionDiff.equals(OptionsDiffPredicate.ALWAYS_INVALIDATE),
"Cache invalidation function was already set");
this.shouldInvalidateCacheForOptionDiff = shouldInvalidateCacheForOptionDiff;
return this;
}
/**
* Overrides the predicate which determines whether the analysis cache should be invalidated for
* the given options diff.
*/
@VisibleForTesting(/* for testing cache invalidation without relying on prod use */ )
Builder overrideShouldInvalidateCacheForOptionDiffForTesting(
OptionsDiffPredicate shouldInvalidateCacheForOptionDiff) {
this.shouldInvalidateCacheForOptionDiff = OptionsDiffPredicate.ALWAYS_INVALIDATE;
return this.setShouldInvalidateCacheForOptionDiff(shouldInvalidateCacheForOptionDiff);
}
private static RuleConfiguredTargetFactory createFactory(
Class<? extends RuleConfiguredTargetFactory> factoryClass) {
try {
Constructor<? extends RuleConfiguredTargetFactory> ctor = factoryClass.getConstructor();
return ctor.newInstance();
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(e);
}
}
private void commitRuleDefinition(Class<? extends RuleDefinition> definitionClass) {
RuleDefinition instance =
checkNotNull(
ruleDefinitionMap.get(definitionClass.getName()),
"addRuleDefinition(new %s()) should be called before build()",
definitionClass.getName());
RuleDefinition.Metadata metadata = instance.getMetadata();
checkArgument(
ruleClassMap.get(metadata.name()) == null,
"The rule %s was committed already, use another name",
metadata.name());
List<Class<? extends RuleDefinition>> ancestors = metadata.ancestors();
checkArgument(
metadata.type() == ABSTRACT
^ metadata.factoryClass() != RuleConfiguredTargetFactory.class);
checkArgument(
(metadata.type() != TEST) || ancestors.contains(BaseRuleClasses.TestBaseRule.class));
RuleClass[] ancestorClasses = new RuleClass[ancestors.size()];
for (int i = 0; i < ancestorClasses.length; i++) {
ancestorClasses[i] = ruleMap.get(ancestors.get(i));
if (ancestorClasses[i] == null) {
// Ancestors should have been initialized by now
throw new IllegalStateException(
"Ancestor " + ancestors.get(i) + " of " + metadata.name() + " is not initialized");
}
}
RuleConfiguredTargetFactory factory = null;
if (metadata.type() != ABSTRACT) {
factory = createFactory(metadata.factoryClass());
}
RuleClass.Builder builder =
new RuleClass.Builder(metadata.name(), metadata.type(), false, ancestorClasses);
builder.factory(factory);
RuleClass ruleClass = instance.build(builder, this);
ruleMap.put(definitionClass, ruleClass);
ruleClassMap.put(ruleClass.getName(), ruleClass);
ruleDefinitionMap.put(ruleClass.getName(), instance);
}
/**
* Locates the builtins zip file as a Java resource, and unpacks it into the given directory.
* Note that the builtins_bzl/ entry itself in the zip is not copied, just its children.
*/
private static void unpackBuiltinsBzlZipResource(String builtinsResourceName, Path targetRoot) {
ClassLoader loader = ConfiguredRuleClassProvider.class.getClassLoader();
try (InputStream builtinsZip = loader.getResourceAsStream(builtinsResourceName)) {
Preconditions.checkArgument(
builtinsZip != null, "No resource with name %s", builtinsResourceName);
try (ZipInputStream zip = new ZipInputStream(builtinsZip)) {
for (ZipEntry entry = zip.getNextEntry(); entry != null; entry = zip.getNextEntry()) {
String entryName = entry.getName();
Preconditions.checkArgument(entryName.startsWith("builtins_bzl/"));
Path dest = targetRoot.getRelative(entryName.substring("builtins_bzl/".length()));
dest.getParentDirectory().createDirectoryAndParents();
try (OutputStream os = dest.getOutputStream()) {
ByteStreams.copy(zip, os);
}
}
}
} catch (IOException ex) {
throw new IllegalArgumentException(
"Error while unpacking builtins_bzl zip resource file", ex);
}
}
public ConfiguredRuleClassProvider build() {
for (Node<Class<? extends RuleDefinition>> ruleDefinition :
dependencyGraph.getTopologicalOrder()) {
commitRuleDefinition(ruleDefinition.getLabel());
}
// Determine the bundled builtins root, if it exists.
Root builtinsRoot;
if (builtinsBzlZipResource == null && !useDummyBuiltinsBzlInsteadOfResource) {
// Use of --experimental_builtins_bzl_path=%bundled% is disallowed.
builtinsRoot = null;
} else {
BundledFileSystem fs = new BundledFileSystem();
Path builtinsPath = fs.getPath("/virtual_builtins_bzl");
if (builtinsBzlZipResource != null) {
// Production case.
unpackBuiltinsBzlZipResource(builtinsBzlZipResource, builtinsPath);
} else {
// Dummy case, use empty bundled builtins content.
try {
builtinsPath.createDirectoryAndParents();
try (OutputStream os = builtinsPath.getRelative("exports.bzl").getOutputStream()) {
String emptyExports =
("exported_rules = {}\n" //
+ "exported_toplevels = {}\n"
+ "exported_to_java = {}\n");
os.write(emptyExports.getBytes(UTF_8));
}
} catch (IOException ex) {
throw new IllegalStateException("Failed to write dummy builtins root", ex);
}
}
builtinsRoot = Root.fromPath(builtinsPath);
}
return new ConfiguredRuleClassProvider(
preludeLabel,
runfilesPrefix,
toolsRepository,
builtinsRoot,
builtinsBzlPackagePathInSource,
ImmutableMap.copyOf(ruleClassMap),
ImmutableMap.copyOf(ruleDefinitionMap),
ImmutableMap.copyOf(nativeAspectClassMap),
FragmentRegistry.create(
configurationFragmentClasses, universalFragments, configurationOptions),
defaultWorkspaceFilePrefix.toString(),
defaultWorkspaceFileSuffix.toString(),
trimmingTransitionFactory,
toolchainTaggedTrimmingTransition,
shouldInvalidateCacheForOptionDiff,
prerequisiteValidator,
buildFileToplevels.buildOrThrow(),
starlarkAccessibleTopLevels.buildOrThrow(),
starlarkBuiltinsInternals.buildOrThrow(),
starlarkBootstraps.build(),
symlinkDefinitions.build(),
ImmutableSet.copyOf(reservedActionMnemonics),
actionEnvironmentProvider,
constraintSemantics,
networkAllowlistForTests);
}
@Override
public RepositoryName getToolsRepository() {
return toolsRepository;
}
@Override
public Optional<Label> getNetworkAllowlistForTests() {
return Optional.ofNullable(networkAllowlistForTests);
}
@CanIgnoreReturnValue
public Builder setNetworkAllowlistForTests(Label allowlist) {
networkAllowlistForTests = allowlist;
return this;
}
}
/** Default content that should be added at the beginning of the WORKSPACE file. */
private final String defaultWorkspaceFilePrefix;
/** Default content that should be added at the end of the WORKSPACE file. */
private final String defaultWorkspaceFileSuffix;
/** Label for the prelude file. */
private final Label preludeLabel;
/** The default runfiles prefix. */
private final String runfilesPrefix;
/** The path to the tools repository. */
private final RepositoryName toolsRepository;
/**
* Where the builtins bzl files are located (if not overridden by
* --experimental_builtins_bzl_path). Note that this lives in a separate InMemoryFileSystem.
*
* <p>May be null in tests, in which case --experimental_builtins_bzl_path must point to a
* builtins root.
*/
@Nullable private final Root bundledBuiltinsRoot;
/**
* The relative location of the builtins_bzl directory within a Bazel source tree.
*
* <p>May be null in tests, in which case --experimental_builtins_bzl_path may not be
* "%workspace%".
*/
@Nullable private final String builtinsBzlPackagePathInSource;
/** Maps rule class name to the metaclass instance for that rule. */
private final ImmutableMap<String, RuleClass> ruleClassMap;
/** Maps rule class name to the rule definition objects. */
private final ImmutableMap<String, RuleDefinition> ruleDefinitionMap;
/** Maps aspect name to the aspect factory meta class. */
private final ImmutableMap<String, NativeAspectClass> nativeAspectClassMap;
private final FragmentRegistry fragmentRegistry;
/** The transition factory used to produce the transition that will trim targets. */
@Nullable private final TransitionFactory<RuleTransitionData> trimmingTransitionFactory;
/** The transition to apply to toolchain deps for manual trimming. */
@Nullable private final PatchTransition toolchainTaggedTrimmingTransition;
/** The predicate used to determine whether a diff requires the cache to be invalidated. */
private final OptionsDiffPredicate shouldInvalidateCacheForOptionDiff;
private final PrerequisiteValidator prerequisiteValidator;
private final BazelStarlarkEnvironment bazelStarlarkEnvironment;
private final ImmutableList<SymlinkDefinition> symlinkDefinitions;
private final ImmutableSet<String> reservedActionMnemonics;
private final Function<BuildOptions, ActionEnvironment> actionEnvironmentProvider;
private final ImmutableMap<String, Class<?>> configurationFragmentMap;
private final ConstraintSemantics<RuleContext> constraintSemantics;
// TODO(b/192694287): Remove once we migrate all tests from the allowlist
@Nullable private final Label networkAllowlistForTests;
private ConfiguredRuleClassProvider(
Label preludeLabel,
String runfilesPrefix,
RepositoryName toolsRepository,
@Nullable Root bundledBuiltinsRoot,
@Nullable String builtinsBzlPackagePathInSource,
ImmutableMap<String, RuleClass> ruleClassMap,
ImmutableMap<String, RuleDefinition> ruleDefinitionMap,
ImmutableMap<String, NativeAspectClass> nativeAspectClassMap,
FragmentRegistry fragmentRegistry,
String defaultWorkspaceFilePrefix,
String defaultWorkspaceFileSuffix,
@Nullable TransitionFactory<RuleTransitionData> trimmingTransitionFactory,
PatchTransition toolchainTaggedTrimmingTransition,
OptionsDiffPredicate shouldInvalidateCacheForOptionDiff,
PrerequisiteValidator prerequisiteValidator,
ImmutableMap<String, Object> buildFileToplevels,
ImmutableMap<String, Object> starlarkAccessibleTopLevels,
ImmutableMap<String, Object> starlarkBuiltinsInternals,
ImmutableList<Bootstrap> starlarkBootstraps,
ImmutableList<SymlinkDefinition> symlinkDefinitions,
ImmutableSet<String> reservedActionMnemonics,
Function<BuildOptions, ActionEnvironment> actionEnvironmentProvider,
ConstraintSemantics<RuleContext> constraintSemantics,
@Nullable Label networkAllowlistForTests) {
this.preludeLabel = preludeLabel;
this.runfilesPrefix = runfilesPrefix;
this.toolsRepository = toolsRepository;
this.bundledBuiltinsRoot = bundledBuiltinsRoot;
this.builtinsBzlPackagePathInSource = builtinsBzlPackagePathInSource;
this.ruleClassMap = ruleClassMap;
this.ruleDefinitionMap = ruleDefinitionMap;
this.nativeAspectClassMap = nativeAspectClassMap;
this.fragmentRegistry = fragmentRegistry;
this.defaultWorkspaceFilePrefix = defaultWorkspaceFilePrefix;
this.defaultWorkspaceFileSuffix = defaultWorkspaceFileSuffix;
this.trimmingTransitionFactory = trimmingTransitionFactory;
this.toolchainTaggedTrimmingTransition = toolchainTaggedTrimmingTransition;
this.shouldInvalidateCacheForOptionDiff = shouldInvalidateCacheForOptionDiff;
this.prerequisiteValidator = prerequisiteValidator;
this.symlinkDefinitions = symlinkDefinitions;
this.reservedActionMnemonics = reservedActionMnemonics;
this.actionEnvironmentProvider = actionEnvironmentProvider;
this.configurationFragmentMap = createFragmentMap(fragmentRegistry.getAllFragments());
this.constraintSemantics = constraintSemantics;
this.networkAllowlistForTests = networkAllowlistForTests;
ImmutableMap<String, Object> registeredBzlToplevels =
createRegisteredBzlToplevels(starlarkAccessibleTopLevels, starlarkBootstraps);
// If needed, we could allow the version to be customized by the builder e.g. for unit testing,
// but at the moment it suffices to use the production value unconditionally.
String version = BlazeVersionInfo.instance().getVersion();
this.bazelStarlarkEnvironment =
new BazelStarlarkEnvironment(
StarlarkGlobalsImpl.INSTANCE,
/* ruleFunctions= */ RuleFactory.buildRuleFunctions(ruleClassMap),
buildFileToplevels,
registeredBzlToplevels,
/* workspaceBzlNativeBindings= */ WorkspaceFactory.createNativeModuleBindings(
ruleClassMap, version),
/* builtinsInternals= */ starlarkBuiltinsInternals);
}
public PrerequisiteValidator getPrerequisiteValidator() {
return prerequisiteValidator;
}
@Override
public Label getPreludeLabel() {
return preludeLabel;
}
@Override
public String getRunfilesPrefix() {
return runfilesPrefix;
}
@Override
public RepositoryName getToolsRepository() {
return toolsRepository;
}
@Override
@Nullable
public Root getBundledBuiltinsRoot() {
return bundledBuiltinsRoot;
}
@Override
@Nullable
public String getBuiltinsBzlPackagePathInSource() {
return builtinsBzlPackagePathInSource;
}
@Override
public ImmutableMap<String, RuleClass> getRuleClassMap() {
return ruleClassMap;
}
@Override
public Map<String, NativeAspectClass> getNativeAspectClassMap() {
return nativeAspectClassMap;
}
@Override
public NativeAspectClass getNativeAspectClass(String key) {
return nativeAspectClassMap.get(key);
}
@Override
public FragmentRegistry getFragmentRegistry() {
return fragmentRegistry;
}
/**
* Returns the transition factory used to produce the transition to trim targets.
*
* <p>This is a temporary measure for supporting manual trimming of feature flags, and support for
* this transition factory will likely be removed at some point in the future (whenever automatic
* trimming is sufficiently workable
*/
@Nullable
public TransitionFactory<RuleTransitionData> getTrimmingTransitionFactory() {
return trimmingTransitionFactory;
}
/**
* Returns the transition manual feature flag trimming should apply to toolchain deps.
*
* <p>See extra notes on {@link #getTrimmingTransitionFactory()}.
*/
@Nullable
public PatchTransition getToolchainTaggedTrimmingTransition() {
return toolchainTaggedTrimmingTransition;
}
/** Returns whether the analysis cache should be invalidated for the given option diff. */
public boolean shouldInvalidateCacheForOptionDiff(
BuildOptions newOptions, OptionDefinition changedOption, Object oldValue, Object newValue) {
return shouldInvalidateCacheForOptionDiff.apply(newOptions, changedOption, oldValue, newValue);
}
/** Returns the definition of the rule class definition with the specified name. */
public RuleDefinition getRuleClassDefinition(String ruleClassName) {
return ruleDefinitionMap.get(ruleClassName);
}
private static ImmutableMap<String, Object> createRegisteredBzlToplevels(
ImmutableMap<String, Object> starlarkAccessibleTopLevels,
ImmutableList<Bootstrap> bootstraps) {
ImmutableMap.Builder<String, Object> bindings = ImmutableMap.builder();
bindings.putAll(starlarkAccessibleTopLevels);
for (Bootstrap bootstrap : bootstraps) {
bootstrap.addBindingsToBuilder(bindings);
}
return bindings.buildOrThrow();
}
private static ImmutableMap<String, Class<?>> createFragmentMap(
FragmentClassSet configurationFragments) {
ImmutableMap.Builder<String, Class<?>> mapBuilder = ImmutableMap.builder();
for (Class<? extends Fragment> fragmentClass : configurationFragments) {
StarlarkBuiltin fragmentModule = StarlarkAnnotations.getStarlarkBuiltin(fragmentClass);
if (fragmentModule != null) {
mapBuilder.put(fragmentModule.name(), fragmentClass);
}
}
return mapBuilder.buildOrThrow();
}
@Override
public BazelStarlarkEnvironment getBazelStarlarkEnvironment() {
return bazelStarlarkEnvironment;
}
@Override
public String getDefaultWorkspacePrefix() {
return defaultWorkspaceFilePrefix;
}
@Override
public String getDefaultWorkspaceSuffix() {
return defaultWorkspaceFileSuffix;
}
@Override
public ImmutableMap<String, Class<?>> getConfigurationFragmentMap() {
return configurationFragmentMap;
}
/**
* Returns the symlink definitions introduced by the fragments registered with this rule class
* provider.
*
* <p>This only includes definitions added by {@link Builder#addSymlinkDefinition}, not the
* standard symlinks in {@link com.google.devtools.build.lib.buildtool.OutputDirectoryLinksUtils}.
*
* <p>Note: Usages of custom symlink definitions should be very rare. This feature was added to
* implement the py2-bin / py3-bin symlinks, which have since been removed from Bazel.
*/
// TODO(bazel-team): Delete?
public ImmutableList<SymlinkDefinition> getSymlinkDefinitions() {
return symlinkDefinitions;
}
public ConstraintSemantics<RuleContext> getConstraintSemantics() {
return constraintSemantics;
}
@Override
public Optional<Label> getNetworkAllowlistForTests() {
return Optional.ofNullable(networkAllowlistForTests);
}
/** Returns a reserved set of action mnemonics. These cannot be used from a Starlark action. */
@Override
public ImmutableSet<String> getReservedActionMnemonics() {
return reservedActionMnemonics;
}
@Override
public ActionEnvironment getActionEnvironment(BuildOptions buildOptions) {
return actionEnvironmentProvider.apply(buildOptions);
}
}