// 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 static com.google.common.truth.Truth.assertThat; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.CommandAction; import com.google.devtools.build.lib.actions.util.ActionsTestUtil; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.actions.FileWriteAction; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.actions.SymlinkAction; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.packages.util.MockObjcSupport; import com.google.devtools.build.lib.packages.util.MockProtoSupport; import com.google.devtools.build.lib.rules.apple.ApplePlatform; import com.google.devtools.build.lib.rules.apple.AppleToolchain; import com.google.devtools.build.lib.rules.cpp.CppModuleMapAction; import com.google.devtools.build.lib.testutil.TestConstants; import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Test case for objc_proto_library. */ @RunWith(JUnit4.class) public class ObjcProtoLibraryTest extends ObjcRuleTestCase { @Before public final void initializeToolsConfigMock() throws Exception { MockProtoSupport.setup(mockToolsConfig); MockObjcSupport.setup(mockToolsConfig); } @Before public final void createFiles() throws Exception { scratch.file( "package/BUILD", "apple_binary(", " name = 'opl_binary',", " deps = [':opl_protobuf'],", " platform_type = 'ios'", ")", "objc_library(", " name = 'non_strict_lib',", " deps = [':strict_lib'],", ")", "", "objc_library(", " name = 'strict_lib',", " deps = [':opl_protobuf'],", ")", "", "objc_proto_library(", " name = 'nested_opl',", " deps = [':opl_protobuf'],", " portable_proto_filters = ['nested_filter.txt'],", ")", "", "objc_proto_library(", " name = 'opl_protobuf',", " deps = [':protolib'],", " portable_proto_filters = [", " 'proto_filter.txt',", " ':portable_proto_filters',", " ],", ")", "", "objc_proto_library(", " name = 'opl_protobuf_well_known_types',", " deps = [':protolib_well_known_types'],", " portable_proto_filters = [", " 'proto_filter.txt',", " ],", ")", "", "filegroup(", " name = 'portable_proto_filters',", " srcs = [", " 'proto_filter2.txt',", " 'proto_filter3.txt',", " ],", ")", "", "proto_library(", " name = 'protolib',", " srcs = ['file_a.proto', 'dir/file_b.proto'],", " deps = ['//dep:dep_lib'],", ")", "", "objc_proto_library(", " name = 'opl_protobuf_special_names',", " deps = [':protolib_special_names'],", " portable_proto_filters = [", " 'proto_filter.txt',", " ],", ")", "objc_proto_library(", " name = 'opl_pb2_special_names',", " deps = [':protolib_special_names'],", ")", "", "proto_library(", " name = 'protolib_special_names',", " srcs = [", " 'j2objc-descriptor.proto',", " 'http.proto',", " 'https.proto',", " 'some_url_blah.proto',", " 'thumbnail_url.proto',", " 'url.proto',", " 'url2https.proto',", " 'urlbar.proto',", " ],", " deps = ['//dep:dep_lib'],", ")", "", "proto_library(", " name = 'protolib_well_known_types',", " srcs = ['file_a.proto'],", " deps = ['" + TestConstants.TOOLS_REPOSITORY + "//objcproto:well_known_type_proto'],", ")", "", "genrule(", " name = 'gen_proto',", " srcs = ['file_a.proto'],", " outs = ['file_a_genfile.proto'],", " cmd = 'cp $(location file_a.proto) $(location file_a_genfile.proto)')", "", "proto_library(", " name = 'gen_protolib',", " srcs = ['file_a_genfile.proto'],", " deps = ['//dep:dep_lib'],", ")", "objc_proto_library(", " name = 'gen_opl',", " deps = [':gen_protolib'],", ")"); scratch.file("dep/BUILD", "proto_library(", " name = 'dep_lib',", " srcs = ['file.proto'],", ")"); scratch.file("package/file_a.proto"); scratch.file("package/dir/file_b.proto"); scratch.file("dep/file.proto"); scratch.file("package/proto_filter.txt"); scratch.file("package/proto_filter2.txt"); scratch.file("package/proto_filter3.txt"); } @Test public void testOutputs() throws Exception { NestedSet filesToBuild = getFilesToBuild(getConfiguredTarget("//package:opl_protobuf")); assertThat(Artifact.toRootRelativePaths(filesToBuild)) .containsAllOf( "package/_generated_protos/opl_protobuf/package/FileA.pbobjc.h", "package/_generated_protos/opl_protobuf/package/FileA.pbobjc.m", "package/_generated_protos/opl_protobuf/package/dir/FileB.pbobjc.h", "package/_generated_protos/opl_protobuf/package/dir/FileB.pbobjc.m", "package/_generated_protos/opl_protobuf/dep/File.pbobjc.h"); } @Test public void testDependingObjcProtoLibrary() throws Exception { NestedSet filesToBuild = getFilesToBuild(getConfiguredTarget("//package:nested_opl")); assertThat(Artifact.toRootRelativePaths(filesToBuild)) .containsAllOf( "package/_generated_protos/nested_opl/package/FileA.pbobjc.h", "package/_generated_protos/nested_opl/package/FileA.pbobjc.m", "package/_generated_protos/nested_opl/package/dir/FileB.pbobjc.h", "package/_generated_protos/nested_opl/package/dir/FileB.pbobjc.m"); } @Test public void testOutputsWithAutoUnion() throws Exception { NestedSet filesToBuild = getFilesToBuild(getConfiguredTarget("//package:opl_protobuf")); assertThat(Artifact.toRootRelativePaths(filesToBuild)) .doesNotContain("package/libopl_protobuf.a"); assertThat(Artifact.toRootRelativePaths(filesToBuild)) .containsAllOf( "package/_generated_protos/opl_protobuf/package/FileA.pbobjc.h", "package/_generated_protos/opl_protobuf/package/FileA.pbobjc.m", "package/_generated_protos/opl_protobuf/package/dir/FileB.pbobjc.h", "package/_generated_protos/opl_protobuf/package/dir/FileB.pbobjc.m", "package/_generated_protos/opl_protobuf/dep/File.pbobjc.h"); } @Test public void testGeneratedFileNames() throws Exception { NestedSet filesToBuild = getFilesToBuild(getConfiguredTarget("//package:opl_protobuf_special_names")); String outputPath = "package/_generated_protos/opl_protobuf_special_names/package/"; assertThat(Artifact.toRootRelativePaths(filesToBuild)) .containsAllOf( outputPath + "J2ObjcDescriptor.pbobjc.h", outputPath + "J2ObjcDescriptor.pbobjc.m", outputPath + "HTTP.pbobjc.h", outputPath + "HTTP.pbobjc.m", outputPath + "HTTPS.pbobjc.h", outputPath + "HTTPS.pbobjc.m", outputPath + "SomeURLBlah.pbobjc.h", outputPath + "SomeURLBlah.pbobjc.m", outputPath + "ThumbnailURL.pbobjc.h", outputPath + "ThumbnailURL.pbobjc.m", outputPath + "URL.pbobjc.h", outputPath + "URL.pbobjc.m", outputPath + "URL2HTTPS.pbobjc.h", outputPath + "URL2HTTPS.pbobjc.m", outputPath + "Urlbar.pbobjc.h", outputPath + "Urlbar.pbobjc.m"); } @Test public void testOutputsWithWellKnownTypes() throws Exception { NestedSet filesToBuild = getFilesToBuild(getConfiguredTarget("//package:opl_protobuf_well_known_types")); assertThat(Artifact.toRootRelativePaths(filesToBuild)) .containsAllOf( "package/_generated_protos/opl_protobuf_well_known_types/package/FileA.pbobjc.h", "package/_generated_protos/opl_protobuf_well_known_types/package/FileA.pbobjc.m"); assertThat(Artifact.toRootRelativePaths(filesToBuild)) .doesNotContain( "package/_generated_protos/opl_protobuf_well_known_types/objcproto/WellKnownType.pbobjc.h"); assertThat(Artifact.toRootRelativePaths(filesToBuild)) .doesNotContain( "package/_generated_protos/opl_protobuf_well_known_types/objcproto/WellKnownType.pbobjc.m"); } @Test public void testOutputsGenfile() throws Exception { NestedSet filesToBuild = getFilesToBuild(getConfiguredTarget("//package:gen_opl")); assertThat(Artifact.toRootRelativePaths(filesToBuild)) .containsAllOf( "package/_generated_protos/gen_opl/package/FileAGenfile.pbobjc.h", "package/_generated_protos/gen_opl/package/FileAGenfile.pbobjc.m"); } @Test public void testSourceGenerationAction() throws Exception { Artifact sourceFile = ActionsTestUtil.getFirstArtifactEndingWith( getFilesToBuild(getConfiguredTarget("//package:opl_protobuf")), "/FileA.pbobjc.m"); SpawnAction action = (SpawnAction) getGeneratingAction(sourceFile); Artifact inputFileList = ActionsTestUtil.getFirstArtifactEndingWith( action.getInputs(), "/_proto_input_files_BundledProtos_0"); ImmutableList protoInputs = ImmutableList.of("dep/file.proto", "package/file_a.proto", "package/dir/file_b.proto"); BuildConfiguration topLevelConfig = getAppleCrosstoolConfiguration(); assertThat(action.getArguments()) .containsExactly( TestConstants.TOOLS_REPOSITORY_PATH_PREFIX + "tools/objc/protobuf_compiler_wrapper.sh", "--input-file-list", inputFileList.getExecPathString(), "--output-dir", // 2x parent directory because the package has one element ("package") sourceFile.getExecPath().getParentDirectory().getParentDirectory().toString(), "--force", "--proto-root-dir", topLevelConfig.getGenfilesFragment().toString(), "--proto-root-dir", ".", "--config", "package/proto_filter.txt", "--config", "package/proto_filter2.txt", "--config", "package/proto_filter3.txt") .inOrder(); assertThat(Artifact.toRootRelativePaths(action.getInputs())) .containsAllOf( TestConstants.TOOLS_REPOSITORY_PATH_PREFIX + "tools/objc/protobuf_compiler_wrapper.sh", TestConstants.TOOLS_REPOSITORY_PATH_PREFIX + "tools/objc/protobuf_compiler_helper.py", TestConstants.TOOLS_REPOSITORY_PATH_PREFIX + "tools/objc/proto_support"); assertThat(Artifact.toRootRelativePaths(action.getInputs())).containsAllIn(protoInputs); assertThat(action.getInputs()).contains(inputFileList); FileWriteAction inputListAction = (FileWriteAction) getGeneratingAction(inputFileList); assertThat(inputListAction.getFileContents()).isEqualTo(sortedJoin(protoInputs)); } @Test public void testWellKnownTypesProtoListInput() throws Exception { Artifact sourceFile = ActionsTestUtil.getFirstArtifactEndingWith( getFilesToBuild(getConfiguredTarget("//package:opl_protobuf_well_known_types")), "/FileA.pbobjc.m"); SpawnAction action = (SpawnAction) getGeneratingAction(sourceFile); Artifact inputFileList = ActionsTestUtil.getFirstArtifactEndingWith( action.getInputs(), "/_proto_input_files_BundledProtos_0"); ImmutableList protoInputs = ImmutableList.of( "package/file_a.proto", TestConstants.TOOLS_REPOSITORY_PATH_PREFIX + "objcproto/well_known_type.proto"); assertThat(Artifact.toRootRelativePaths(action.getInputs())).containsAllIn(protoInputs); assertThat(action.getInputs()).contains(inputFileList); FileWriteAction inputListAction = (FileWriteAction) getGeneratingAction(inputFileList); assertThat(inputListAction.getFileContents()).contains("package/file_a.proto"); } @Test public void testObjcProviderWithAutoUnion() throws Exception { ConfiguredTarget target = getConfiguredTarget("//package:opl_protobuf"); Artifact headerFile = ActionsTestUtil.getFirstArtifactEndingWith(getFilesToBuild(target), "/FileA.pbobjc.h"); ObjcProvider provider = providerForTarget("//package:opl_protobuf"); assertThat(provider.get(ObjcProvider.INCLUDE).toSet()) .contains(headerFile.getExecPath().getParentDirectory().getParentDirectory()); assertThat(provider.get(ObjcProvider.LIBRARY).toSet()) .doesNotContain(getBinArtifact("libopl_protobuf.a", target)); assertThat(provider.get(ObjcProvider.HEADER).toSet()).contains(headerFile); } @Test public void testErrorForNoDepsAttribute() throws Exception { checkError( "x", "x", ProtoAttributes.NO_PROTOS_ERROR, "objc_proto_library(", " name = 'x',", ")"); } @Test public void testErrorForEmptyDepsAttribute() throws Exception { checkError( "x", "x", ProtoAttributes.NO_PROTOS_ERROR, "objc_proto_library(", " name = 'x',", " deps = [],", ")"); } @Test public void testErrorForFileInDeps() throws Exception { String expectedError = "filegroup rule '//x:protos' is misplaced here " + "(expected proto_library or objc_proto_library)"; checkError( "x", "x", expectedError, "objc_proto_library(", " name = 'x',", " deps = [':protos'],", ")", "filegroup(", " name = 'protos',", " srcs = ['file.proto'],", ")"); } @Test public void testErrorForPortableProtoFiltersEmpty() throws Exception { checkError( "x", "x", ProtoAttributes.PORTABLE_PROTO_FILTERS_EMPTY_ERROR, "objc_proto_library(", " name = 'x',", " portable_proto_filters = [],", " deps = [':protos'],", ")", "proto_library(", " name = 'protos',", " srcs = ['file.proto'],", ")"); } @Test public void testModulemapCreatedForNonLinkingTargets() throws Exception { // TODO(b/73943026): Remove this flag once everyone has migrated to the new strict behavior and // it is made the default. useConfiguration("--incompatible_strict_objc_module_maps"); // The library target should propagate its module map. ObjcProvider provider = providerForTarget("//package:opl_protobuf"); assertThat(Artifact.toRootRelativePaths(provider.get(ObjcProvider.MODULE_MAP).toSet())) .containsExactly("package/opl_protobuf.modulemaps/module.modulemap"); } @Test public void testModulemapNotCreatedForLinkingTargets() throws Exception { // TODO(b/73943026): Remove this flag once everyone has migrated to the new strict behavior and // it is made the default. useConfiguration("--incompatible_strict_objc_module_maps"); // The binary target should not propagate the module map from the library it depends on. ObjcProvider provider = providerForTarget("//package:opl_binary"); assertThat(Artifact.toRootRelativePaths(provider.get(ObjcProvider.MODULE_MAP).toSet())) .isEmpty(); } private static String sortedJoin(Iterable elements) { return Joiner.on('\n').join(Ordering.natural().immutableSortedCopy(elements)); } @Test public void testObjcProvider() throws Exception { ConfiguredTarget target = getConfiguredTarget("//package:opl_protobuf"); Artifact headerFile = ActionsTestUtil.getFirstArtifactEndingWith(getFilesToBuild(target), "/FileA.pbobjc.h"); ObjcProvider provider = providerForTarget("//package:opl_protobuf"); assertThat(provider.get(ObjcProvider.INCLUDE).toSet()) .contains(headerFile.getExecPath().getParentDirectory().getParentDirectory()); assertThat(Artifact.toRootRelativePaths(provider.get(ObjcProvider.LIBRARY).toSet())) .containsExactly(TestConstants.TOOLS_REPOSITORY_PATH_PREFIX + "objcproto/libprotobuf_lib.a"); assertThat(provider.get(ObjcProvider.HEADER).toSet()).contains(headerFile); assertThat(provider.get(ObjcProvider.INCLUDE).toSet()) .contains(headerFile.getExecPath().getParentDirectory().getParentDirectory()); } @Test public void testModuleMapActionFiltersHeaders() throws Exception { ConfiguredTarget configuredTarget = getConfiguredTarget("//package:opl_protobuf"); Artifact moduleMap = getGenfilesArtifact("opl_protobuf.modulemaps/module.modulemap", configuredTarget); CppModuleMapAction genMap = (CppModuleMapAction) getGeneratingAction(moduleMap); assertThat(Artifact.toRootRelativePaths(genMap.getPrivateHeaders())).isEmpty(); assertThat(Artifact.toRootRelativePaths(genMap.getPublicHeaders())) .containsExactly( "package/_generated_protos/opl_protobuf/package/FileA.pbobjc.h", "package/_generated_protos/opl_protobuf/package/dir/FileB.pbobjc.h", "package/_generated_protos/opl_protobuf/dep/File.pbobjc.h"); } @Test public void testCompilationAction() throws Exception { useConfiguration("--cpu=ios_i386", "--apple_crosstool_in_output_directory_name"); ApplePlatform platform = ApplePlatform.IOS_SIMULATOR; // Because protos are linked/compiled within the apple_binary context, we need to traverse the // action graph to find the linked protos (.a) and compiled protos (.o). ConfiguredTarget binaryTarget = getConfiguredTarget("//package:opl_binary"); SymlinkAction symlinkAction = (SymlinkAction) getGeneratingAction(getBinArtifact("opl_binary_lipobin", binaryTarget)); Artifact binaryInput = Iterables.getOnlyElement(symlinkAction.getInputs()); CommandAction linkAction = (CommandAction) getGeneratingAction(binaryInput); Artifact linkedProtos = ActionsTestUtil.getFirstArtifactEndingWith( linkAction.getInputs(), "libopl_binary_BundledProtos.a"); CommandAction linkedProtosAction = (CommandAction) getGeneratingAction(linkedProtos); Artifact objectFile = ActionsTestUtil.getFirstArtifactEndingWith( linkedProtosAction.getInputs(), "FileA.pbobjc.o"); CommandAction compiledProtoAction = (CommandAction) getGeneratingAction(objectFile); Artifact sourceFile = ActionsTestUtil.getFirstArtifactEndingWith( compiledProtoAction.getInputs(), "/FileA.pbobjc.m"); Artifact dotdFile = ActionsTestUtil.getFirstArtifactEndingWith(compiledProtoAction.getOutputs(), ".d"); // We remove spaces since the crosstool rules do not use spaces in command line args. String compileArgs = Joiner.on("").join(compiledProtoAction.getArguments()).replace(" ", ""); List expectedArgs = new ImmutableList.Builder() .addAll(AppleToolchain.DEFAULT_WARNINGS.values()) .add("-fexceptions") .add("-fasm-blocks") .add("-fobjc-abi-version=2") .add("-fobjc-legacy-dispatch") .addAll(CompilationSupport.DEFAULT_COMPILER_FLAGS) .add("-mios-simulator-version-min=" + DEFAULT_IOS_SDK_VERSION) .add("-arch", "i386") .add("-isysroot", AppleToolchain.sdkDir()) .add("-F", AppleToolchain.sdkDir() + AppleToolchain.DEVELOPER_FRAMEWORK_PATH) .add("-F", frameworkDir(platform)) .addAll(FASTBUILD_COPTS) .addAll( ObjcLibraryTest.iquoteArgs( providerForTarget("//package:opl_binary"), getAppleCrosstoolConfiguration())) .add("-I") .add(sourceFile.getExecPath().getParentDirectory().getParentDirectory().toString()) .add("-fno-objc-arc") .add("-c", sourceFile.getExecPathString()) .add("-o") .add(objectFile.getExecPathString()) .add("-MD") .add("-MF") .add(dotdFile.getExecPathString()) .build(); for (String expectedArg : expectedArgs) { assertThat(compileArgs).contains(expectedArg); } assertRequiresDarwin(compiledProtoAction); assertThat(Artifact.toRootRelativePaths(compiledProtoAction.getInputs())) .containsAllOf( "package/_generated_protos/opl_binary/package/FileA.pbobjc.m", "package/_generated_protos/opl_binary/package/FileA.pbobjc.h", "package/_generated_protos/opl_binary/package/dir/FileB.pbobjc.h", "package/_generated_protos/opl_binary/dep/File.pbobjc.h"); } @Test public void testLibraryLinkAction() throws Exception { useConfiguration("--cpu=ios_armv7"); // Because protos are linked within the apple_binary context, we need to traverse the action // graph to find the linked protos (.a). ConfiguredTarget binaryTarget = getConfiguredTarget("//package:opl_binary"); SymlinkAction symlinkAction = (SymlinkAction) getGeneratingAction(getBinArtifact("opl_binary_lipobin", binaryTarget)); Artifact binaryInput = Iterables.getOnlyElement(symlinkAction.getInputs()); CommandAction linkAction = (CommandAction) getGeneratingAction(binaryInput); Artifact linkedProtos = ActionsTestUtil.getFirstArtifactEndingWith( linkAction.getInputs(), "libopl_binary_BundledProtos.a"); CommandAction linkedProtosAction = (CommandAction) getGeneratingAction(linkedProtos); Artifact objListFile = ActionsTestUtil.getFirstArtifactEndingWith(linkedProtosAction.getInputs(), ".objlist"); assertThat(linkedProtosAction.getArguments()) .containsAllIn( ImmutableList.of( "-static", "-filelist", objListFile.getExecPathString(), "-arch_only", "armv7", "-syslibroot", AppleToolchain.sdkDir(), "-o", linkedProtos.getExecPathString())); assertRequiresDarwin(linkedProtosAction); } }