// 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.packages; import com.google.common.base.Function; import com.google.common.collect.ImmutableMap; 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.events.Location; import com.google.devtools.build.lib.packages.Attribute.SkylarkComputedDefaultTemplate.CannotPrecomputeDefaultsException; import com.google.devtools.build.lib.packages.Package.NameConflictException; import com.google.devtools.build.lib.packages.PackageFactory.PackageContext; import com.google.devtools.build.lib.syntax.BaseFunction; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.UserDefinedFunction; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.util.Preconditions; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * Given a {@link RuleClass} and a set of attribute values, returns a {@link Rule} instance. Also * performs a number of checks and associates the {@link Rule} and the owning {@link Package} * with each other. * *
Note: the code that actually populates the RuleClass map has been moved to {@link
* RuleClassProvider}.
*/
public class RuleFactory {
/**
* Maps rule class name to the metaclass instance for that rule.
*/
private final ImmutableMap It is the caller's responsibility to add the rule to the package (the caller may choose not
* to do so if, for example, the rule has errors).
*/
static Rule createRule(
Package.Builder pkgBuilder,
RuleClass ruleClass,
BuildLangTypedAttributeValuesMap attributeValues,
EventHandler eventHandler,
@Nullable FuncallExpression ast,
Location location,
@Nullable Environment env,
AttributeContainer attributeContainer)
throws InvalidRuleException, InterruptedException {
Preconditions.checkNotNull(ruleClass);
String ruleClassName = ruleClass.getName();
Object nameObject = attributeValues.getAttributeValue("name");
if (nameObject == null) {
throw new InvalidRuleException(ruleClassName + " rule has no 'name' attribute");
} else if (!(nameObject instanceof String)) {
throw new InvalidRuleException(ruleClassName + " 'name' attribute must be a string");
}
String name = (String) nameObject;
Label label;
try {
// Test that this would form a valid label name -- in particular, this
// catches cases where Makefile variables $(foo) appear in "name".
label = pkgBuilder.createLabel(name);
} catch (LabelSyntaxException e) {
throw new InvalidRuleException("illegal rule name: " + name + ": " + e.getMessage());
}
boolean inWorkspaceFile = pkgBuilder.isWorkspace();
if (ruleClass.getWorkspaceOnly() && !inWorkspaceFile) {
throw new RuleFactory.InvalidRuleException(
ruleClass + " must be in the WORKSPACE file " + "(used by " + label + ")");
} else if (!ruleClass.getWorkspaceOnly() && inWorkspaceFile) {
throw new RuleFactory.InvalidRuleException(
ruleClass + " cannot be in the WORKSPACE file " + "(used by " + label + ")");
}
AttributesAndLocation generator =
generatorAttributesForMacros(attributeValues, env, location, label);
try {
return ruleClass.createRule(
pkgBuilder,
label,
generator.attributes,
eventHandler,
ast,
generator.location,
attributeContainer);
} catch (LabelSyntaxException | CannotPrecomputeDefaultsException e) {
throw new RuleFactory.InvalidRuleException(ruleClass + " " + e.getMessage());
}
}
/**
* Creates a {@link Rule} instance, adds it to the {@link Package.Builder} and returns it.
*
* @param pkgBuilder the under-construction {@link Package.Builder} to which the rule belongs
* @param ruleClass the {@link RuleClass} of the rule
* @param attributeValues a {@link BuildLangTypedAttributeValuesMap} mapping attribute names to
* attribute values of build-language type. Each attribute must be defined for this class of
* rule, and have a build-language-typed value which can be converted to the appropriate
* native type of the attribute (i.e. via {@link BuildType#selectableConvert}). There must
* be a map entry for each non-optional attribute of this class of rule.
* @param eventHandler a eventHandler on which errors and warnings are reported during
* rule creation
* @param ast the abstract syntax tree of the rule expression (optional)
* @param location the location at which this rule was declared
* @param env the lexical environment of the function call which declared this rule (optional)
* @param attributeContainer the {@link AttributeContainer} the rule will contain
* @throws InvalidRuleException if the rule could not be constructed for any
* reason (e.g. no {@code name} attribute is defined)
* @throws NameConflictException if the rule's name or output files conflict with others in this
* package
* @throws InterruptedException if interrupted
*/
static Rule createAndAddRule(
Package.Builder pkgBuilder,
RuleClass ruleClass,
BuildLangTypedAttributeValuesMap attributeValues,
EventHandler eventHandler,
@Nullable FuncallExpression ast,
Location location,
@Nullable Environment env,
AttributeContainer attributeContainer)
throws InvalidRuleException, NameConflictException, InterruptedException {
Rule rule =
createRule(
pkgBuilder,
ruleClass,
attributeValues,
eventHandler,
ast,
location,
env,
attributeContainer);
pkgBuilder.addRule(rule);
return rule;
}
/**
* Creates a {@link Rule} instance, adds it to the {@link Package.Builder} and returns it.
*
* @param context the package-building context in which this rule was declared
* @param ruleClass the {@link RuleClass} of the rule
* @param attributeValues a {@link BuildLangTypedAttributeValuesMap} mapping attribute names to
* attribute values of build-language type. Each attribute must be defined for this class
* of rule, and have a build-language-typed value which can be converted to the appropriate
* native type of the attribute (i.e. via {@link BuildType#selectableConvert}). There must
* be a map entry for each non-optional attribute of this class of rule.
* @param ast the abstract syntax tree of the rule expression (mandatory because this looks up a
* {@link Location} from the {@code ast})
* @param env the lexical environment of the function call which declared this rule (optional)
* @param attributeContainer the {@link AttributeContainer} the rule will contain
* @throws InvalidRuleException if the rule could not be constructed for any reason (e.g. no
* {@code name} attribute is defined)
* @throws NameConflictException if the rule's name or output files conflict with others in this
* package
* @throws InterruptedException if interrupted
*/
public static Rule createAndAddRule(
PackageContext context,
RuleClass ruleClass,
BuildLangTypedAttributeValuesMap attributeValues,
FuncallExpression ast,
@Nullable Environment env,
AttributeContainer attributeContainer)
throws InvalidRuleException, NameConflictException, InterruptedException {
return createAndAddRule(
context.pkgBuilder,
ruleClass,
attributeValues,
context.eventHandler,
ast,
ast.getLocation(),
env,
attributeContainer);
}
/**
* InvalidRuleException is thrown by {@link Rule} creation methods if the {@link Rule} could
* not be constructed. It contains an error message.
*/
public static class InvalidRuleException extends Exception {
private InvalidRuleException(String message) {
super(message);
}
}
/** A pair of attributes and location. */
private static final class AttributesAndLocation {
final BuildLangTypedAttributeValuesMap attributes;
final Location location;
AttributesAndLocation(BuildLangTypedAttributeValuesMap attributes, Location location) {
this.attributes = attributes;
this.location = location;
}
}
/**
* A wrapper around an map of named attribute values that specifies whether the map's values
* are of "build-language" or of "native" types.
*/
public interface AttributeValuesMap {
/**
* Returns {@code true} if all the map's values are "build-language typed", i.e., resulting
* from the evaluation of an expression in the build language. Returns {@code false} if all
* the map's values are "natively typed", i.e. of a type returned by {@link
* BuildType#selectableConvert}.
*/
boolean valuesAreBuildLanguageTyped();
Iterable Otherwise, it returns the given attributes without any changes.
*/
private static AttributesAndLocation generatorAttributesForMacros(
BuildLangTypedAttributeValuesMap args,
@Nullable Environment env,
Location location,
Label label) {
// Returns the original arguments if a) there is only the rule itself on the stack
// trace (=> no macro) or b) the attributes have already been set by Python pre-processing.
if (env == null) {
return new AttributesAndLocation(args, location);
}
boolean hasName = args.containsAttributeNamed("generator_name");
boolean hasFunc = args.containsAttributeNamed("generator_function");
// TODO(bazel-team): resolve cases in our code where hasName && !hasFunc, or hasFunc && !hasName
if (hasName || hasFunc) {
return new AttributesAndLocation(args, location);
}
Pair For example, the location /usr/local/workspace/my/cool/package/BUILD:3:1 and the label
* //my/cool/package:BUILD would lead to "my/cool/package:BUILD:3".
*
* @return The workspace-relative path of the given location, or null if it could not be computed.
*/
@Nullable
private static String maybeGetRelativeLocation(@Nullable Location location, Label label) {
if (location == null) {
return null;
}
// Determining the workspace root only works reliably if both location and label point to files
// in the same package.
// It would be preferable to construct the path from the label itself, but this doesn't work for
// rules created from function calls in a subincluded file, even if both files share a path
// prefix (for example, when //a/package:BUILD subincludes //a/package/with/a/subpackage:BUILD).
// We can revert to that approach once subincludes aren't supported anymore.
String absolutePath = Location.printPathAndLine(location);
int pos = absolutePath.indexOf(label.getPackageName());
return (pos < 0) ? null : absolutePath.substring(pos);
}
}