| // Copyright 2017 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.rules.objc; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.build.lib.testutil.Scratch; |
| import java.io.IOException; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Provides utilities to help test a certain rule type without requiring the calling code to know |
| * exactly what kind of rule is being tested. Only one instance is needed per rule type (e.g. one |
| * instance for {@code objc_library}). |
| */ |
| public abstract class RuleType { |
| /** |
| * What to pass as the value of some attribute to indicate an attribute should not be added to the |
| * rule. This can either be to test an error condition, or to use an alternative attribute to |
| * supply the value. |
| */ |
| public static final String OMIT_REQUIRED_ATTR = "<OMIT_REQUIRED_ATTR>"; |
| |
| private final String ruleTypeName; |
| |
| RuleType(String ruleTypeName) { |
| this.ruleTypeName = ruleTypeName; |
| } |
| |
| /** |
| * The name of this type as it appears in {@code BUILD} files, such as {@code objc_library}. |
| */ |
| final String getRuleTypeName() { |
| return ruleTypeName; |
| } |
| |
| /** |
| * Returns whether this type exports companion library target in Xcode. |
| */ |
| final boolean exportsXcodeCompanionTarget() { |
| return ruleTypeName.equals("objc_binary"); |
| } |
| |
| /** |
| * Returns the bundle extension for the bundles generated by the rule. |
| */ |
| final String bundleExtension() { |
| return ruleTypeName.equals("ios_test") ? "xctest" : "app"; |
| } |
| |
| /** |
| * Returns names and values, and otherwise prepares, extra attributes required for this rule type |
| * to be without error. For instance, if this rule type requires 'srcs' and 'infoplist' |
| * attributes, this method may be implemented as follows: |
| * <pre> |
| * {@code |
| * List<String> attributes = new ArrayList<>(); |
| * if (!alreadyAdded.contains("srcs")) { |
| * scratch.file("/workspace_root/" + packageDir + "/a.m"); |
| * attributes.add("srcs = ['a.m']"); |
| * } |
| * if (!alreadyAdded.contains(INFOPLIST_ATTR)) { |
| * scratch.file("/workspace_root/" + packageDir + "Info.plist"); |
| * attributes.add("infoplist = ['Info.plist']"); |
| * } |
| * return attributes; |
| * </pre> |
| * } |
| * |
| * @throws IOException for whatever reason the implementer feels like, but mostly just when |
| * a scratch file couldn't be created |
| */ |
| abstract Iterable<String> requiredAttributes( |
| Scratch scratch, String packageDir, Set<String> alreadyAdded) throws IOException; |
| |
| private ImmutableMap<String, String> map(String... attrs) { |
| ImmutableMap.Builder<String, String> map = new ImmutableMap.Builder<>(); |
| Preconditions.checkArgument((attrs.length & 1) == 0, |
| "attrs must have an even number of elements"); |
| for (int i = 0; i < attrs.length; i += 2) { |
| map.put(attrs[i], attrs[i + 1]); |
| } |
| return map.build(); |
| } |
| |
| /** |
| * Generates the String necessary to define a target of this rule type. |
| * |
| * @param packageDir the package in which to create the target |
| * @param name the name of the target |
| * @param checkSpecificAttrs alternating name/values of attributes to add to the rule that are |
| * required for the check being performed to be defined a certain way. Pass |
| * {@link #OMIT_REQUIRED_ATTR} for a value to prevent an attribute from being automatically |
| * defined. |
| */ |
| final String target( |
| Scratch scratch, String packageDir, String name, String... checkSpecificAttrs) |
| throws IOException { |
| ImmutableMap<String, String> checkSpecific = map(checkSpecificAttrs); |
| StringBuilder target = new StringBuilder(ruleTypeName) |
| .append("(name = '") |
| .append(name) |
| .append("',"); |
| for (Map.Entry<String, String> entry : checkSpecific.entrySet()) { |
| if (entry.getValue().equals(OMIT_REQUIRED_ATTR)) { |
| continue; |
| } |
| target.append(entry.getKey()) |
| .append("=") |
| .append(entry.getValue()) |
| .append(","); |
| } |
| Joiner.on(",").appendTo( |
| target, |
| requiredAttributes(scratch, packageDir, checkSpecific.keySet())); |
| target.append(')'); |
| return target.toString(); |
| |
| } |
| |
| /** |
| * Creates a target at //x:x which is the only target in the BUILD file. Returns the string that |
| * is written to the scratch file as it is often useful for debugging purposes. |
| */ |
| public final String scratchTarget(Scratch scratch, String... checkSpecificAttrs) |
| throws IOException { |
| return scratchTarget("x", "x", scratch, checkSpecificAttrs); |
| } |
| |
| /** |
| * Creates a target at a given package which is the only target in the BUILD file. Returns the |
| * string that is written to the scratch file as it is often useful for debugging purposes. |
| * |
| * @param packageDir the package of the target, for example "foo" in //foo:bar |
| * @param targetName the name of the target, for example "bar" in //foo:bar |
| * @param scratch the scratch object to use to create the build file |
| * @param checkSpecificAttrs alternating name/values of attributes to add to the rule that are |
| * required for the check being performed to be defined a certain way. Pass |
| * {@link #OMIT_REQUIRED_ATTR} for a value to prevent an attribute from being automatically |
| * defined. |
| */ |
| public final String scratchTarget(String packageDir, String targetName, |
| Scratch scratch, String... checkSpecificAttrs) |
| throws IOException { |
| String target = target(scratch, packageDir, targetName, checkSpecificAttrs); |
| scratch.file(packageDir + "/BUILD", starlarkLoadPrerequisites() + "\n" + target); |
| return target; |
| } |
| |
| /** |
| * Returns a string (of one or more lines) required by BUILD files which reference targets of this |
| * rule type. |
| * |
| * <p>Subclasses of {@link RuleType} should override this method if using the rule requires |
| * Starlark files to be loaded. |
| */ |
| public String starlarkLoadPrerequisites() { |
| return ""; |
| } |
| } |