// Copyright 2015 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 static com.google.common.base.CaseFormat.LOWER_UNDERSCORE; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.CharMatcher; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; import com.google.devtools.build.lib.rules.proto.ProtoSourceFileBlacklist; import com.google.devtools.build.lib.rules.proto.ProtoSourcesProvider; import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; import java.util.ArrayList; /** Common rule attributes used by an objc_proto_library. */ final class ProtoAttributes { /** * List of file name segments that should be upper cased when being generated. More information * available in the generateProtobufFilename() method. */ private static final ImmutableSet UPPERCASE_SEGMENTS = ImmutableSet.of("url", "http", "https"); @VisibleForTesting static final String PORTABLE_PROTO_FILTERS_EMPTY_ERROR = "The portable_proto_filters attribute can't be empty"; @VisibleForTesting static final String NO_PROTOS_ERROR = "no protos to compile - a non-empty deps attribute is required"; private final RuleContext ruleContext; /** * Creates a new ProtoAttributes object that wraps over objc_proto_library's attributes. * * @param ruleContext context of the objc_proto_library to wrap */ ProtoAttributes(RuleContext ruleContext) { this.ruleContext = ruleContext; } /** * Validates the proto attributes for this target. * * */ public void validate() throws RuleErrorException { if (getProtoFiles().isEmpty() && !hasObjcProtoLibraryDependencies()) { ruleContext.throwWithAttributeError("deps", NO_PROTOS_ERROR); } if (hasPortableProtoFilters() && getPortableProtoFilters().isEmpty()) { ruleContext.throwWithAttributeError( ObjcProtoLibraryRule.PORTABLE_PROTO_FILTERS_ATTR, PORTABLE_PROTO_FILTERS_EMPTY_ERROR); } } /** * Returns whether the target is an objc_proto_library. It does so by making sure that the * portable_proto_filters attribute exists in this target's attributes (even if it's empty). */ boolean isObjcProtoLibrary() { return ruleContext.attributes().has(ObjcProtoLibraryRule.PORTABLE_PROTO_FILTERS_ATTR); } private boolean isObjcProtoLibrary(ConfiguredTargetAndData dependency) { try { String targetName = dependency.getTarget().getTargetKind(); return targetName.equals("objc_proto_library rule"); } catch (Exception e) { return false; } } /** Returns whether to use the protobuf library instead of the PB2 library. */ boolean hasPortableProtoFilters() { return ruleContext .attributes() .isAttributeValueExplicitlySpecified(ObjcProtoLibraryRule.PORTABLE_PROTO_FILTERS_ATTR); } /** Returns the list of portable proto filters. */ ImmutableList getPortableProtoFilters() { if (ruleContext .attributes() .has(ObjcProtoLibraryRule.PORTABLE_PROTO_FILTERS_ATTR, LABEL_LIST)) { return ruleContext .getPrerequisiteArtifacts(ObjcProtoLibraryRule.PORTABLE_PROTO_FILTERS_ATTR, Mode.HOST) .list(); } return ImmutableList.of(); } /** Returns the list of well known type protos. */ ImmutableList getWellKnownTypeProtos() { return ruleContext .getPrerequisiteArtifacts(ObjcRuleClasses.PROTOBUF_WELL_KNOWN_TYPES, Mode.HOST) .list(); } /** Returns the list of proto files to compile. */ NestedSet getProtoFiles() { return NestedSetBuilder.stableOrder() .addTransitive(getProtoDepsSources()) .build(); } /** Returns the proto compiler to be used. */ Artifact getProtoCompiler() { return ruleContext.getPrerequisiteArtifact(ObjcRuleClasses.PROTO_COMPILER_ATTR, Mode.HOST); } /** Returns the list of files needed by the proto compiler. */ Iterable getProtoCompilerSupport() { return ruleContext .getPrerequisiteArtifacts(ObjcRuleClasses.PROTO_COMPILER_SUPPORT_ATTR, Mode.HOST) .list(); } /** * Filters the well known protos from the given list of proto files. This should be used to * prevent the well known protos from being generated as they are already generated in the runtime * library. */ Iterable filterWellKnownProtos(Iterable protoFiles) { ProtoSourceFileBlacklist wellKnownProtoBlacklist = new ProtoSourceFileBlacklist(ruleContext, getWellKnownTypeProtos()); return wellKnownProtoBlacklist.filter(protoFiles); } /** Returns whether the given proto is a well known proto or not. */ boolean isProtoWellKnown(Artifact protoFile) { ProtoSourceFileBlacklist wellKnownProtoBlacklist = new ProtoSourceFileBlacklist(ruleContext, getWellKnownTypeProtos()); return wellKnownProtoBlacklist.isBlacklisted(protoFile); } /** * Processes the case of the proto file name in the same fashion as the objective_c generator's * UnderscoresToCamelCase function. This converts snake case to camel case by splitting words * by non alphabetic characters. This also treats the URL, HTTP and HTTPS as special words that * need to be completely uppercase. * * Examples: * - j2objc_descriptor -> J2ObjcDescriptor (notice that O is uppercase after the 2) * - my_http_url_array -> MyHTTPURLArray * - proto-descriptor -> ProtoDescriptor * * Original code reference: *

