| // Copyright 2016 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.bazel.repository.skylark; |
| |
| import static com.google.devtools.build.lib.packages.Attribute.attr; |
| import static com.google.devtools.build.lib.syntax.SkylarkType.castMap; |
| import static com.google.devtools.build.lib.syntax.Type.BOOLEAN; |
| import static com.google.devtools.build.lib.syntax.Type.STRING; |
| import static com.google.devtools.build.lib.syntax.Type.STRING_LIST; |
| |
| import com.google.devtools.build.lib.analysis.BaseRuleClasses; |
| import com.google.devtools.build.lib.analysis.skylark.SkylarkAttr.Descriptor; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.LabelSyntaxException; |
| import com.google.devtools.build.lib.events.Location; |
| import com.google.devtools.build.lib.packages.AttributeValueSource; |
| import com.google.devtools.build.lib.packages.Package; |
| import com.google.devtools.build.lib.packages.Package.NameConflictException; |
| import com.google.devtools.build.lib.packages.PackageFactory; |
| import com.google.devtools.build.lib.packages.PackageFactory.PackageContext; |
| 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.RuleClassType; |
| import com.google.devtools.build.lib.packages.RuleFactory.InvalidRuleException; |
| import com.google.devtools.build.lib.packages.SkylarkExportable; |
| import com.google.devtools.build.lib.packages.WorkspaceFactoryHelper; |
| import com.google.devtools.build.lib.skylarkbuildapi.repository.RepositoryModuleApi; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; |
| import com.google.devtools.build.lib.syntax.BaseFunction; |
| import com.google.devtools.build.lib.syntax.DebugFrame; |
| import com.google.devtools.build.lib.syntax.DotExpression; |
| import com.google.devtools.build.lib.syntax.EvalException; |
| import com.google.devtools.build.lib.syntax.Expression; |
| import com.google.devtools.build.lib.syntax.FuncallExpression; |
| import com.google.devtools.build.lib.syntax.FunctionSignature; |
| import com.google.devtools.build.lib.syntax.Identifier; |
| import com.google.devtools.build.lib.syntax.Runtime; |
| import com.google.devtools.build.lib.syntax.SkylarkList; |
| import com.google.devtools.build.lib.syntax.SkylarkUtils; |
| import java.util.Map; |
| |
| /** |
| * The Skylark module containing the definition of {@code repository_rule} function to define a |
| * skylark remote repository. |
| */ |
| public class SkylarkRepositoryModule implements RepositoryModuleApi { |
| |
| @Override |
| public BaseFunction repositoryRule( |
| BaseFunction implementation, |
| Object attrs, |
| Boolean local, |
| SkylarkList<String> environ, |
| String doc, |
| FuncallExpression ast, |
| com.google.devtools.build.lib.syntax.Environment funcallEnv) |
| throws EvalException { |
| SkylarkUtils.checkLoadingOrWorkspacePhase(funcallEnv, "repository_rule", ast.getLocation()); |
| // We'll set the name later, pass the empty string for now. |
| RuleClass.Builder builder = new RuleClass.Builder("", RuleClassType.WORKSPACE, true); |
| |
| builder.addOrOverrideAttribute(attr("$local", BOOLEAN).defaultValue(local).build()); |
| builder.addOrOverrideAttribute( |
| attr("$environ", STRING_LIST).defaultValue(environ).build()); |
| BaseRuleClasses.nameAttribute(builder); |
| BaseRuleClasses.commonCoreAndSkylarkAttributes(builder); |
| builder.add(attr("expect_failure", STRING)); |
| if (attrs != Runtime.NONE) { |
| for (Map.Entry<String, Descriptor> attr : |
| castMap(attrs, String.class, Descriptor.class, "attrs").entrySet()) { |
| Descriptor attrDescriptor = attr.getValue(); |
| AttributeValueSource source = attrDescriptor.getValueSource(); |
| String attrName = source.convertToNativeName(attr.getKey(), ast.getLocation()); |
| builder.addOrOverrideAttribute(attrDescriptor.build(attrName)); |
| } |
| } |
| builder.setConfiguredTargetFunction(implementation); |
| builder.setRuleDefinitionEnvironmentLabelAndHashCode( |
| funcallEnv.getGlobals().getLabel(), funcallEnv.getTransitiveContentHashCode()); |
| builder.setWorkspaceOnly(); |
| return new RepositoryRuleFunction(builder, ast.getLocation()); |
| } |
| |
| private static final class RepositoryRuleFunction extends BaseFunction |
| implements SkylarkExportable { |
| private final RuleClass.Builder builder; |
| private Label extensionLabel; |
| private String exportedName; |
| private final Location ruleClassDefinitionLocation; |
| |
| public RepositoryRuleFunction(RuleClass.Builder builder, Location ruleClassDefinitionLocation) { |
| super("repository_rule", FunctionSignature.KWARGS); |
| this.builder = builder; |
| this.ruleClassDefinitionLocation = ruleClassDefinitionLocation; |
| } |
| |
| @Override |
| public void export(Label extensionLabel, String exportedName) { |
| this.extensionLabel = extensionLabel; |
| this.exportedName = exportedName; |
| } |
| |
| @Override |
| public boolean isExported() { |
| return extensionLabel != null; |
| } |
| |
| @Override |
| public void repr(SkylarkPrinter printer) { |
| if (exportedName == null) { |
| printer.append("<anonymous starlark repository rule>"); |
| } else { |
| printer.append("<starlark repository rule " + extensionLabel + "%" + exportedName + ">"); |
| } |
| } |
| |
| @Override |
| public Object call( |
| Object[] args, FuncallExpression ast, com.google.devtools.build.lib.syntax.Environment env) |
| throws EvalException, InterruptedException { |
| String ruleClassName = null; |
| Expression function = ast.getFunction(); |
| // If the function ever got exported (the common case), we take the name |
| // it was exprted to. Only in the not intended case of calling an unexported |
| // repository function through an exported macro, we fall back, for lack of |
| // alternatives, to the name in the local context. |
| // TODO(b/111199163): we probably should disallow the use of non-exported |
| // repository rules anyway. |
| if (isExported()) { |
| ruleClassName = exportedName; |
| } else if (function instanceof Identifier) { |
| ruleClassName = ((Identifier) function).getName(); |
| } else if (function instanceof DotExpression) { |
| ruleClassName = ((DotExpression) function).getField().getName(); |
| } else { |
| // TODO: Remove the wrong assumption that a "function name" always exists and is relevant |
| throw new IllegalStateException("Function is not an identifier or method call"); |
| } |
| try { |
| RuleClass ruleClass = builder.build(ruleClassName, ruleClassName); |
| PackageContext context = PackageFactory.getContext(env, ast.getLocation()); |
| Package.Builder packageBuilder = context.getBuilder(); |
| |
| @SuppressWarnings("unchecked") |
| Map<String, Object> attributeValues = (Map<String, Object>) args[0]; |
| String externalRepoName = (String) attributeValues.get("name"); |
| |
| StringBuilder callStack = |
| new StringBuilder("Call stack for the definition of repository '") |
| .append(externalRepoName) |
| .append("' which is a ") |
| .append(ruleClassName) |
| .append(" (rule definition at ") |
| .append(ruleClassDefinitionLocation.toString()) |
| .append("):"); |
| for (DebugFrame frame : env.listFrames(ast.getLocation())) { |
| callStack.append("\n - ").append(frame.location().toString()); |
| } |
| |
| WorkspaceFactoryHelper.addMainRepoEntry( |
| packageBuilder, externalRepoName, env.getSemantics()); |
| |
| WorkspaceFactoryHelper.addRepoMappings( |
| packageBuilder, attributeValues, externalRepoName, ast.getLocation()); |
| |
| Rule rule = |
| WorkspaceFactoryHelper.createAndAddRepositoryRule( |
| context.getBuilder(), |
| ruleClass, |
| null, |
| WorkspaceFactoryHelper.getFinalKwargs(attributeValues), |
| ast, |
| callStack.toString()); |
| return rule; |
| } catch (InvalidRuleException | NameConflictException | LabelSyntaxException e) { |
| throw new EvalException(ast.getLocation(), e.getMessage()); |
| } |
| } |
| } |
| } |