| // 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); | |
| } | |
| } |