https://github.com/google/protobuf/blob/master/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc */ String getGeneratedProtoFilename(String protoFilename, boolean upcaseReservedWords) { boolean lastCharWasDigit = false; boolean lastCharWasUpper = false; boolean lastCharWasLower = false; StringBuilder currentSegment = new StringBuilder(); ArrayList segments = new ArrayList<>(); for (int i = 0; i < protoFilename.length(); i++) { char currentChar = protoFilename.charAt(i); if (CharMatcher.javaDigit().matches(currentChar)) { if (!lastCharWasDigit) { segments.add(currentSegment.toString()); currentSegment = new StringBuilder(); } currentSegment.append(currentChar); lastCharWasDigit = true; lastCharWasUpper = false; lastCharWasLower = false; } else if (CharMatcher.javaLowerCase().matches(currentChar)) { if (!lastCharWasLower && !lastCharWasUpper) { segments.add(currentSegment.toString()); currentSegment = new StringBuilder(); } currentSegment.append(currentChar); lastCharWasDigit = false; lastCharWasUpper = false; lastCharWasLower = true; } else if (CharMatcher.javaUpperCase().matches(currentChar)) { if (!lastCharWasUpper) { segments.add(currentSegment.toString()); currentSegment = new StringBuilder(); } currentSegment.append(Character.toLowerCase(currentChar)); lastCharWasDigit = false; lastCharWasUpper = true; lastCharWasLower = false; } else { lastCharWasDigit = false; lastCharWasUpper = false; lastCharWasLower = false; } } segments.add(currentSegment.toString()); StringBuilder casedSegments = new StringBuilder(); for (String segment : segments) { if (upcaseReservedWords && UPPERCASE_SEGMENTS.contains(segment)) { casedSegments.append(segment.toUpperCase()); } else { casedSegments.append(LOWER_UNDERSCORE.to(UPPER_CAMEL, segment)); } } return casedSegments.toString(); } /** Returns the sets of proto files that were added using proto_library dependencies. */ private NestedSet getProtoDepsSources() { NestedSetBuilder artifacts = NestedSetBuilder.stableOrder(); Iterable providers = ruleContext.getPrerequisites("deps", Mode.TARGET, ProtoSourcesProvider.class); for (ProtoSourcesProvider provider : providers) { artifacts.addTransitive(provider.getTransitiveProtoSources()); } return artifacts.build(); } private boolean hasObjcProtoLibraryDependencies() { for (ConfiguredTargetAndData dep : ruleContext.getPrerequisiteConfiguredTargetAndTargets("deps", Mode.TARGET)) { if (isObjcProtoLibrary(dep)) { return true; } } return false; } }