// 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.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
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.DefaultsPackage;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.events.EventHandler;
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.NativeAspectClass.NativeAspectFactory;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
import com.google.devtools.build.lib.rules.SkylarkModules;
import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.Environment.Extension;
import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.SkylarkType;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.common.options.OptionsClassProvider;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Knows about every rule Blaze supports and the associated configuration options.
*
*
This class is initialized on server startup and the set of rules, build info factories
* and configuration options is guarantees not to change over the life time of the Blaze server.
*/
public class ConfiguredRuleClassProvider implements RuleClassProvider {
/**
* 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}.
*
*
Can be used for enforcing any organization-specific policies about the layout of the
* workspace.
*/
void validate(
RuleContext.Builder contextBuilder, ConfiguredTarget prerequisite, Attribute attribute);
}
/**
* Builder for {@link ConfiguredRuleClassProvider}.
*/
public static class Builder implements RuleDefinitionEnvironment {
private final StringBuilder defaultWorkspaceFile = new StringBuilder();
private Label preludeLabel;
private String runfilesPrefix;
private final List configurationFragments = new ArrayList<>();
private final List buildInfoFactories = new ArrayList<>();
private final List> configurationOptions = new ArrayList<>();
private final Map ruleClassMap = new HashMap<>();
private final Map> ruleDefinitionMap =
new HashMap<>();
private final Map> aspectFactoryMap =
new HashMap<>();
private final Map, RuleClass> ruleMap = new HashMap<>();
private final Map, RuleDefinition> ruleDefinitionInstanceCache =
new HashMap<>();
private final Digraph> dependencyGraph =
new Digraph<>();
private ConfigurationCollectionFactory configurationCollectionFactory;
private Class extends BuildConfiguration.Fragment> universalFragment;
private PrerequisiteValidator prerequisiteValidator;
private ImmutableMap skylarkAccessibleJavaClasses = ImmutableMap.of();
private final List> buildOptions = Lists.newArrayList();
public void addWorkspaceFile(String contents) {
defaultWorkspaceFile.append(contents);
}
public Builder setPrelude(String preludeLabelString) {
try {
this.preludeLabel = Label.parseAbsolute(preludeLabelString);
} catch (LabelSyntaxException e) {
String errorMsg =
String.format("Prelude label '%s' is invalid: %s", preludeLabelString, e.getMessage());
throw new IllegalArgumentException(errorMsg);
}
return this;
}
public Builder addBuildOptions(Collection> optionsClasses) {
buildOptions.addAll(optionsClasses);
return this;
}
public Builder setRunfilesPrefix(String runfilesPrefix) {
this.runfilesPrefix = runfilesPrefix;
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();
ruleDefinitionInstanceCache.put(ruleDefinitionClass, ruleDefinition);
dependencyGraph.createNode(ruleDefinitionClass);
for (Class extends RuleDefinition> ancestor : ruleDefinition.getMetadata().ancestors()) {
dependencyGraph.addEdge(ancestor, ruleDefinitionClass);
}
return this;
}
public Builder addAspectFactory(
String name, Class extends ConfiguredNativeAspectFactory> configuredAspectFactoryClass) {
aspectFactoryMap.put(name, configuredAspectFactoryClass);
return this;
}
public Builder addConfigurationOptions(Class extends FragmentOptions> configurationOptions) {
this.configurationOptions.add(configurationOptions);
return this;
}
public Builder addConfigurationFragment(ConfigurationFragmentFactory factory) {
configurationFragments.add(factory);
return this;
}
public Builder setConfigurationCollectionFactory(ConfigurationCollectionFactory factory) {
this.configurationCollectionFactory = factory;
return this;
}
public Builder setUniversalConfigurationFragment(
Class extends BuildConfiguration.Fragment> fragment) {
this.universalFragment = fragment;
return this;
}
public Builder setSkylarkAccessibleJavaClasses(ImmutableMap objects) {
this.skylarkAccessibleJavaClasses = objects;
return this;
}
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(ruleDefinitionInstanceCache.get(definitionClass),
"addRuleDefinition(new %s()) should be called before build()", definitionClass.getName());
RuleDefinition.Metadata metadata = instance.getMetadata();
checkArgument(ruleClassMap.get(metadata.name()) == null, metadata.name());
List> 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(), definitionClass);
return ruleClass;
}
public ConfiguredRuleClassProvider build() {
for (Node> ruleDefinition :
dependencyGraph.getTopologicalOrder()) {
commitRuleDefinition(ruleDefinition.getLabel());
}
return new ConfiguredRuleClassProvider(
preludeLabel,
runfilesPrefix,
ImmutableMap.copyOf(ruleClassMap),
ImmutableMap.copyOf(ruleDefinitionMap),
ImmutableMap.copyOf(aspectFactoryMap),
defaultWorkspaceFile.toString(),
ImmutableList.copyOf(buildInfoFactories),
ImmutableList.copyOf(configurationOptions),
ImmutableList.copyOf(configurationFragments),
configurationCollectionFactory,
universalFragment,
prerequisiteValidator,
skylarkAccessibleJavaClasses,
buildOptions);
}
@Override
public Label getLabel(String labelValue) {
return LABELS.getUnchecked(labelValue);
}
}
/**
* Used to make the label instances unique, so that we don't create a new
* instance for every rule.
*/
private static final LoadingCache LABELS = CacheBuilder.newBuilder().build(
new CacheLoader() {
@Override
public Label load(String from) {
try {
return Label.parseAbsolute(from);
} catch (LabelSyntaxException e) {
throw new IllegalArgumentException(from, e);
}
}
});
/**
* A list of relative paths to the WORKSPACE files needed to provide external dependencies for
* the rule classes.
*/
String defaultWorkspaceFile;
/**
* Label for the prelude file.
*/
private final Label preludeLabel;
/**
* The default runfiles prefix.
*/
private final String runfilesPrefix;
/**
* Maps rule class name to the metaclass instance for that rule.
*/
private final ImmutableMap ruleClassMap;
/**
* Maps rule class name to the rule definition metaclasses.
*/
private final ImmutableMap> ruleDefinitionMap;
/**
* Maps aspect name to the aspect factory meta class.
*/
private final ImmutableMap> aspectFactoryMap;
/**
* The configuration options that affect the behavior of the rules.
*/
private final ImmutableList> configurationOptions;
/**
* The set of configuration fragment factories.
*/
private final ImmutableList configurationFragments;
/**
* The factory that creates the configuration collection.
*/
private final ConfigurationCollectionFactory configurationCollectionFactory;
/**
* A configuration fragment that should be available to all rules even when they don't
* explicitly require it.
*/
private final Class extends BuildConfiguration.Fragment> universalFragment;
private final ImmutableList buildInfoFactories;
private final PrerequisiteValidator prerequisiteValidator;
private final Environment.Frame globals;
private final List> buildOptions;
private ConfiguredRuleClassProvider(
Label preludeLabel,
String runfilesPrefix,
ImmutableMap ruleClassMap,
ImmutableMap> ruleDefinitionMap,
ImmutableMap> aspectFactoryMap,
String defaultWorkspaceFile,
ImmutableList buildInfoFactories,
ImmutableList> configurationOptions,
ImmutableList configurationFragments,
ConfigurationCollectionFactory configurationCollectionFactory,
Class extends BuildConfiguration.Fragment> universalFragment,
PrerequisiteValidator prerequisiteValidator,
ImmutableMap skylarkAccessibleJavaClasses,
List> buildOptions) {
this.preludeLabel = preludeLabel;
this.runfilesPrefix = runfilesPrefix;
this.ruleClassMap = ruleClassMap;
this.ruleDefinitionMap = ruleDefinitionMap;
this.aspectFactoryMap = aspectFactoryMap;
this.defaultWorkspaceFile = defaultWorkspaceFile;
this.buildInfoFactories = buildInfoFactories;
this.configurationOptions = configurationOptions;
this.configurationFragments = configurationFragments;
this.configurationCollectionFactory = configurationCollectionFactory;
this.universalFragment = universalFragment;
this.prerequisiteValidator = prerequisiteValidator;
this.globals = createGlobals(skylarkAccessibleJavaClasses);
this.buildOptions = buildOptions;
}
public PrerequisiteValidator getPrerequisiteValidator() {
return prerequisiteValidator;
}
@Override
public Label getPreludeLabel() {
return preludeLabel;
}
@Override
public String getRunfilesPrefix() {
return runfilesPrefix;
}
@Override
public Map getRuleClassMap() {
return ruleClassMap;
}
@Override
public Map> getAspectFactoryMap() {
return aspectFactoryMap;
}
/**
* Returns a list of build info factories that are needed for the supported languages.
*/
public ImmutableList getBuildInfoFactories() {
return buildInfoFactories;
}
/**
* Returns the set of configuration fragments provided by this module.
*/
public ImmutableList getConfigurationFragments() {
return configurationFragments;
}
/**
* Returns the set of configuration options that are supported in this module.
*/
public ImmutableList> getConfigurationOptions() {
return configurationOptions;
}
/**
* Returns the definition of the rule class definition with the specified name.
*/
public Class extends RuleDefinition> getRuleClassDefinition(String ruleClassName) {
return ruleDefinitionMap.get(ruleClassName);
}
/**
* Returns the configuration collection creator.
*/
public ConfigurationCollectionFactory getConfigurationCollectionFactory() {
return configurationCollectionFactory;
}
/**
* Returns the configuration fragment that should be available to all rules even when they
* don't explicitly require it.
*/
public Class extends BuildConfiguration.Fragment> getUniversalFragment() {
return universalFragment;
}
/**
* Returns the defaults package for the default settings.
*/
public String getDefaultsPackageContent() {
return DefaultsPackage.getDefaultsPackageContent(configurationOptions);
}
/**
* Returns the defaults package for the given options taken from an optionsProvider.
*/
public String getDefaultsPackageContent(OptionsClassProvider optionsProvider) {
return DefaultsPackage.getDefaultsPackageContent(
BuildOptions.of(configurationOptions, optionsProvider));
}
public ImmutableList> getOptionFragments() {
return ImmutableList.copyOf(buildOptions);
}
/**
* Creates a BuildOptions class for the given options taken from an optionsProvider.
*/
public BuildOptions createBuildOptions(OptionsClassProvider optionsProvider) {
return BuildOptions.of(configurationOptions, optionsProvider);
}
private Environment.Frame createGlobals(
ImmutableMap skylarkAccessibleJavaClasses) {
try (Mutability mutability = Mutability.create("ConfiguredRuleClassProvider globals")) {
Environment env = createSkylarkRuleClassEnvironment(
mutability, SkylarkModules.GLOBALS, null, null, null);
for (Map.Entry entry : skylarkAccessibleJavaClasses.entrySet()) {
env.setup(entry.getKey(), entry.getValue().getType());
}
return env.getGlobals();
}
}
private Environment createSkylarkRuleClassEnvironment(
Mutability mutability,
Environment.Frame globals,
EventHandler eventHandler,
String astFileContentHashCode,
Map importMap) {
Environment env = Environment.builder(mutability)
.setSkylark()
.setGlobals(globals)
.setEventHandler(eventHandler)
.setFileContentHashCode(astFileContentHashCode)
.setImportedExtensions(importMap)
.setLoadingPhase()
.build();
return env;
}
@Override
public Environment createSkylarkRuleClassEnvironment(
Mutability mutability,
EventHandler eventHandler,
String astFileContentHashCode,
Map importMap) {
return createSkylarkRuleClassEnvironment(
mutability, globals, eventHandler, astFileContentHashCode, importMap);
}
@Override
public String getDefaultWorkspaceFile() {
return defaultWorkspaceFile;
}
}