aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Googler <noreply@google.com>2016-02-24 00:19:03 +0000
committerGravatar Philipp Wollermann <philwo@google.com>2016-02-24 17:58:01 +0000
commit5c748301315e3ae993482baef784098f0e19c061 (patch)
tree70296e72c8c05995254e88e5de214bf549eb4684
parente3f5387155026da2194eefed2723b773ae62051d (diff)
Blaze - ObjcProtoLibrary: Adds support for the new protobuf library. This includes improvements such as proto3 syntax support for Objective C.
-- MOS_MIGRATED_REVID=115395892
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProtoLibrary.java230
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProtoLibraryRule.java104
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/ProtoSupport.java506
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java4
4 files changed, 599 insertions, 245 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProtoLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProtoLibrary.java
index aa02e1f23a..8c23b1d575 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProtoLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProtoLibrary.java
@@ -14,196 +14,49 @@
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.rules.objc.XcodeProductType.LIBRARY_STATIC;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
-import com.google.devtools.build.lib.analysis.PrerequisiteArtifacts;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleContext;
-import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
-import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
-import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
-import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
-import com.google.devtools.build.lib.rules.proto.ProtoSourcesProvider;
-import com.google.devtools.build.lib.syntax.Type;
-import com.google.devtools.build.lib.util.FileType;
-import com.google.devtools.build.lib.vfs.FileSystemUtils;
-import com.google.devtools.build.lib.vfs.PathFragment;
-
-import javax.annotation.Nullable;
/**
* Implementation for the "objc_proto_library" rule.
*/
public class ObjcProtoLibrary implements RuleConfiguredTargetFactory {
- private static final Function<Artifact, PathFragment> PARENT_PATHFRAGMENT =
- new Function<Artifact, PathFragment>() {
- @Override
- public PathFragment apply(Artifact input) {
- return input.getExecPath().getParentDirectory();
- }
- };
-
- @VisibleForTesting
- static final String NO_PROTOS_ERROR =
- "no protos to compile - a non-empty deps attribute is required";
-
- @VisibleForTesting
- static final String FILES_DEPRECATED_WARNING =
- "Using files and filegroups in objc_proto_library is deprecated";
-
@Override
public ConfiguredTarget create(final RuleContext ruleContext) throws InterruptedException {
- Artifact compileProtos = ruleContext.getPrerequisiteArtifact(
- ObjcProtoLibraryRule.COMPILE_PROTOS_ATTR, Mode.HOST);
- Optional<Artifact> optionsFile = Optional.fromNullable(
- ruleContext.getPrerequisiteArtifact(ObjcProtoLibraryRule.OPTIONS_FILE_ATTR, Mode.HOST));
-
- NestedSet<Artifact> protos = NestedSetBuilder.<Artifact>stableOrder()
- .addAll(maybeGetProtoFiles(ruleContext))
- .addTransitive(maybeGetProtoSources(ruleContext))
- .build();
-
- if (Iterables.isEmpty(protos)) {
- ruleContext.ruleError(NO_PROTOS_ERROR);
- }
-
- ImmutableList<Artifact> libProtobuf = ruleContext
- .getPrerequisiteArtifacts(ObjcProtoLibraryRule.LIBPROTOBUF_ATTR, Mode.TARGET)
- .list();
- ImmutableList<Artifact> protoSupport = ruleContext
- .getPrerequisiteArtifacts(ObjcProtoLibraryRule.PROTO_SUPPORT_ATTR, Mode.HOST)
- .list();
-
- // Generate sources in a package-and-rule-scoped directory; adds both the
- // package-and-rule-scoped directory and the header-containing-directory to the include path of
- // dependers.
- String uniqueDirectoryName = "_generated_protos";
-
- PathFragment rootRelativeOutputDir = ruleContext.getUniqueDirectory(uniqueDirectoryName);
- PathFragment workspaceRelativeOutputDir = new PathFragment(
- ruleContext.getBinOrGenfilesDirectory().getExecPath(), rootRelativeOutputDir);
- PathFragment generatedProtoDir =
- new PathFragment(workspaceRelativeOutputDir, ruleContext.getLabel().getPackageFragment());
-
- boolean outputCpp =
- ruleContext.attributes().get(ObjcProtoLibraryRule.OUTPUT_CPP_ATTR, Type.BOOLEAN);
-
- boolean useObjcHeaderNames =
- ruleContext.attributes().get(
- ObjcProtoLibraryRule.USE_OBJC_HEADER_NAMES_ATTR, Type.BOOLEAN);
-
- ImmutableList<Artifact> protoGeneratedSources = outputArtifacts(
- ruleContext, uniqueDirectoryName, protos, FileType.of(".pb." + (outputCpp ? "cc" : "m")),
- outputCpp);
- ImmutableList<Artifact> protoGeneratedHeaders = outputArtifacts(
- ruleContext, uniqueDirectoryName, protos,
- FileType.of(".pb" + (useObjcHeaderNames ? "objc.h" : ".h")), outputCpp);
-
- Artifact inputFileList = ruleContext.getUniqueDirectoryArtifact(
- "_protos", "_proto_input_files", ruleContext.getConfiguration().getGenfilesDirectory());
-
- ruleContext.registerAction(new FileWriteAction(
- ruleContext.getActionOwner(),
- inputFileList,
- Artifact.joinExecPaths("\n", protos),
- false));
+ ProtoSupport protoSupport = new ProtoSupport(ruleContext);
- CustomCommandLine.Builder commandLineBuilder = new CustomCommandLine.Builder()
- .add(compileProtos.getExecPathString())
- .add("--input-file-list").add(inputFileList.getExecPathString())
- .add("--output-dir").add(workspaceRelativeOutputDir.getSafePathString())
- .add("--working-dir").add(".");
- if (optionsFile.isPresent()) {
- commandLineBuilder
- .add("--compiler-options-path")
- .add(optionsFile.get().getExecPathString());
- }
- if (outputCpp) {
- commandLineBuilder.add("--generate-cpp");
- }
- if (useObjcHeaderNames) {
- commandLineBuilder.add("--use-objc-header-names");
- }
-
- if (!Iterables.isEmpty(protos)) {
- ruleContext.registerAction(ObjcRuleClasses.spawnOnDarwinActionBuilder()
- .setMnemonic("GenObjcProtos")
- .addInput(compileProtos)
- .addInputs(optionsFile.asSet())
- .addInputs(protos)
- .addInput(inputFileList)
- .addInputs(libProtobuf)
- .addInputs(protoSupport)
- .addOutputs(Iterables.concat(protoGeneratedSources, protoGeneratedHeaders))
- .setExecutable(new PathFragment("/usr/bin/python"))
- .setCommandLine(commandLineBuilder.build())
- .build(ruleContext));
- }
+ ObjcCommon.Builder commonBuilder = new ObjcCommon.Builder(ruleContext);
+ XcodeProvider.Builder xcodeProviderBuilder = new XcodeProvider.Builder();
+ NestedSetBuilder<Artifact> filesToBuild = NestedSetBuilder.stableOrder();
- IntermediateArtifacts intermediateArtifacts =
- ObjcRuleClasses.intermediateArtifacts(ruleContext);
- CompilationArtifacts compilationArtifacts = new CompilationArtifacts.Builder()
- .addNonArcSrcs(protoGeneratedSources)
- .setIntermediateArtifacts(intermediateArtifacts)
- .setPchFile(Optional.<Artifact>absent())
- .addAdditionalHdrs(protoGeneratedHeaders)
- .addAdditionalHdrs(protoGeneratedSources)
- .build();
+ protoSupport
+ .validate()
+ .addCommonOptions(commonBuilder)
+ .addXcodeProviderOptions(xcodeProviderBuilder)
+ .addFilesToBuild(filesToBuild)
+ .registerActions();
- ImmutableSet.Builder<PathFragment> searchPathEntriesBuilder =
- new ImmutableSet.Builder<PathFragment>()
- .add(workspaceRelativeOutputDir);
- if (ruleContext.attributes().get(
- ObjcProtoLibraryRule.PER_PROTO_INCLUDES, Type.BOOLEAN)) {
- searchPathEntriesBuilder
- .add(generatedProtoDir)
- .addAll(Iterables.transform(protoGeneratedHeaders, PARENT_PATHFRAGMENT));
+ if (ruleContext.hasErrors()) {
+ return null;
}
- ImmutableSet<PathFragment> searchPathEntries = searchPathEntriesBuilder.build();
- ObjcCommon common =
- new ObjcCommon.Builder(ruleContext)
- .setCompilationArtifacts(compilationArtifacts)
- .addUserHeaderSearchPaths(searchPathEntries)
- .addDepObjcProviders(
- ruleContext.getPrerequisites(
- ObjcProtoLibraryRule.LIBPROTOBUF_ATTR, Mode.TARGET, ObjcProvider.class))
- .setIntermediateArtifacts(intermediateArtifacts)
- .setHasModuleMap()
- .build();
+ ObjcCommon common = commonBuilder.build();
- NestedSetBuilder<Artifact> filesToBuild = NestedSetBuilder.<Artifact>stableOrder()
- .addAll(common.getCompiledArchive().asSet())
- .addAll(protoGeneratedSources)
- .addAll(protoGeneratedHeaders);
+ filesToBuild.addAll(common.getCompiledArchive().asSet());
- ObjcConfiguration configuration = ObjcRuleClasses.objcConfiguration(ruleContext);
- XcodeProvider.Builder xcodeProviderBuilder = new XcodeProvider.Builder()
- .addUserHeaderSearchPaths(searchPathEntries)
- .addCopts(configuration.getCopts())
- .addHeaders(protoGeneratedHeaders)
- .setCompilationArtifacts(common.getCompilationArtifacts().get());
-
- new CompilationSupport(ruleContext)
- .registerCompileAndArchiveActions(common);
+ new CompilationSupport(ruleContext).registerCompileAndArchiveActions(commonBuilder.build());
new XcodeSupport(ruleContext)
.addFilesToBuild(filesToBuild)
.addXcodeSettings(xcodeProviderBuilder, common.getObjcProvider(), LIBRARY_STATIC)
.addDependencies(
- xcodeProviderBuilder, new Attribute(ObjcProtoLibraryRule.LIBPROTOBUF_ATTR, Mode.TARGET))
+ xcodeProviderBuilder, new Attribute(ObjcProtoLibraryRule.PROTO_LIB_ATTR, Mode.TARGET))
.registerActions(xcodeProviderBuilder.build());
return ObjcRuleClasses.ruleConfiguredTarget(ruleContext, filesToBuild.build())
@@ -211,55 +64,4 @@ public class ObjcProtoLibrary implements RuleConfiguredTargetFactory {
.addProvider(ObjcProvider.class, common.getObjcProvider())
.build();
}
-
- /**
- * Get .proto files added to the deps attribute. This is for backwards compatibility,
- * and emits a warning.
- */
- private ImmutableList<Artifact> maybeGetProtoFiles(RuleContext ruleContext) {
- PrerequisiteArtifacts prerequisiteArtifacts =
- ruleContext.getPrerequisiteArtifacts("deps", Mode.TARGET);
- ImmutableList<Artifact> protoFiles = prerequisiteArtifacts.filter(FileType.of(".proto")).list();
- if (!protoFiles.isEmpty()) {
- ruleContext.attributeWarning("deps", FILES_DEPRECATED_WARNING);
- }
- return protoFiles;
- }
-
- private NestedSet<Artifact> maybeGetProtoSources(RuleContext ruleContext) {
- NestedSetBuilder<Artifact> artifacts = new NestedSetBuilder<>(Order.STABLE_ORDER);
- Iterable<ProtoSourcesProvider> providers =
- ruleContext.getPrerequisites("deps", Mode.TARGET, ProtoSourcesProvider.class);
- for (ProtoSourcesProvider provider : providers) {
- artifacts.addTransitive(provider.getTransitiveProtoSources());
- }
- return artifacts.build();
- }
-
- private ImmutableList<Artifact> outputArtifacts(RuleContext ruleContext,
- String uniqueDirectoryName, Iterable<Artifact> protos, FileType newFileType,
- boolean outputCpp) {
- ImmutableList.Builder<Artifact> builder = new ImmutableList.Builder<>();
- for (Artifact proto : protos) {
- String protoOutputName;
- if (outputCpp) {
- protoOutputName = proto.getFilename();
- } else {
- String lowerUnderscoreBaseName = proto.getFilename().replace('-', '_').toLowerCase();
- protoOutputName = LOWER_UNDERSCORE.to(UPPER_CAMEL, lowerUnderscoreBaseName);
- }
- PathFragment rawFragment = new PathFragment(
- proto.getRootRelativePath().getParentDirectory(),
- new PathFragment(protoOutputName));
- @Nullable PathFragment outputFile = FileSystemUtils.replaceExtension(
- rawFragment,
- newFileType.getExtensions().get(0),
- ".proto");
- if (outputFile != null) {
- builder.add(ruleContext.getUniqueDirectoryArtifact(uniqueDirectoryName,
- outputFile, ruleContext.getBinOrGenfilesDirectory()));
- }
- }
- return builder.build();
- }
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProtoLibraryRule.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProtoLibraryRule.java
index fc5a9e6e7b..294c3d84fe 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProtoLibraryRule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProtoLibraryRule.java
@@ -37,13 +37,16 @@ import com.google.devtools.build.lib.util.FileType;
* This is a temporary rule until it is better known how to support proto_library rules.
*/
public class ObjcProtoLibraryRule implements RuleDefinition {
- static final String COMPILE_PROTOS_ATTR = "$googlemac_proto_compiler";
- static final String PROTO_SUPPORT_ATTR = "$googlemac_proto_compiler_support";
static final String OPTIONS_FILE_ATTR = "options_file";
static final String OUTPUT_CPP_ATTR = "output_cpp";
static final String USE_OBJC_HEADER_NAMES_ATTR = "use_objc_header_names";
- static final String PER_PROTO_INCLUDES = "per_proto_includes";
- static final String LIBPROTOBUF_ATTR = "$lib_protobuf";
+ static final String PER_PROTO_INCLUDES_ATTR = "per_proto_includes";
+ static final String PORTABLE_PROTO_FILTERS_ATTR = "portable_proto_filters";
+
+ static final String PROTO_COMPILER_ATTR = "$googlemac_proto_compiler";
+ static final String PROTO_COMPILER_SUPPORT_ATTR = "$googlemac_proto_compiler_support";
+ static final String PROTO_LIB_ATTR = "$lib_protobuf";
+ static final String XCODE_GEN_ATTR = "$xcodegen";
@Override
public RuleClass build(Builder builder, final RuleDefinitionEnvironment env) {
@@ -52,10 +55,11 @@ public class ObjcProtoLibraryRule implements RuleDefinition {
/* <!-- #BLAZE_RULE(objc_proto_library).ATTRIBUTE(deps) -->
The directly depended upon proto_library rules.
<!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
- .override(attr("deps", LABEL_LIST)
- // Support for files in deps is for backwards compatibility.
- .allowedRuleClasses("proto_library", "filegroup")
- .legacyAllowAnyFileType())
+ .override(
+ attr("deps", LABEL_LIST)
+ // Support for files in deps is for backwards compatibility.
+ .allowedRuleClasses("proto_library", "filegroup")
+ .legacyAllowAnyFileType())
/* <!-- #BLAZE_RULE(objc_proto_library).ATTRIBUTE(options_file) -->
Optional options file to apply to protos which affects compilation (e.g. class
whitelist/blacklist settings).
@@ -68,31 +72,69 @@ public class ObjcProtoLibraryRule implements RuleDefinition {
/* <!-- #BLAZE_RULE(objc_proto_library).ATTRIBUTE(use_objc_header_names) -->
If true, output headers with .pbobjc.h, rather than .pb.h.
<!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
- .add(attr(PER_PROTO_INCLUDES, BOOLEAN).value(false))
+ .add(attr(USE_OBJC_HEADER_NAMES_ATTR, BOOLEAN).value(false))
/* <!-- #BLAZE_RULE(objc_proto_library).ATTRIBUTE(per_proto_includes) -->
- If true, always add all directories to objc_library includes,
+ If true, always add all directories to objc_library includes.
<!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
- .add(attr(COMPILE_PROTOS_ATTR, LABEL)
- .allowedFileTypes(FileType.of(".py"))
- .cfg(HOST)
- .singleArtifact()
- .value(env.getToolsLabel("//tools/objc:compile_protos")))
- .add(attr(PROTO_SUPPORT_ATTR, LABEL)
- .legacyAllowAnyFileType()
- .cfg(HOST)
- .value(env.getToolsLabel("//tools/objc:proto_support")))
- .add(attr(USE_OBJC_HEADER_NAMES_ATTR, BOOLEAN).value(false))
- .add(attr(LIBPROTOBUF_ATTR, LABEL).allowedRuleClasses("objc_library")
- .value(new ComputedDefault(OUTPUT_CPP_ATTR) {
- @Override
- public Object getDefault(AttributeMap rule) {
- return rule.get(OUTPUT_CPP_ATTR, Type.BOOLEAN)
- ? env.getLabel("//external:objc_proto_cpp_lib")
- : env.getLabel("//external:objc_proto_lib");
- }
- }))
- .add(attr("$xcodegen", LABEL).cfg(HOST).exec()
- .value(env.getToolsLabel("//tools/objc:xcodegen")))
+ .add(attr(PER_PROTO_INCLUDES_ATTR, BOOLEAN).value(false))
+ /* <!-- #BLAZE_RULE(objc_proto_library).ATTRIBUTE(portable_proto_filters) -->
+ List of portable proto filters to be passed on to the protobuf compiler. This attribute
+ cannot be used together with the options_file, output_cpp, per_proto_includes and
+ use_objc_header_names attributes.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(
+ attr(PORTABLE_PROTO_FILTERS_ATTR, LABEL_LIST)
+ .legacyAllowAnyFileType()
+ .allowedRuleClasses("filegroup")
+ .cfg(HOST))
+ .add(
+ attr(PROTO_COMPILER_ATTR, LABEL)
+ .allowedFileTypes(FileType.of(".py"))
+ .cfg(HOST)
+ .singleArtifact()
+ .value(
+ new ComputedDefault(PORTABLE_PROTO_FILTERS_ATTR) {
+ @Override
+ public Object getDefault(AttributeMap rule) {
+ return rule.isAttributeValueExplicitlySpecified(PORTABLE_PROTO_FILTERS_ATTR)
+ ? env.getToolsLabel("//tools/objc:protobuf_compiler")
+ : env.getToolsLabel("//tools/objc:compile_protos");
+ }
+ }))
+ .add(
+ attr(PROTO_COMPILER_SUPPORT_ATTR, LABEL)
+ .legacyAllowAnyFileType()
+ .cfg(HOST)
+ .value(
+ new ComputedDefault(PORTABLE_PROTO_FILTERS_ATTR) {
+ @Override
+ public Object getDefault(AttributeMap rule) {
+ return rule.isAttributeValueExplicitlySpecified(PORTABLE_PROTO_FILTERS_ATTR)
+ ? env.getToolsLabel("//tools/objc:protobuf_compiler_support")
+ : env.getToolsLabel("//tools/objc:proto_support");
+ }
+ }))
+ .add(
+ attr(PROTO_LIB_ATTR, LABEL)
+ .allowedRuleClasses("objc_library")
+ .value(
+ new ComputedDefault(PORTABLE_PROTO_FILTERS_ATTR, OUTPUT_CPP_ATTR) {
+ @Override
+ public Object getDefault(AttributeMap rule) {
+ if (rule.isAttributeValueExplicitlySpecified(PORTABLE_PROTO_FILTERS_ATTR)) {
+ return env.getLabel("//external:objc_protobuf_lib");
+ } else {
+ return rule.get(OUTPUT_CPP_ATTR, Type.BOOLEAN)
+ ? env.getLabel("//external:objc_proto_cpp_lib")
+ : env.getLabel("//external:objc_proto_lib");
+ }
+ }
+ }))
+ .add(
+ attr(XCODE_GEN_ATTR, LABEL)
+ .cfg(HOST)
+ .exec()
+ .value(env.getToolsLabel("//tools/objc:xcodegen")))
.build();
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ProtoSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ProtoSupport.java
new file mode 100644
index 0000000000..6d0a6a3fec
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ProtoSupport.java
@@ -0,0 +1,506 @@
+// 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 com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.PrerequisiteArtifacts;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.rules.proto.ProtoSourcesProvider;
+import com.google.devtools.build.lib.syntax.Type;
+import com.google.devtools.build.lib.util.FileType;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import javax.annotation.Nullable;
+
+/**
+ * Support for generating Objective C proto static libraries that registers actions that generate
+ * the Objective C protos and validates the rules' attributes.
+ *
+ * This ProtoSupport class supports 2 protocol buffer compilers, named ProtocolBuffers2 and the
+ * open-sourced version named protobuf. When refering to a specific library, the naming will either
+ * refer to PB2 (for ProtocolBuffers2) or protobuf (the open-source version). When the context is
+ * independent of the library, the naming will just refer to "proto". The selection of which proto
+ * library to use depends on the presence of the 'portable_proto_filters' rule attribute.
+ *
+ * Keep in mind that these libraries are independent of the proto syntax used. ProtocolBuffers2
+ * supports proto2 syntax, but the protobuf library supports both proto2 and proto3 syntax.
+ *
+ * <p>Methods on this class can be called in any order without impacting the result.
+ */
+final class ProtoSupport {
+ private static final Function<Artifact, PathFragment> PARENT_PATHFRAGMENT =
+ new Function<Artifact, PathFragment>() {
+ @Override
+ public PathFragment apply(Artifact input) {
+ return input.getExecPath().getParentDirectory();
+ }
+ };
+
+ @VisibleForTesting
+ static final String NO_PROTOS_ERROR =
+ "no protos to compile - a non-empty deps attribute is required";
+
+ @VisibleForTesting
+ static final String FILES_DEPRECATED_WARNING =
+ "Using files and filegroups in objc_proto_library is deprecated";
+
+ @VisibleForTesting
+ static final String PORTABLE_PROTO_FILTERS_NOT_EXCLUSIVE_ERROR =
+ "The portable_proto_filters attribute is incompatible with the options_file, output_cpp, "
+ + "per_proto_includes and use_objc_header_names attributes.";
+
+ @VisibleForTesting
+ static final String PORTABLE_PROTO_FILTERS_EMPTY_ERROR =
+ "The portable_proto_filters attribute can't be empty";
+
+ private static final String UNIQUE_DIRECTORY_NAME = "_generated_protos";
+
+ private final RuleContext ruleContext;
+ private final Attributes attributes;
+
+ /**
+ * Creates a new proto support.
+ *
+ * @param ruleContext context this proto library is constructed in
+ */
+ public ProtoSupport(RuleContext ruleContext) {
+ this.ruleContext = ruleContext;
+ this.attributes = new Attributes(ruleContext);
+ }
+
+ /**
+ * Validates proto support.
+ * <ul>
+ * <li>Validates that there are protos specified to be compiled.
+ * <li>Validates that, when enabling the open source protobuf library, the options for the PB2
+ * are not specified also.
+ * <li>Validates that, when enabling the open source protobuf library, the rule specifies at least
+ * one portable proto filter file.
+ * </ul>
+ *
+ * @return this proto support
+ */
+ public ProtoSupport validate() {
+ if (attributes.getProtoFiles().isEmpty()) {
+ ruleContext.ruleError(NO_PROTOS_ERROR);
+ }
+
+ if (attributes.usesProtobufLibrary()) {
+ if (attributes.getPortableProtoFilters().isEmpty()) {
+ ruleContext.ruleError(PORTABLE_PROTO_FILTERS_EMPTY_ERROR);
+ }
+
+ if (attributes.outputsCpp()
+ || attributes.usesObjcHeaderNames()
+ || attributes.needsPerProtoIncludes()
+ || attributes.getOptionsFile() != null) {
+ ruleContext.ruleError(PORTABLE_PROTO_FILTERS_NOT_EXCLUSIVE_ERROR);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Registers actions required for compiling the proto files.
+ *
+ * @return this proto support
+ */
+ public ProtoSupport registerActions() {
+ registerProtoInputListFileAction();
+ if (!attributes.getProtoFiles().isEmpty()) {
+ registerGenerateProtoFilesAction();
+ }
+ return this;
+ }
+
+ /**
+ * Adds required configuration to the ObjcCommon support class for proto compilation.
+ *
+ * @param commonBuilder The builder for the ObjcCommon support class.
+ * @return this bundle support
+ */
+ public ProtoSupport addCommonOptions(ObjcCommon.Builder commonBuilder) {
+ commonBuilder
+ .setCompilationArtifacts(getCompilationArtifacts())
+ .addUserHeaderSearchPaths(getSearchPathEntries())
+ .setIntermediateArtifacts(ObjcRuleClasses.intermediateArtifacts(ruleContext))
+ .addDepObjcProviders(
+ ruleContext.getPrerequisites(
+ ObjcProtoLibraryRule.PROTO_LIB_ATTR, Mode.TARGET, ObjcProvider.class))
+ .setHasModuleMap();
+ return this;
+ }
+
+ /**
+ * Adds required configuration to the XcodeProvider support class for proto compilation.
+ *
+ * @param xcodeProviderBuilder The builder for the XcodeProvider support class.
+ * @return this bundle support
+ */
+ public ProtoSupport addXcodeProviderOptions(XcodeProvider.Builder xcodeProviderBuilder) {
+ xcodeProviderBuilder
+ .addUserHeaderSearchPaths(getSearchPathEntries())
+ .addCopts(ObjcRuleClasses.objcConfiguration(ruleContext).getCopts())
+ .addHeaders(getGeneratedHeaders())
+ .setCompilationArtifacts(getCompilationArtifacts());
+ return this;
+ }
+
+ /**
+ * Adds the files needed to be built by the rule.
+ *
+ * @param filesToBuild An aggregated set of the files to be built by the rule.
+ * @return this bundle support
+ */
+ public ProtoSupport addFilesToBuild(NestedSetBuilder<Artifact> filesToBuild) {
+ filesToBuild.addAll(getGeneratedSources()).addAll(getGeneratedHeaders());
+ return this;
+ }
+
+ private void registerProtoInputListFileAction() {
+ ruleContext.registerAction(
+ new FileWriteAction(
+ ruleContext.getActionOwner(),
+ getProtoInputListFile(),
+ getProtoInputListFileContents(),
+ false));
+ }
+
+ private void registerGenerateProtoFilesAction() {
+ ruleContext.registerAction(
+ ObjcRuleClasses.spawnOnDarwinActionBuilder()
+ .setMnemonic("GenObjcProtos")
+ .addInputs(getGenerateActionInputs())
+ .addOutputs(getGenerateActionOutputs())
+ .setExecutable(new PathFragment("/usr/bin/python"))
+ .setCommandLine(getGenerateCommandLine())
+ .build(ruleContext));
+ }
+
+ private PathFragment getWorkspaceRelativeOutputDir() {
+ // Generate sources in a package-and-rule-scoped directory; adds both the
+ // package-and-rule-scoped directory and the header-containing-directory to the include path
+ // of dependers.
+ PathFragment rootRelativeOutputDir = ruleContext.getUniqueDirectory(UNIQUE_DIRECTORY_NAME);
+
+ return new PathFragment(
+ ruleContext.getBinOrGenfilesDirectory().getExecPath(), rootRelativeOutputDir);
+ }
+
+ private CompilationArtifacts getCompilationArtifacts() {
+ ImmutableList<Artifact> generatedSources = getGeneratedSources();
+ return new CompilationArtifacts.Builder()
+ .addNonArcSrcs(generatedSources)
+ .setIntermediateArtifacts(ObjcRuleClasses.intermediateArtifacts(ruleContext))
+ .setPchFile(Optional.<Artifact>absent())
+ .addAdditionalHdrs(getGeneratedHeaders())
+ .addAdditionalHdrs(generatedSources)
+ .build();
+ }
+
+ private ImmutableList<Artifact> getGeneratedHeaders() {
+ boolean useObjcName = attributes.usesObjcHeaderNames() || attributes.usesProtobufLibrary();
+ return generatedOutputArtifacts(FileType.of(".pb" + (useObjcName ? "objc.h" : ".h")));
+ }
+
+ private ImmutableList<Artifact> getGeneratedSources() {
+ return generatedOutputArtifacts(
+ FileType.of(
+ ".pb"
+ + (attributes.usesProtobufLibrary() ? "objc" : "")
+ + (attributes.outputsCpp() ? ".cc" : ".m")));
+ }
+
+ private ImmutableSet<PathFragment> getSearchPathEntries() {
+ PathFragment workspaceRelativeOutputDir = getWorkspaceRelativeOutputDir();
+
+ ImmutableSet.Builder<PathFragment> searchPathEntriesBuilder =
+ new ImmutableSet.Builder<PathFragment>().add(workspaceRelativeOutputDir);
+
+ if (attributes.needsPerProtoIncludes()) {
+ PathFragment generatedProtoDir =
+ new PathFragment(workspaceRelativeOutputDir, ruleContext.getLabel().getPackageFragment());
+
+ searchPathEntriesBuilder
+ .add(generatedProtoDir)
+ .addAll(Iterables.transform(getGeneratedHeaders(), PARENT_PATHFRAGMENT));
+ }
+
+ return searchPathEntriesBuilder.build();
+ }
+
+ private Artifact getProtoInputListFile() {
+ return ruleContext.getUniqueDirectoryArtifact(
+ "_protos", "_proto_input_files", ruleContext.getConfiguration().getGenfilesDirectory());
+ }
+
+ private String getProtoInputListFileContents() {
+ return Artifact.joinExecPaths("\n", attributes.getProtoFiles());
+ }
+
+ private NestedSet<Artifact> getGenerateActionInputs() {
+ NestedSetBuilder<Artifact> inputsBuilder =
+ NestedSetBuilder.<Artifact>stableOrder()
+ .add(attributes.getProtoCompiler())
+ .addAll(attributes.getProtoFiles())
+ .add(getProtoInputListFile())
+ .addAll(attributes.getProtoLibrary())
+ .addAll(attributes.getProtoCompilerSupport())
+ .addAll(attributes.getPortableProtoFilters());
+
+ Artifact optionsFile = attributes.getOptionsFile();
+ if (optionsFile != null) {
+ inputsBuilder.add(optionsFile);
+ }
+
+ return inputsBuilder.build();
+ }
+
+ private Iterable<Artifact> getGenerateActionOutputs() {
+ return Iterables.concat(getGeneratedHeaders(), getGeneratedSources());
+ }
+
+ private CustomCommandLine getGenerateCommandLine() {
+ if (attributes.usesProtobufLibrary()) {
+ return getProtobufCommandLine();
+ } else {
+ return getPb2CommandLine();
+ }
+ }
+
+ private CustomCommandLine getPb2CommandLine() {
+ CustomCommandLine.Builder commandLineBuilder =
+ new CustomCommandLine.Builder()
+ .add(attributes.getProtoCompiler().getExecPathString())
+ .add("--input-file-list")
+ .add(getProtoInputListFile().getExecPathString())
+ .add("--output-dir")
+ .add(getWorkspaceRelativeOutputDir().getSafePathString())
+ .add("--working-dir")
+ .add(".");
+
+ if (attributes.getOptionsFile() != null) {
+ commandLineBuilder
+ .add("--compiler-options-path")
+ .add(attributes.getOptionsFile().getExecPathString());
+ }
+
+ if (attributes.outputsCpp()) {
+ commandLineBuilder.add("--generate-cpp");
+ }
+
+ if (attributes.usesObjcHeaderNames()) {
+ commandLineBuilder.add("--use-objc-header-names");
+ }
+ return commandLineBuilder.build();
+ }
+
+ private CustomCommandLine getProtobufCommandLine() {
+ CustomCommandLine.Builder commandLineBuilder =
+ new CustomCommandLine.Builder()
+ .add(attributes.getProtoCompiler().getExecPathString())
+ .add("--input-file-list")
+ .add(getProtoInputListFile().getExecPathString())
+ .add("--output-dir")
+ .add(getWorkspaceRelativeOutputDir().getSafePathString())
+ .add("--force")
+ .add("--proto-root-dir")
+ .add(".");
+
+ boolean configAdded = false;
+ for (Artifact portableProtoFilter : attributes.getPortableProtoFilters()) {
+ String configFlag;
+ if (!configAdded) {
+ configFlag = "--config";
+ configAdded = true;
+ } else {
+ configFlag = "--extra-filter-config";
+ }
+
+ commandLineBuilder.add(configFlag).add(portableProtoFilter.getExecPathString());
+ }
+ return commandLineBuilder.build();
+ }
+
+ private ImmutableList<Artifact> generatedOutputArtifacts(FileType newFileType) {
+ ImmutableList.Builder<Artifact> builder = new ImmutableList.Builder<>();
+ for (Artifact protoFile : attributes.getProtoFiles()) {
+ String generatedOutputName;
+ if (attributes.outputsCpp()) {
+ generatedOutputName = protoFile.getFilename();
+ } else {
+ String lowerUnderscoreBaseName = protoFile.getFilename().replace('-', '_').toLowerCase();
+ generatedOutputName = LOWER_UNDERSCORE.to(UPPER_CAMEL, lowerUnderscoreBaseName);
+ }
+
+ PathFragment generatedFilePath =
+ new PathFragment(
+ protoFile.getRootRelativePath().getParentDirectory(),
+ new PathFragment(generatedOutputName));
+
+ PathFragment outputFile =
+ FileSystemUtils.replaceExtension(
+ generatedFilePath, newFileType.getExtensions().get(0), ".proto");
+
+ if (outputFile != null) {
+ builder.add(
+ ruleContext.getUniqueDirectoryArtifact(
+ UNIQUE_DIRECTORY_NAME, outputFile, ruleContext.getBinOrGenfilesDirectory()));
+ }
+ }
+ return builder.build();
+ }
+
+ /**
+ * Common rule attributes used by an Objective C proto library.
+ */
+ private static class Attributes {
+ private final RuleContext ruleContext;
+
+ private Attributes(RuleContext ruleContext) {
+ this.ruleContext = ruleContext;
+ }
+
+ /**
+ * Returns whether the generated files should be C++ or Objective C.
+ */
+ boolean outputsCpp() {
+ return ruleContext.attributes().get(ObjcProtoLibraryRule.OUTPUT_CPP_ATTR, Type.BOOLEAN);
+ }
+
+ /**
+ * Returns whether the generated header files should have be of type pb.h or pbobjc.h.
+ */
+ boolean usesObjcHeaderNames() {
+ return ruleContext
+ .attributes()
+ .get(ObjcProtoLibraryRule.USE_OBJC_HEADER_NAMES_ATTR, Type.BOOLEAN);
+ }
+
+ /**
+ * Returns whether the includes should include each of the proto generated headers.
+ */
+ boolean needsPerProtoIncludes() {
+ return ruleContext
+ .attributes()
+ .get(ObjcProtoLibraryRule.PER_PROTO_INCLUDES_ATTR, Type.BOOLEAN);
+ }
+
+ /**
+ * Returns whether to use the protobuf library instead of the PB2 library.
+ */
+ boolean usesProtobufLibrary() {
+ return ruleContext
+ .attributes()
+ .isAttributeValueExplicitlySpecified(ObjcProtoLibraryRule.PORTABLE_PROTO_FILTERS_ATTR);
+ }
+
+ /**
+ * Returns the list of portable proto filters.
+ */
+ ImmutableList<Artifact> getPortableProtoFilters() {
+ return ruleContext
+ .getPrerequisiteArtifacts(ObjcProtoLibraryRule.PORTABLE_PROTO_FILTERS_ATTR, Mode.HOST)
+ .list();
+ }
+
+ /**
+ * Returns the options file, or null if it was not specified.
+ */
+ @Nullable
+ Artifact getOptionsFile() {
+ return ruleContext.getPrerequisiteArtifact(ObjcProtoLibraryRule.OPTIONS_FILE_ATTR, Mode.HOST);
+ }
+
+ /**
+ * Returns the proto compiler to be used.
+ */
+ Artifact getProtoCompiler() {
+ return ruleContext.getPrerequisiteArtifact(
+ ObjcProtoLibraryRule.PROTO_COMPILER_ATTR, Mode.HOST);
+ }
+
+ /**
+ * Returns the list of files needed by the proto compiler.
+ */
+ ImmutableList<Artifact> getProtoCompilerSupport() {
+ return ruleContext
+ .getPrerequisiteArtifacts(ObjcProtoLibraryRule.PROTO_COMPILER_SUPPORT_ATTR, Mode.HOST)
+ .list();
+ }
+
+ /**
+ * Returns the list of files that compose the proto library. This is the implicit dependency
+ * added to the objc_proto_library target.
+ */
+ ImmutableList<Artifact> getProtoLibrary() {
+ return ruleContext
+ .getPrerequisiteArtifacts(ObjcProtoLibraryRule.PROTO_LIB_ATTR, Mode.TARGET)
+ .list();
+ }
+
+ /**
+ * Returns the list of proto files to compile.
+ */
+ NestedSet<Artifact> getProtoFiles() {
+ return NestedSetBuilder.<Artifact>stableOrder()
+ .addAll(getProtoDepsFiles())
+ .addTransitive(getProtoDepsSources())
+ .build();
+ }
+
+ /**
+ * Returns the list of proto files that were added directly into the deps attributes. This way
+ * of specifying the protos is deprecated, and displays a warning when the target does so.
+ */
+ private ImmutableList<Artifact> getProtoDepsFiles() {
+ PrerequisiteArtifacts prerequisiteArtifacts =
+ ruleContext.getPrerequisiteArtifacts("deps", Mode.TARGET);
+ ImmutableList<Artifact> protos = prerequisiteArtifacts.filter(FileType.of(".proto")).list();
+ if (!protos.isEmpty()) {
+ ruleContext.attributeWarning("deps", FILES_DEPRECATED_WARNING);
+ }
+ return protos;
+ }
+
+ /**
+ * Returns the list of proto files that were added using proto_library dependencies.
+ */
+ private NestedSet<Artifact> getProtoDepsSources() {
+ NestedSetBuilder<Artifact> artifacts = NestedSetBuilder.stableOrder();
+ Iterable<ProtoSourcesProvider> providers =
+ ruleContext.getPrerequisites("deps", Mode.TARGET, ProtoSourcesProvider.class);
+ for (ProtoSourcesProvider provider : providers) {
+ artifacts.addTransitive(provider.getTransitiveProtoSources());
+ }
+ return artifacts.build();
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
index 8b5817ffda..6304712c6c 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
@@ -71,6 +71,10 @@ public final class BazelAnalysisMock extends AnalysisMock {
" name = 'objc_proto_cpp_lib',",
" actual = '//objcproto:ProtocolBuffersCPP_lib',",
")",
+ "bind(",
+ " name = 'objc_protobuf_lib',",
+ " actual = '//objcproto:protobuf_lib',",
+ ")",
"bind(name = 'android/sdk', actual='@bazel_tools//tools/android:sdk')",
"bind(name = 'tools/python', actual='//tools/python')"));