blob: fb023bbf2775daeed3e464d675b7e11202b499e7 [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 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.collect.ImmutableSortedSet;
import com.google.devtools.build.lib.actions.ActionEnvironment;
import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory;
import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory.BuildInfoKey;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory;
import com.google.devtools.build.lib.analysis.config.ConvenienceSymlinks.SymlinkDefinition;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
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.skylark.SkylarkModules;
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.Attribute;
import com.google.devtools.build.lib.packages.BazelStarlarkContext;
import com.google.devtools.build.lib.packages.NativeAspectClass;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleClass.Builder.ThirdPartyLicenseExistencePolicy;
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.packages.SymbolGenerator;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
import com.google.devtools.build.lib.skylarkbuildapi.core.Bootstrap;
import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.syntax.ClassObject;
import com.google.devtools.build.lib.syntax.Module;
import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.StarlarkSemantics;
import com.google.devtools.build.lib.syntax.StarlarkThread;
import com.google.devtools.build.lib.syntax.StarlarkThread.Extension;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDefinition;
import com.google.devtools.common.options.OptionsProvider;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.annotation.Nullable;
/**
* 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 {
/**
* Predicate for determining whether the analysis cache should be cleared, given the new and old
* value of an option which has changed and the values of the other new options.
*/
@FunctionalInterface
public interface OptionsDiffPredicate {
public static final OptionsDiffPredicate ALWAYS_INVALIDATE =
(options, option, oldValue, newValue) -> true;
public boolean apply(
BuildOptions newOptions, OptionDefinition option, Object oldValue, Object newValue);
}
/**
* Custom dependency validation logic.
*/
public interface PrerequisiteValidator {
/**
* Checks whether the rule in {@code contextBuilder} is allowed to depend on {@code
* prerequisite} through the attribute {@code attribute}.
*
* <p>Can be used for enforcing any organization-specific policies about the layout of the
* workspace.
*/
void validate(
RuleContext.Builder contextBuilder,
ConfiguredTargetAndData prerequisite,
Attribute attribute);
}
/**
* 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. */
ImmutableList<RuleSet> requires();
}
/** Builder for {@link ConfiguredRuleClassProvider}. */
public static class Builder implements RuleDefinitionEnvironment {
private final StringBuilder defaultWorkspaceFilePrefix = new StringBuilder();
private final StringBuilder defaultWorkspaceFileSuffix = new StringBuilder();
private Label preludeLabel;
private String runfilesPrefix;
private String toolsRepository;
private final List<ConfigurationFragmentFactory> configurationFragmentFactories =
new ArrayList<>();
private final List<BuildInfoFactory> buildInfoFactories = new ArrayList<>();
private final Set<Class<? extends FragmentOptions>> configurationOptions =
new LinkedHashSet<>();
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<Rule> trimmingTransitionFactory = null;
@Nullable private PatchTransition toolchainTaggedTrimmingTransition = null;
private OptionsDiffPredicate shouldInvalidateCacheForOptionDiff =
OptionsDiffPredicate.ALWAYS_INVALIDATE;
private PrerequisiteValidator prerequisiteValidator;
private final ImmutableList.Builder<Bootstrap> skylarkBootstraps = ImmutableList.builder();
private ImmutableMap.Builder<String, Object> skylarkAccessibleTopLevels =
ImmutableMap.builder();
private final ImmutableList.Builder<SymlinkDefinition> symlinkDefinitions =
ImmutableList.builder();
private Set<String> reservedActionMnemonics = new TreeSet<>();
private BuildConfiguration.ActionEnvironmentProvider actionEnvironmentProvider =
(BuildOptions options) -> ActionEnvironment.EMPTY;
private ConstraintSemantics constraintSemantics = new ConstraintSemantics();
private ThirdPartyLicenseExistencePolicy thirdPartyLicenseExistencePolicy =
ThirdPartyLicenseExistencePolicy.USER_CONTROLLABLE;
private boolean enableExecutionTransition = false;
public Builder addWorkspaceFilePrefix(String contents) {
defaultWorkspaceFilePrefix.append(contents);
return this;
}
public Builder addWorkspaceFileSuffix(String contents) {
defaultWorkspaceFileSuffix.append(contents);
return this;
}
@VisibleForTesting
public Builder clearWorkspaceFileSuffixForTesting() {
defaultWorkspaceFileSuffix.delete(0, defaultWorkspaceFileSuffix.length());
return this;
}
public Builder setPrelude(String preludeLabelString) {
try {
this.preludeLabel = Label.parseAbsolute(preludeLabelString, ImmutableMap.of());
} catch (LabelSyntaxException e) {
String errorMsg =
String.format("Prelude label '%s' is invalid: %s", preludeLabelString, e.getMessage());
throw new IllegalArgumentException(errorMsg);
}
return this;
}
public Builder setRunfilesPrefix(String runfilesPrefix) {
this.runfilesPrefix = runfilesPrefix;
return this;
}
public Builder setToolsRepository(String toolsRepository) {
this.toolsRepository = toolsRepository;
return this;
}
public Builder setPrerequisiteValidator(PrerequisiteValidator prerequisiteValidator) {
this.prerequisiteValidator = prerequisiteValidator;
return this;
}
public Builder addBuildInfoFactory(BuildInfoFactory factory) {
buildInfoFactories.add(factory);
return this;
}
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;
}
public Builder addNativeAspectClass(NativeAspectClass aspectFactoryClass) {
nativeAspectClassMap.put(aspectFactoryClass.getName(), aspectFactoryClass);
return this;
}
/**
* Adds a configuration fragment factory and all build options required by its fragment.
*
* <p>Note that configuration fragments annotated with a Skylark name must have a unique name;
* no two different configuration fragments can share the same name.
*/
public Builder addConfigurationFragment(ConfigurationFragmentFactory factory) {
this.configurationOptions.addAll(factory.requiredOptions());
configurationFragmentFactories.add(factory);
return this;
}
/**
* Adds configuration options that aren't required by configuration fragments.
*
* <p>If {@link #addConfigurationFragment(ConfigurationFragmentFactory)} adds a fragment factory
* that also requires these options, this method is redundant.
*/
public Builder addConfigurationOptions(Class<? extends FragmentOptions> configurationOptions) {
this.configurationOptions.add(configurationOptions);
return this;
}
public Builder addUniversalConfigurationFragment(Class<? extends Fragment> fragment) {
this.universalFragments.add(fragment);
return this;
}
public Builder addSkylarkBootstrap(Bootstrap bootstrap) {
this.skylarkBootstraps.add(bootstrap);
return this;
}
public Builder addSkylarkAccessibleTopLevels(String name, Object object) {
this.skylarkAccessibleTopLevels.put(name, object);
return this;
}
public Builder addSymlinkDefinition(SymlinkDefinition symlinkDefinition) {
this.symlinkDefinitions.add(symlinkDefinition);
return this;
}
public Builder addReservedActionMnemonic(String mnemonic) {
this.reservedActionMnemonics.add(mnemonic);
return this;
}
public Builder setActionEnvironmentProvider(
BuildConfiguration.ActionEnvironmentProvider 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
* {@ConstraintSemantics}. See {@ConstraintSemantics} for more details.
*/
public Builder setConstraintSemantics(ConstraintSemantics constraintSemantics) {
this.constraintSemantics = constraintSemantics;
return this;
}
/**
* Sets the policy for checking if third_party rules declare <code>licenses()</code>. See {@link
* #thirdPartyLicenseExistencePolicy} for the default value.
*/
public Builder setThirdPartyLicenseExistencePolicy(ThirdPartyLicenseExistencePolicy policy) {
this.thirdPartyLicenseExistencePolicy = policy;
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).
*/
public Builder addTrimmingTransitionFactory(TransitionFactory<Rule> 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. */
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 {@link #addTrimmingTransitionFactory(TransitionFactory<Rule>)}
*/
@VisibleForTesting(/* for testing trimming transition factories without relying on prod use */ )
public Builder overrideTrimmingTransitionFactoryForTesting(TransitionFactory<Rule> factory) {
trimmingTransitionFactory = null;
return this.addTrimmingTransitionFactory(factory);
}
/**
* Sets the predicate which determines whether the analysis cache should be invalidated for the
* given options diff.
*/
public Builder setShouldInvalidateCacheForOptionDiff(
OptionsDiffPredicate shouldInvalidateCacheForOptionDiff) {
Preconditions.checkState(
this.shouldInvalidateCacheForOptionDiff.equals(OptionsDiffPredicate.ALWAYS_INVALIDATE),
"Cache invalidation function was already set");
this.shouldInvalidateCacheForOptionDiff = shouldInvalidateCacheForOptionDiff;
return this;
}
@Override
public boolean enableExecutionTransition() {
return enableExecutionTransition;
}
public Builder enableExecutionTransition(boolean flag) {
this.enableExecutionTransition = flag;
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 */ )
public Builder overrideShouldInvalidateCacheForOptionDiffForTesting(
OptionsDiffPredicate shouldInvalidateCacheForOptionDiff) {
this.shouldInvalidateCacheForOptionDiff = OptionsDiffPredicate.ALWAYS_INVALIDATE;
return this.setShouldInvalidateCacheForOptionDiff(shouldInvalidateCacheForOptionDiff);
}
private RuleConfiguredTargetFactory createFactory(
Class<? extends RuleConfiguredTargetFactory> factoryClass) {
try {
Constructor<? extends RuleConfiguredTargetFactory> ctor = factoryClass.getConstructor();
return ctor.newInstance();
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException
| InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
private RuleClass 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 " + metadata.name() + " was committed already, use another 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);
builder.setThirdPartyLicenseExistencePolicy(thirdPartyLicenseExistencePolicy);
RuleClass ruleClass = instance.build(builder, this);
ruleMap.put(definitionClass, ruleClass);
ruleClassMap.put(ruleClass.getName(), ruleClass);
ruleDefinitionMap.put(ruleClass.getName(), instance);
return ruleClass;
}
public ConfiguredRuleClassProvider build() {
for (Node<Class<? extends RuleDefinition>> ruleDefinition :
dependencyGraph.getTopologicalOrder()) {
commitRuleDefinition(ruleDefinition.getLabel());
}
return new ConfiguredRuleClassProvider(
preludeLabel,
runfilesPrefix,
toolsRepository,
ImmutableMap.copyOf(ruleClassMap),
ImmutableMap.copyOf(ruleDefinitionMap),
ImmutableMap.copyOf(nativeAspectClassMap),
defaultWorkspaceFilePrefix.toString(),
defaultWorkspaceFileSuffix.toString(),
ImmutableList.copyOf(buildInfoFactories),
ImmutableList.copyOf(configurationOptions),
ImmutableList.copyOf(configurationFragmentFactories),
ImmutableList.copyOf(universalFragments),
trimmingTransitionFactory,
toolchainTaggedTrimmingTransition,
shouldInvalidateCacheForOptionDiff,
prerequisiteValidator,
skylarkAccessibleTopLevels.build(),
skylarkBootstraps.build(),
symlinkDefinitions.build(),
ImmutableSet.copyOf(reservedActionMnemonics),
actionEnvironmentProvider,
constraintSemantics,
thirdPartyLicenseExistencePolicy);
}
@Override
public Label getToolsLabel(String labelValue) {
return Label.parseAbsoluteUnchecked(toolsRepository + labelValue);
}
@Override
public String getToolsRepository() {
return toolsRepository;
}
}
/**
* 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 String toolsRepository;
/**
* 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;
/**
* The configuration options that affect the behavior of the rules.
*/
private final ImmutableList<Class<? extends FragmentOptions>> configurationOptions;
/** The set of configuration fragment factories. */
private final ImmutableList<ConfigurationFragmentFactory> configurationFragmentFactories;
/**
* Maps build option names to matching config fragments. This is used to determine correct
* fragment requirements for config_setting rules, which are unique in that their dependencies are
* triggered by string representations of option names.
*/
private final Map<String, Class<? extends Fragment>> optionsToFragmentMap;
/** The transition factory used to produce the transition that will trim targets. */
@Nullable private final TransitionFactory<Rule> 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;
/**
* Configuration fragments that should be available to all rules even when they don't explicitly
* require it.
*/
private final ImmutableList<Class<? extends Fragment>> universalFragments;
private final ImmutableList<BuildInfoFactory> buildInfoFactories;
private final PrerequisiteValidator prerequisiteValidator;
private final ImmutableMap<String, Object> environment;
private final ImmutableList<SymlinkDefinition> symlinkDefinitions;
private final ImmutableSet<String> reservedActionMnemonics;
private final BuildConfiguration.ActionEnvironmentProvider actionEnvironmentProvider;
private final ImmutableMap<String, Class<?>> configurationFragmentMap;
private final ConstraintSemantics constraintSemantics;
private final ThirdPartyLicenseExistencePolicy thirdPartyLicenseExistencePolicy;
private ConfiguredRuleClassProvider(
Label preludeLabel,
String runfilesPrefix,
String toolsRepository,
ImmutableMap<String, RuleClass> ruleClassMap,
ImmutableMap<String, RuleDefinition> ruleDefinitionMap,
ImmutableMap<String, NativeAspectClass> nativeAspectClassMap,
String defaultWorkspaceFilePrefix,
String defaultWorkspaceFileSuffix,
ImmutableList<BuildInfoFactory> buildInfoFactories,
ImmutableList<Class<? extends FragmentOptions>> configurationOptions,
ImmutableList<ConfigurationFragmentFactory> configurationFragments,
ImmutableList<Class<? extends Fragment>> universalFragments,
@Nullable TransitionFactory<Rule> trimmingTransitionFactory,
PatchTransition toolchainTaggedTrimmingTransition,
OptionsDiffPredicate shouldInvalidateCacheForOptionDiff,
PrerequisiteValidator prerequisiteValidator,
ImmutableMap<String, Object> skylarkAccessibleJavaClasses,
ImmutableList<Bootstrap> skylarkBootstraps,
ImmutableList<SymlinkDefinition> symlinkDefinitions,
ImmutableSet<String> reservedActionMnemonics,
BuildConfiguration.ActionEnvironmentProvider actionEnvironmentProvider,
ConstraintSemantics constraintSemantics,
ThirdPartyLicenseExistencePolicy thirdPartyLicenseExistencePolicy) {
this.preludeLabel = preludeLabel;
this.runfilesPrefix = runfilesPrefix;
this.toolsRepository = toolsRepository;
this.ruleClassMap = ruleClassMap;
this.ruleDefinitionMap = ruleDefinitionMap;
this.nativeAspectClassMap = nativeAspectClassMap;
this.defaultWorkspaceFilePrefix = defaultWorkspaceFilePrefix;
this.defaultWorkspaceFileSuffix = defaultWorkspaceFileSuffix;
this.buildInfoFactories = buildInfoFactories;
this.configurationOptions = configurationOptions;
this.configurationFragmentFactories = configurationFragments;
this.optionsToFragmentMap = computeOptionsToFragmentMap(configurationFragments);
this.universalFragments = universalFragments;
this.trimmingTransitionFactory = trimmingTransitionFactory;
this.toolchainTaggedTrimmingTransition = toolchainTaggedTrimmingTransition;
this.shouldInvalidateCacheForOptionDiff = shouldInvalidateCacheForOptionDiff;
this.prerequisiteValidator = prerequisiteValidator;
this.environment = createEnvironment(skylarkAccessibleJavaClasses, skylarkBootstraps);
this.symlinkDefinitions = symlinkDefinitions;
this.reservedActionMnemonics = reservedActionMnemonics;
this.actionEnvironmentProvider = actionEnvironmentProvider;
this.configurationFragmentMap = createFragmentMap(configurationFragments);
this.constraintSemantics = constraintSemantics;
this.thirdPartyLicenseExistencePolicy = thirdPartyLicenseExistencePolicy;
}
/**
* Computes the option name --> config fragments map. Note that this mapping is technically
* one-to-many: a single option may be required by multiple fragments (e.g. Java options are used
* by both JavaConfiguration and Jvm). In such cases, we arbitrarily choose one fragment since
* that's all that's needed to satisfy the config_setting.
*/
private static Map<String, Class<? extends Fragment>> computeOptionsToFragmentMap(
Iterable<ConfigurationFragmentFactory> configurationFragments) {
Map<String, Class<? extends Fragment>> result = new LinkedHashMap<>();
Map<Class<? extends FragmentOptions>, Integer> visitedOptionsClasses = new HashMap<>();
for (ConfigurationFragmentFactory factory : configurationFragments) {
Set<Class<? extends FragmentOptions>> requiredOpts = factory.requiredOptions();
for (Class<? extends FragmentOptions> optionsClass : requiredOpts) {
Integer previousBest = visitedOptionsClasses.get(optionsClass);
if (previousBest != null && previousBest <= requiredOpts.size()) {
// Multiple config fragments may require the same options class, but we only need one of
// them to guarantee that class makes it into the configuration. Pick one that depends
// on as few options classes as possible (not necessarily unique).
continue;
}
visitedOptionsClasses.put(optionsClass, requiredOpts.size());
for (Field field : optionsClass.getFields()) {
if (field.isAnnotationPresent(Option.class)) {
result.put(field.getAnnotation(Option.class).name(), factory.creates());
}
}
}
}
return result;
}
public PrerequisiteValidator getPrerequisiteValidator() {
return prerequisiteValidator;
}
@Override
public Label getPreludeLabel() {
return preludeLabel;
}
@Override
public String getRunfilesPrefix() {
return runfilesPrefix;
}
@Override
public String getToolsRepository() {
return toolsRepository;
}
@Override
public Map<String, RuleClass> getRuleClassMap() {
return ruleClassMap;
}
@Override
public Map<String, NativeAspectClass> getNativeAspectClassMap() {
return nativeAspectClassMap;
}
@Override
public NativeAspectClass getNativeAspectClass(String key) {
return nativeAspectClassMap.get(key);
}
public Map<BuildInfoKey, BuildInfoFactory> getBuildInfoFactoriesAsMap() {
ImmutableMap.Builder<BuildInfoKey, BuildInfoFactory> factoryMapBuilder = ImmutableMap.builder();
for (BuildInfoFactory factory : buildInfoFactories) {
factoryMapBuilder.put(factory.getKey(), factory);
}
return factoryMapBuilder.build();
}
/**
* Returns the set of configuration fragments provided by this module.
*/
public ImmutableList<ConfigurationFragmentFactory> getConfigurationFragments() {
return configurationFragmentFactories;
}
@Nullable
public Class<? extends Fragment> getConfigurationFragmentForOption(String requiredOption) {
return optionsToFragmentMap.get(requiredOption);
}
/**
* 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<Rule> 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 set of configuration options that are supported in this module.
*/
public ImmutableList<Class<? extends FragmentOptions>> getConfigurationOptions() {
return configurationOptions;
}
/**
* Returns the definition of the rule class definition with the specified name.
*/
public RuleDefinition getRuleClassDefinition(String ruleClassName) {
return ruleDefinitionMap.get(ruleClassName);
}
/**
* Returns the configuration fragment that should be available to all rules even when they don't
* explicitly require it.
*/
public ImmutableList<Class<? extends Fragment>> getUniversalFragments() {
return universalFragments;
}
/**
* Creates a BuildOptions class for the given options taken from an optionsProvider.
*/
public BuildOptions createBuildOptions(OptionsProvider optionsProvider) {
return BuildOptions.of(configurationOptions, optionsProvider);
}
private static ImmutableMap<String, Object> createEnvironment(
ImmutableMap<String, Object> skylarkAccessibleTopLevels,
ImmutableList<Bootstrap> bootstraps) {
ImmutableMap.Builder<String, Object> envBuilder = ImmutableMap.builder();
// Among other symbols, this step adds the Starlark universe (e.g. None/True/len), for now.
SkylarkModules.addSkylarkGlobalsToBuilder(envBuilder);
envBuilder.putAll(skylarkAccessibleTopLevels.entrySet());
for (Bootstrap bootstrap : bootstraps) {
bootstrap.addBindingsToBuilder(envBuilder);
}
return envBuilder.build();
}
private static ImmutableMap<String, Class<?>> createFragmentMap(
Iterable<ConfigurationFragmentFactory> configurationFragmentFactories) {
ImmutableMap.Builder<String, Class<?>> mapBuilder = ImmutableMap.builder();
for (ConfigurationFragmentFactory fragmentFactory : configurationFragmentFactories) {
Class<? extends Fragment> fragmentClass = fragmentFactory.creates();
SkylarkModule fragmentModule = SkylarkInterfaceUtils.getSkylarkModule(fragmentClass);
if (fragmentModule != null) {
mapBuilder.put(fragmentModule.name(), fragmentClass);
}
}
return mapBuilder.build();
}
@Override
public ImmutableMap<String, Object> getEnvironment() {
return environment;
}
@Override
public StarlarkThread createRuleClassStarlarkThread(
Label fileLabel,
Mutability mutability,
StarlarkSemantics starlarkSemantics,
StarlarkThread.PrintHandler printHandler,
String astFileContentHashCode,
Map<String, Extension> importMap,
ClassObject nativeModule,
ImmutableMap<RepositoryName, RepositoryName> repoMapping) {
Map<String, Object> env = new HashMap<>(environment);
env.put("native", nativeModule);
StarlarkThread thread =
StarlarkThread.builder(mutability)
.setGlobals(Module.createForBuiltins(env).withLabel(fileLabel))
.setSemantics(starlarkSemantics)
.setFileContentHashCode(astFileContentHashCode)
.setImportedExtensions(importMap)
.build();
thread.setPrintHandler(printHandler);
new BazelStarlarkContext(
BazelStarlarkContext.Phase.LOADING,
toolsRepository,
configurationFragmentMap,
repoMapping,
new SymbolGenerator<>(fileLabel),
/* analysisRuleLabel= */ null)
.storeInThread(thread);
return thread;
}
@Override
public String getDefaultWorkspacePrefix() {
return defaultWorkspaceFilePrefix;
}
@Override
public String getDefaultWorkspaceSuffix() {
return defaultWorkspaceFileSuffix;
}
@Override
public Map<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 #addSymlinkDefinition}, not the standard
* symlinks in {@link ConvenienceSymlinks#getStandardLinkDefinitions}.
*
* <p>Note: Usages of custom symlink definitions should be rare. Currently it is only used to
* implement the py2-bin / py3-bin symlinks.
*/
public ImmutableList<SymlinkDefinition> getSymlinkDefinitions() {
return symlinkDefinitions;
}
public ConstraintSemantics getConstraintSemantics() {
return constraintSemantics;
}
@Override
public ThirdPartyLicenseExistencePolicy getThirdPartyLicenseExistencePolicy() {
return thirdPartyLicenseExistencePolicy;
}
/** Returns all registered {@link Fragment} classes. */
public ImmutableSortedSet<Class<? extends Fragment>> getAllFragments() {
ImmutableSortedSet.Builder<Class<? extends Fragment>> fragmentsBuilder =
ImmutableSortedSet.orderedBy(BuildConfiguration.lexicalFragmentSorter);
for (ConfigurationFragmentFactory factory : getConfigurationFragments()) {
fragmentsBuilder.add(factory.creates());
}
fragmentsBuilder.addAll(getUniversalFragments());
return fragmentsBuilder.build();
}
/** Returns a reserved set of action mnemonics. These cannot be used from a Skylark action. */
public ImmutableSet<String> getReservedActionMnemonics() {
return reservedActionMnemonics;
}
public BuildConfiguration.ActionEnvironmentProvider getActionEnvironmentProvider() {
return actionEnvironmentProvider;
}
}