diff options
Diffstat (limited to 'src/test/java/com/google/devtools/build/lib/rules')
53 files changed, 21892 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/AppleBinaryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleBinaryTest.java new file mode 100644 index 0000000000..37a12f9626 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleBinaryTest.java @@ -0,0 +1,1410 @@ +// 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 static com.google.devtools.build.lib.actions.util.ActionsTestUtil.getFirstArtifactEndingWith; + +import com.google.common.base.Joiner; +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.Action; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.analysis.actions.SymlinkAction; +import com.google.devtools.build.lib.rules.apple.AppleConfiguration.ConfigurationDistinguisher; +import com.google.devtools.build.lib.rules.objc.AppleBinary.BinaryType; +import com.google.devtools.build.lib.rules.objc.CompilationSupport.ExtraLinkArgs; +import com.google.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider; +import com.google.devtools.build.lib.syntax.SkylarkDict; +import com.google.devtools.build.lib.testutil.Scratch; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for apple_binary. */ +@RunWith(JUnit4.class) +public class AppleBinaryTest extends ObjcRuleTestCase { + static final RuleType RULE_TYPE = new RuleType("apple_binary") { + @Override + Iterable<String> requiredAttributes(Scratch scratch, String packageDir, + Set<String> alreadyAdded) throws IOException { + ImmutableList.Builder<String> attributes = new ImmutableList.Builder<>(); + if (!alreadyAdded.contains("srcs") && !alreadyAdded.contains("non_arc_srcs")) { + scratch.file(packageDir + "/a.m"); + scratch.file(packageDir + "/private.h"); + attributes.add("srcs = ['a.m', 'private.h']"); + } + if (!alreadyAdded.contains("platform_type")) { + attributes.add("platform_type = 'ios'"); + } + return attributes.build(); + } + }; + + private static final String COCOA_FRAMEWORK_FLAG = "-framework Cocoa"; + private static final String FOUNDATION_FRAMEWORK_FLAG = "-framework Foundation"; + private static final String UIKIT_FRAMEWORK_FLAG = "-framework UIKit"; + private static final ImmutableSet<String> IMPLICIT_NON_MAC_FRAMEWORK_FLAGS = + ImmutableSet.of(FOUNDATION_FRAMEWORK_FLAG, UIKIT_FRAMEWORK_FLAG); + private static final ImmutableSet<String> IMPLICIT_MAC_FRAMEWORK_FLAGS = + ImmutableSet.of(FOUNDATION_FRAMEWORK_FLAG); + private static final ImmutableSet<String> COCOA_FEATURE_FLAGS = + ImmutableSet.of(COCOA_FRAMEWORK_FLAG); + + @Test + public void testLipoActionEnv() throws Exception { + RULE_TYPE.scratchTarget(scratch, + "srcs", "['a.m']", + "platform_type", "'watchos'"); + + useConfiguration("--watchos_cpus=i386,armv7k", "--xcode_version=7.3", + "--watchos_sdk_version=2.1"); + + CommandAction action = (CommandAction) lipoBinAction("//x:x"); + assertAppleSdkVersionEnv(action, "2.1"); + assertAppleSdkPlatformEnv(action, "WatchOS"); + assertXcodeVersionEnv(action, "7.3"); + } + + @Test + public void testSymlinkInsteadOfLipoSingleArch() throws Exception { + RULE_TYPE.scratchTarget(scratch, + "srcs", "['a.m']"); + + SymlinkAction action = (SymlinkAction) lipoBinAction("//x:x"); + CommandAction linkAction = linkAction("//x:x"); + + assertThat(action.getInputs()) + .containsExactly(Iterables.getOnlyElement(linkAction.getOutputs())); + } + + @Test + public void testLipoActionEnv_sdkVersionPadding() throws Exception { + RULE_TYPE.scratchTarget(scratch, + "srcs", "['a.m']", + "platform_type", "'watchos'"); + + useConfiguration("--watchos_cpus=i386,armv7k", + "--xcode_version=7.3", "--watchos_sdk_version=2"); + + CommandAction action = (CommandAction) lipoBinAction("//x:x"); + assertAppleSdkVersionEnv(action, "2.0"); + } + + @Test + public void testCcDependencyLinkoptsArePropagatedToLinkAction() throws Exception { + checkCcDependencyLinkoptsArePropagatedToLinkAction(RULE_TYPE); + } + + @Test + public void testUnknownPlatformType() throws Exception { + checkError( + "package", + "test", + String.format(MultiArchSplitTransitionProvider.UNSUPPORTED_PLATFORM_TYPE_ERROR_FORMAT, + "meow_meow_os"), + "apple_binary(name = 'test', srcs = [ 'a.m' ], platform_type = 'meow_meow_os')"); + } + + @Test + public void testProtoDylibDeps() throws Exception { + checkProtoDedupingDeps(BinaryType.DYLIB); + } + + @Test + public void testProtoBundleLoaderDeps() throws Exception { + checkProtoDedupingDeps(BinaryType.LOADABLE_BUNDLE); + } + + /** + * Test scenario where all proto symbols are contained within the lower level dependency. There is + * an implicit dependency hierarchy between the different apple_binary types (executable, + * loadable_bundle, dylib) which looks like this (top level binary types can depend on lower level + * binary types): + * + * loadable_bundle + * / \ + * dylib(s) executable + * | + * dylibs + * + * The mechanism to remove duplicate dependencies between dylibs and executable binaries works the + * same way between executable and loadable_bundle binaries; the only difference is through which + * attribute the dependency is declared (dylibs vs bundle_loader). This test scenario sets up + * dependencies for low level and top level binaries and checks that the correct files are linked + * for each of the binaries. + * + * @param depBinaryType either {@link BinaryType#DYLIB} or {@link BinaryType#LOADABLE_BUNDLE}, as + * this deduping test is applicable for either + */ + private void checkProtoDedupingDeps(BinaryType depBinaryType) throws Exception { + scratch.file( + "protos/BUILD", + "proto_library(", + " name = 'protos_1',", + " srcs = ['data_a.proto', 'data_b.proto'],", + ")", + "proto_library(", + " name = 'protos_2',", + " srcs = ['data_b.proto', 'data_c.proto'],", + ")", + "proto_library(", + " name = 'protos_3',", + " srcs = ['data_a.proto', 'data_c.proto', 'data_d.proto'],", + ")", + "objc_proto_library(", + " name = 'objc_protos_a',", + " portable_proto_filters = ['filter_a.pbascii'],", + " deps = [':protos_1'],", + ")", + "objc_proto_library(", + " name = 'objc_protos_b',", + " portable_proto_filters = ['filter_b.pbascii'],", + " deps = [':protos_2', ':protos_3'],", + ")"); + scratch.file( + "libs/BUILD", + "objc_library(", + " name = 'objc_lib',", + " srcs = ['a.m'],", + " deps = ['//protos:objc_protos_a', '//protos:objc_protos_b']", + ")"); + + if (depBinaryType == BinaryType.DYLIB) { + scratchFrameworkSkylarkStub("frameworkstub/framework_stub.bzl"); + scratch.file( + "depBinary/BUILD", + "load('//frameworkstub:framework_stub.bzl', 'framework_stub_rule')", + "apple_binary(", + " name = 'apple_low_level_binary',", + " srcs = ['b.m'],", + " deps = ['//libs:objc_lib'],", + " platform_type = 'ios',", + " binary_type = 'dylib',", + ")", + "framework_stub_rule(", + " name = 'low_level_framework',", + " deps = [':apple_low_level_binary'],", + " binary = ':apple_low_level_binary')"); + RULE_TYPE.scratchTarget( + scratch, + "binary_type", + "'executable'", + "srcs", + "['main.m']", + "deps", + "['//libs:objc_lib']", + "dylibs", + "['//depBinary:low_level_framework']", + "defines", + "['SHOULDNOTBEINPROTOS']", + "copts", + "['-ISHOULDNOTBEINPROTOS']"); + + } else { + assertThat(depBinaryType == BinaryType.LOADABLE_BUNDLE).isTrue(); + scratch.file( + "depBinary/BUILD", + "apple_binary(", + " name = 'apple_low_level_binary',", + " srcs = ['b.m'],", + " deps = ['//libs:objc_lib'],", + " platform_type = 'ios',", + " binary_type = 'executable',", + ")"); + RULE_TYPE.scratchTarget( + scratch, + "binary_type", + "'loadable_bundle'", + "srcs", + "['main.m']", + "deps", + "['//libs:objc_lib']", + "bundle_loader", + "'//depBinary:apple_low_level_binary'"); + } + + // The proto libraries objc_lib depends on should be linked into the low level binary but not x. + Artifact lowLevelBinary = + Iterables.getOnlyElement(linkAction("//depBinary:apple_low_level_binary").getOutputs()); + ImmutableList<Artifact> lowLevelObjectFiles = getAllObjectFilesLinkedInBin(lowLevelBinary); + assertThat(getFirstArtifactEndingWith(lowLevelObjectFiles, "DataA.pbobjc.o")).isNotNull(); + assertThat(getFirstArtifactEndingWith(lowLevelObjectFiles, "DataB.pbobjc.o")).isNotNull(); + assertThat(getFirstArtifactEndingWith(lowLevelObjectFiles, "DataC.pbobjc.o")).isNotNull(); + assertThat(getFirstArtifactEndingWith(lowLevelObjectFiles, "DataD.pbobjc.o")).isNotNull(); + + Artifact bin = Iterables.getOnlyElement(linkAction("//x:x").getOutputs()); + ImmutableList<Artifact> binObjectFiles = getAllObjectFilesLinkedInBin(bin); + assertThat(getFirstArtifactEndingWith(binObjectFiles, "DataA.pbobjc.o")).isNull(); + assertThat(getFirstArtifactEndingWith(binObjectFiles, "DataB.pbobjc.o")).isNull(); + assertThat(getFirstArtifactEndingWith(binObjectFiles, "DataC.pbobjc.o")).isNull(); + assertThat(getFirstArtifactEndingWith(binObjectFiles, "DataD.pbobjc.o")).isNull(); + } + + @Test + public void testProtoDylibDepsPartial() throws Exception { + checkProtoDedupingDepsPartial(AppleBinary.BinaryType.DYLIB); + } + + @Test + public void testProtoBundleLoaderDepsPartial() throws Exception { + checkProtoDedupingDepsPartial(AppleBinary.BinaryType.LOADABLE_BUNDLE); + } + + /** + * Test scenario where proto symbols are mixed between the low and top level binaries. There is + * an implicit dependency hierarchy between the different apple_binary types (executable, + * loadable_bundle, dylib) which looks like this (top level binary types can depend on lower level + * binary types): + * + * loadable_bundle + * / \ + * dylib(s) executable + * | + * dylibs + * + * The mechanism to remove duplicate dependencies between dylibs and executable binaries works the + * same way between executable and loadable_bundle binaries; the only difference is through which + * attribute the dependency is declared (dylibs vs bundle_loader). This test scenario sets up + * dependencies for low level and top level binaries and checks that the correct files are linked + * for each of the binaries. + * + * @param depBinaryType either {@link BinaryType#DYLIB} or {@link BinaryType#LOADABLE_BUNDLE}, as + * this deduping test is applicable for either + */ + private void checkProtoDedupingDepsPartial(BinaryType depBinaryType) throws Exception { + scratch.file( + "protos/BUILD", + "proto_library(", + " name = 'protos_1',", + " srcs = ['data_a.proto', 'data_b.proto'],", + ")", + "proto_library(", + " name = 'protos_2',", + " srcs = ['data_b.proto', 'data_c.proto'],", + ")", + "proto_library(", + " name = 'protos_3',", + " srcs = ['data_a.proto', 'data_c.proto', 'data_d.proto'],", + ")", + "objc_proto_library(", + " name = 'objc_protos_a',", + " portable_proto_filters = ['filter_a.pbascii'],", + " deps = [':protos_1'],", + ")", + "objc_proto_library(", + " name = 'objc_protos_b',", + " portable_proto_filters = ['filter_b.pbascii'],", + " deps = [':protos_2', ':protos_3'],", + ")"); + scratch.file( + "libs/BUILD", + "objc_library(", + " name = 'main_lib',", + " srcs = ['a.m'],", + " deps = ['//protos:objc_protos_a', '//protos:objc_protos_b']", + ")", + "objc_library(", + " name = 'apple_low_level_lib',", + " srcs = ['b.m'],", + " deps = ['//protos:objc_protos_a']", + ")"); + + if (depBinaryType == BinaryType.DYLIB) { + scratchFrameworkSkylarkStub("frameworkstub/framework_stub.bzl"); + scratch.file( + "depBinary/BUILD", + "load('//frameworkstub:framework_stub.bzl', 'framework_stub_rule')", + "apple_binary(", + " name = 'apple_low_level_binary',", + " srcs = ['b.m'],", + " deps = ['//libs:apple_low_level_lib'],", + " platform_type = 'ios',", + " binary_type = 'dylib',", + ")", + "framework_stub_rule(", + " name = 'low_level_framework',", + " deps = [':apple_low_level_binary'],", + " binary = ':apple_low_level_binary')"); + RULE_TYPE.scratchTarget( + scratch, + "binary_type", + "'executable'", + "srcs", + "['main.m']", + "deps", + "['//libs:main_lib']", + "dylibs", + "['//depBinary:low_level_framework']", + "defines", + "['SHOULDNOTBEINPROTOS']", + "copts", + "['-ISHOULDNOTBEINPROTOS']"); + + } else { + assertThat(depBinaryType == BinaryType.LOADABLE_BUNDLE).isTrue(); + scratch.file( + "depBinary/BUILD", + "apple_binary(", + " name = 'apple_low_level_binary',", + " srcs = ['b.m'],", + " deps = ['//libs:apple_low_level_lib'],", + " platform_type = 'ios',", + " binary_type = 'executable',", + ")"); + RULE_TYPE.scratchTarget( + scratch, + "binary_type", + "'loadable_bundle'", + "srcs", + "['main.m']", + "deps", + "['//libs:main_lib']", + "bundle_loader", + "'//depBinary:apple_low_level_binary'", + "defines", + "['SHOULDNOTBEINPROTOS']", + "copts", + "['-ISHOULDNOTBEINPROTOS']"); + } + + // The proto libraries objc_lib depends on should be linked into the low level binary but not x. + Artifact lowLevelBinary = + Iterables.getOnlyElement(linkAction("//depBinary:apple_low_level_binary").getOutputs()); + ImmutableList<Artifact> lowLevelObjectFiles = getAllObjectFilesLinkedInBin(lowLevelBinary); + assertThat(getFirstArtifactEndingWith(lowLevelObjectFiles, "DataA.pbobjc.o")).isNotNull(); + assertThat(getFirstArtifactEndingWith(lowLevelObjectFiles, "DataB.pbobjc.o")).isNotNull(); + assertThat(getFirstArtifactEndingWith(lowLevelObjectFiles, "DataC.pbobjc.o")).isNull(); + assertThat(getFirstArtifactEndingWith(lowLevelObjectFiles, "DataD.pbobjc.o")).isNull(); + + Artifact bin = Iterables.getOnlyElement(linkAction("//x:x").getOutputs()); + ImmutableList<Artifact> binObjectFiles = getAllObjectFilesLinkedInBin(bin); + assertThat(getFirstArtifactEndingWith(binObjectFiles, "DataA.pbobjc.o")).isNull(); + assertThat(getFirstArtifactEndingWith(binObjectFiles, "DataB.pbobjc.o")).isNull(); + assertThat(getFirstArtifactEndingWith(binObjectFiles, "DataC.pbobjc.o")).isNotNull(); + assertThat(getFirstArtifactEndingWith(binObjectFiles, "DataD.pbobjc.o")).isNotNull(); + } + + @Test + public void testProtoDepsViaDylib() throws Exception { + checkProtoDisjointDeps(BinaryType.DYLIB); + } + + @Test + public void testProtoDepsViaBundleLoader() throws Exception { + checkProtoDisjointDeps(BinaryType.LOADABLE_BUNDLE); + } + + /** + * Test scenario where a proto in the top level binary depends on a proto in a low level binary. + * There is an implicit dependency hierarchy between the different apple_binary types (executable, + * loadable_bundle, dylib) which looks like this (top level binary types can depend on lower level + * binary types): + * + * loadable_bundle + * / \ + * dylib(s) executable + * | + * dylibs + * + * The mechanism to remove duplicate dependencies between dylibs and executable binaries works the + * same way between executable and loadable_bundle binaries; the only difference is through which + * attribute the dependency is declared (dylibs vs bundle_loader). This test scenario sets up + * dependencies for low level and top level binaries and checks that the correct files are linked + * for each of the binaries. + * + * @param depBinaryType either {@link BinaryType#DYLIB} or {@link BinaryType#LOADABLE_BUNDLE}, as + * this deduping test is applicable for either + */ + private void checkProtoDisjointDeps(BinaryType depBinaryType) throws Exception { + scratch.file( + "protos/BUILD", + "proto_library(", + " name = 'protos_main',", + " srcs = ['data_a.proto', 'data_b.proto'],", + ")", + "proto_library(", + " name = 'protos_low_level',", + " srcs = ['data_b.proto'],", + ")", + "objc_proto_library(", + " name = 'objc_protos_main',", + " portable_proto_filters = ['filter_a.pbascii'],", + " deps = [':protos_main'],", + ")", + "objc_proto_library(", + " name = 'objc_protos_low_level',", + " portable_proto_filters = ['filter_b.pbascii'],", + " deps = [':protos_low_level'],", + ")"); + scratch.file( + "libs/BUILD", + "objc_library(", + " name = 'main_lib',", + " srcs = ['a.m'],", + " deps = ['//protos:objc_protos_main',]", + ")", + "objc_library(", + " name = 'apple_low_level_lib',", + " srcs = ['a.m'],", + " deps = ['//protos:objc_protos_low_level',]", + ")"); + + if (depBinaryType == BinaryType.DYLIB) { + scratchFrameworkSkylarkStub("frameworkstub/framework_stub.bzl"); + scratch.file( + "depBinary/BUILD", + "load('//frameworkstub:framework_stub.bzl', 'framework_stub_rule')", + "apple_binary(", + " name = 'apple_low_level_binary',", + " srcs = ['b.m'],", + " deps = ['//libs:apple_low_level_lib'],", + " platform_type = 'ios',", + " binary_type = 'dylib',", + ")", + "framework_stub_rule(", + " name = 'low_level_framework',", + " deps = [':apple_low_level_binary'],", + " binary = ':apple_low_level_binary')"); + RULE_TYPE.scratchTarget( + scratch, + "binary_type", + "'executable'", + "srcs", + "['main.m']", + "deps", + "['//libs:main_lib']", + "dylibs", + "['//depBinary:low_level_framework']", + "defines", + "['SHOULDNOTBEINPROTOS']", + "copts", + "['-ISHOULDNOTBEINPROTOS']"); + + } else { + assertThat(depBinaryType == BinaryType.LOADABLE_BUNDLE).isTrue(); + scratch.file( + "depBinary/BUILD", + "apple_binary(", + " name = 'apple_low_level_binary',", + " srcs = ['b.m'],", + " deps = ['//libs:apple_low_level_lib'],", + " platform_type = 'ios',", + " binary_type = 'executable',", + ")"); + RULE_TYPE.scratchTarget( + scratch, + "binary_type", + "'loadable_bundle'", + "srcs", + "['main.m']", + "deps", + "['//libs:main_lib']", + "bundle_loader", + "'//depBinary:apple_low_level_binary'", + "defines", + "['SHOULDNOTBEINPROTOS']", + "copts", + "['-ISHOULDNOTBEINPROTOS']"); + } + + // The proto libraries objc_lib depends on should be linked into apple_dylib but not x. + Artifact lowLevelBinary = + Iterables.getOnlyElement(linkAction("//depBinary:apple_low_level_binary").getOutputs()); + ImmutableList<Artifact> lowLevelObjectFiles = getAllObjectFilesLinkedInBin(lowLevelBinary); + assertThat(getFirstArtifactEndingWith(lowLevelObjectFiles, "DataA.pbobjc.o")).isNull(); + assertThat(getFirstArtifactEndingWith(lowLevelObjectFiles, "DataB.pbobjc.o")).isNotNull(); + + Artifact bin = Iterables.getOnlyElement(linkAction("//x:x").getOutputs()); + ImmutableList<Artifact> binObjectFiles = getAllObjectFilesLinkedInBin(bin); + assertThat(getFirstArtifactEndingWith(binObjectFiles, "DataA.pbobjc.o")).isNotNull(); + assertThat(getFirstArtifactEndingWith(binObjectFiles, "DataB.pbobjc.o")).isNull(); + Action dataAObjectAction = + getGeneratingAction(getFirstArtifactEndingWith(binObjectFiles, "DataA.pbobjc.o")); + assertThat(getFirstArtifactEndingWith(dataAObjectAction.getInputs(), "DataB.pbobjc.h")) + .isNotNull(); + } + + @Test + public void testProtoBundlingWithTargetsWithNoDeps() throws Exception { + checkProtoBundlingWithTargetsWithNoDeps(RULE_TYPE); + } + + @Test + public void testProtoBundlingDoesNotHappen() throws Exception { + useConfiguration("--noenable_apple_binary_native_protos"); + checkProtoBundlingDoesNotHappen(RULE_TYPE); + } + + @Test + public void testAvoidDepsObjectsWithCrosstool() throws Exception { + checkAvoidDepsObjectsWithCrosstool(RULE_TYPE); + } + + @Test + public void testBundleLoaderCantBeSetWithoutBundleBinaryType() throws Exception { + scratch.file("bin/BUILD", + "apple_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " platform_type = 'ios',", + ")"); + checkError( + "bundle", "bundle", AppleBinary.BUNDLE_LOADER_NOT_IN_BUNDLE_ERROR, + "apple_binary(", + " name = 'bundle',", + " bundle_loader = '//bin:bin',", + " platform_type = 'ios',", + ")"); + } + + /** Returns the bcsymbolmap artifact for given architecture. */ + protected Artifact bitcodeSymbol(String arch) throws Exception { + SpawnAction lipoAction = (SpawnAction) lipoBinAction("//examples/apple_skylark:bin"); + + String bin = + configurationBin(arch, ConfigurationDistinguisher.APPLEBIN_IOS) + + "examples/apple_skylark/bin_bin"; + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), bin); + CommandAction linkAction = (CommandAction) getGeneratingAction(binArtifact); + return getFirstArtifactEndingWith(linkAction.getOutputs(), "bcsymbolmap"); + } + + /** Returns the path to the dSYM binary artifact for given architecture. */ + protected String dsymBinaryPath(String arch) throws Exception { + return configurationBin(arch, ConfigurationDistinguisher.APPLEBIN_IOS) + + "examples/apple_skylark/bin.app.dSYM/Contents/Resources/DWARF/bin_bin"; + } + + /** Returns the path to the linkmap artifact for a given architecture. */ + protected String linkmapPath(String arch) throws Exception { + return configurationBin(arch, ConfigurationDistinguisher.APPLEBIN_IOS) + + "examples/apple_skylark/bin.linkmap"; + } + + @Test + @SuppressWarnings("unchecked") + public void testProvider_dylib() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _test_rule_impl(ctx):", + " dep = ctx.attr.deps[0]", + " provider = dep[apple_common.AppleDylibBinary]", + " return struct(", + " binary = provider.binary,", + " objc = provider.objc,", + " dep_dir = dir(dep),", + " )", + "test_rule = rule(implementation = _test_rule_impl,", + " attrs = {", + " 'deps': attr.label_list(allow_files = False, mandatory = False,)", + "})"); + + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'test_rule')", + "apple_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " binary_type = '" + BinaryType.DYLIB + "',", + " platform_type = 'ios',", + ")", + "test_rule(", + " name = 'my_target',", + " deps = [':bin'],", + ")"); + + useConfiguration("--ios_multi_cpus=armv7,arm64"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + assertThat(skylarkTarget.get("binary")).isInstanceOf(Artifact.class); + assertThat(skylarkTarget.get("objc")).isInstanceOf(ObjcProvider.class); + + List<String> depProviders = (List<String>) skylarkTarget.getValue("dep_dir"); + assertThat(depProviders).doesNotContain("AppleExecutableBinary"); + assertThat(depProviders).doesNotContain("AppleLoadableBundleBinary"); + } + + @Test + @SuppressWarnings("unchecked") + public void testProvider_executable() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _test_rule_impl(ctx):", + " dep = ctx.attr.deps[0]", + " provider = dep[apple_common.AppleExecutableBinary]", + " return struct(", + " binary = provider.binary,", + " objc = provider.objc,", + " dep_dir = dir(dep),", + " )", + "test_rule = rule(implementation = _test_rule_impl,", + " attrs = {", + " 'deps': attr.label_list(allow_files = False, mandatory = False,)", + "})"); + + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'test_rule')", + "apple_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " binary_type = '" + BinaryType.EXECUTABLE + "',", + " platform_type = 'ios',", + ")", + "test_rule(", + " name = 'my_target',", + " deps = [':bin'],", + ")"); + + useConfiguration("--ios_multi_cpus=armv7,arm64"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + assertThat(skylarkTarget.get("binary")).isInstanceOf(Artifact.class); + assertThat(skylarkTarget.get("objc")).isInstanceOf(ObjcProvider.class); + + List<String> depProviders = (List<String>) skylarkTarget.get("dep_dir"); + assertThat(depProviders).doesNotContain("AppleDylibBinary"); + assertThat(depProviders).doesNotContain("AppleLoadableBundleBinary"); + } + + @Test + @SuppressWarnings("unchecked") + public void testProvider_loadableBundle() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _test_rule_impl(ctx):", + " dep = ctx.attr.deps[0]", + " provider = dep[apple_common.AppleLoadableBundleBinary]", + " return struct(", + " binary = provider.binary,", + " dep_dir = dir(dep),", + " )", + "test_rule = rule(implementation = _test_rule_impl,", + " attrs = {", + " 'deps': attr.label_list(allow_files = False, mandatory = False,)", + "})"); + + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'test_rule')", + "apple_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " binary_type = '" + BinaryType.LOADABLE_BUNDLE + "',", + " platform_type = 'ios',", + ")", + "test_rule(", + " name = 'my_target',", + " deps = [':bin'],", + ")"); + + useConfiguration("--ios_multi_cpus=armv7,arm64"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + assertThat((Artifact) skylarkTarget.get("binary")).isNotNull(); + + List<String> depProviders = (List<String>) skylarkTarget.get("dep_dir"); + assertThat(depProviders).doesNotContain("AppleExecutableBinary"); + assertThat(depProviders).doesNotContain("AppleDylibBinary"); + } + + @Test + public void testDuplicateLinkopts() throws Exception { + RULE_TYPE.scratchTarget(scratch, "linkopts", "['-foo', 'bar', '-foo', 'baz']"); + + CommandAction linkAction = linkAction("//x:x"); + String linkArgs = Joiner.on(" ").join(linkAction.getArguments()); + assertThat(linkArgs).contains("-Wl,-foo -Wl,bar"); + assertThat(linkArgs).contains("-Wl,-foo -Wl,baz"); + } + + @Test + public void testCanUseCrosstool_singleArch() throws Exception { + checkLinkingRuleCanUseCrosstool_singleArch(RULE_TYPE); + } + + @Test + public void testCanUseCrosstool_multiArch() throws Exception { + checkLinkingRuleCanUseCrosstool_multiArch(RULE_TYPE); + } + + @Test + public void testAppleSdkIphoneosPlatformEnv() throws Exception { + checkAppleSdkIphoneosPlatformEnv(RULE_TYPE); + } + + @Test + public void testXcodeVersionEnv() throws Exception { + checkXcodeVersionEnv(RULE_TYPE); + } + + @Test + public void testLinksImplicitFrameworksWithCrosstoolIos() throws Exception { + useConfiguration( + ObjcCrosstoolMode.ALL, + "--ios_multi_cpus=x86_64", + "--ios_sdk_version=10.0", + "--ios_minimum_os=8.0"); + RULE_TYPE.scratchTarget(scratch, "srcs", "['a.m']", "platform_type", "'ios'"); + + Action lipoAction = actionProducingArtifact("//x:x", "_lipobin"); + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), "x/x_bin"); + CommandAction linkAction = (CommandAction) getGeneratingAction(binArtifact); + + assertThat(linkAction.getArguments()).containsAllIn(IMPLICIT_NON_MAC_FRAMEWORK_FLAGS); + } + + @Test + public void testLinksImplicitFrameworksWithCrosstoolWatchos() throws Exception { + useConfiguration( + ObjcCrosstoolMode.ALL, + "--watchos_cpus=i386", + "--watchos_sdk_version=3.0", + "--watchos_minimum_os=2.0"); + RULE_TYPE.scratchTarget(scratch, "srcs", "['a.m']", "platform_type", "'watchos'"); + + Action lipoAction = actionProducingArtifact("//x:x", "_lipobin"); + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), "x/x_bin"); + CommandAction linkAction = (CommandAction) getGeneratingAction(binArtifact); + + assertThat(linkAction.getArguments()).containsAllIn(IMPLICIT_NON_MAC_FRAMEWORK_FLAGS); + } + + @Test + public void testLinksImplicitFrameworksWithCrosstoolTvos() throws Exception { + useConfiguration( + ObjcCrosstoolMode.ALL, + "--tvos_cpus=x86_64", + "--tvos_sdk_version=10.1", + "--tvos_minimum_os=10.0"); + RULE_TYPE.scratchTarget(scratch, "srcs", "['a.m']", "platform_type", "'tvos'"); + + Action lipoAction = actionProducingArtifact("//x:x", "_lipobin"); + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), "x/x_bin"); + CommandAction linkAction = (CommandAction) getGeneratingAction(binArtifact); + + assertThat(linkAction.getArguments()).containsAllIn(IMPLICIT_NON_MAC_FRAMEWORK_FLAGS); + } + + @Test + public void testLinksImplicitFrameworksWithCrosstoolMacos() throws Exception { + useConfiguration( + ObjcCrosstoolMode.ALL, + "--macos_cpus=x86_64", + "--macos_sdk_version=10.11", + "--macos_minimum_os=10.11"); + RULE_TYPE.scratchTarget(scratch, "srcs", "['a.m']", "platform_type", "'macos'"); + + Action lipoAction = actionProducingArtifact("//x:x", "_lipobin"); + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), "x/x_bin"); + CommandAction linkAction = (CommandAction) getGeneratingAction(binArtifact); + + assertThat(linkAction.getArguments()).containsAllIn(IMPLICIT_MAC_FRAMEWORK_FLAGS); + assertThat(linkAction.getArguments()) + .containsNoneOf(COCOA_FRAMEWORK_FLAG, UIKIT_FRAMEWORK_FLAG); + } + + @Test + public void testLinkCocoaFeatureWithCrosstoolMacos() throws Exception { + useConfiguration( + ObjcCrosstoolMode.ALL, + "--macos_cpus=x86_64", + "--macos_sdk_version=10.11", + "--macos_minimum_os=10.11"); + RULE_TYPE.scratchTarget( + scratch, "srcs", "['a.m']", "platform_type", "'macos'", "features", "['link_cocoa']"); + + Action lipoAction = actionProducingArtifact("//x:x", "_lipobin"); + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), "x/x_bin"); + CommandAction linkAction = (CommandAction) getGeneratingAction(binArtifact); + + assertThat(linkAction.getArguments()).containsAllIn(IMPLICIT_MAC_FRAMEWORK_FLAGS); + assertThat(linkAction.getArguments()).containsAllIn(COCOA_FEATURE_FLAGS); + assertThat(linkAction.getArguments()).doesNotContain(UIKIT_FRAMEWORK_FLAG); + } + + @Test + public void testAliasedLinkoptsThroughObjcLibrary() throws Exception { + checkAliasedLinkoptsThroughObjcLibrary(RULE_TYPE); + } + + @Test + public void testAppleSdkVersionEnv() throws Exception { + checkAppleSdkVersionEnv(RULE_TYPE); + } + + @Test + public void testNonDefaultAppleSdkVersionEnv() throws Exception { + checkNonDefaultAppleSdkVersionEnv(RULE_TYPE); + } + + @Test + public void testAppleSdkDefaultPlatformEnv() throws Exception { + checkAppleSdkDefaultPlatformEnv(RULE_TYPE); + } + + @Test + public void testAvoidDepsThroughDylib() throws Exception { + checkAvoidDepsThroughDylib(RULE_TYPE); + } + + @Test + public void testAvoidDepsObjects_avoidViaCcLibrary() throws Exception { + checkAvoidDepsObjects_avoidViaCcLibrary(RULE_TYPE); + } + + @Test + public void testBundleLoaderIsCorrectlyPassedToTheLinker() throws Exception { + checkBundleLoaderIsCorrectlyPassedToTheLinker(RULE_TYPE); + } + + @Test + public void testNoSrcs() throws Exception { + checkNoSrcs(RULE_TYPE); + } + + @Test + public void testLipoBinaryAction() throws Exception { + checkLipoBinaryAction(RULE_TYPE); + } + + @Test + public void testLinkActionHasCorrectIosSimulatorMinVersion() throws Exception { + RULE_TYPE.scratchTarget(scratch, "srcs", "['a.m']", "platform_type", "'ios'"); + useConfiguration("--ios_multi_cpus=x86_64", "--ios_sdk_version=10.0", "--ios_minimum_os=8.0"); + checkLinkMinimumOSVersion( + ConfigurationDistinguisher.APPLEBIN_IOS, "x86_64", "-mios-simulator-version-min=8.0"); + } + + @Test + public void testLinkActionHasCorrectIosMinVersion() throws Exception { + RULE_TYPE.scratchTarget(scratch, "srcs", "['a.m']", "platform_type", "'ios'"); + useConfiguration("--ios_multi_cpus=arm64", "--ios_sdk_version=10.0", "--ios_minimum_os=8.0"); + checkLinkMinimumOSVersion( + ConfigurationDistinguisher.APPLEBIN_IOS, "arm64", "-miphoneos-version-min=8.0"); + } + + @Test + public void testWatchSimulatorDepCompile() throws Exception { + checkWatchSimulatorDepCompile(RULE_TYPE); + } + + @Test + public void testDylibBinaryType() throws Exception { + RULE_TYPE.scratchTarget(scratch, "binary_type", "'dylib'"); + + CommandAction linkAction = linkAction("//x:x"); + assertThat(Joiner.on(" ").join(linkAction.getArguments())).contains("-dynamiclib"); + } + + @Test + public void testBinaryTypeIsCorrectlySetToBundle() throws Exception { + RULE_TYPE.scratchTarget(scratch, "binary_type", "'loadable_bundle'"); + + CommandAction linkAction = linkAction("//x:x"); + assertThat(Joiner.on(" ").join(linkAction.getArguments())).contains("-bundle"); + } + + @Test + public void testMultiarchCcDep() throws Exception { + checkMultiarchCcDep(RULE_TYPE); + } + + @Test + public void testWatchSimulatorLipoAction() throws Exception { + checkWatchSimulatorLipoAction(RULE_TYPE); + } + + @Test + public void testLinkActionsWithSrcs() throws Exception { + checkLinkActionsWithSrcs(RULE_TYPE, new ExtraLinkArgs()); + } + + @Test + public void testFrameworkDepLinkFlags() throws Exception { + checkFrameworkDepLinkFlags(RULE_TYPE, new ExtraLinkArgs()); + } + + @Test + public void testDylibDependencies() throws Exception { + checkDylibDependencies(RULE_TYPE, new ExtraLinkArgs()); + } + + @Test + public void testMinimumOs() throws Exception { + checkMinimumOsLinkAndCompileArg(RULE_TYPE); + } + + @Test + public void testMinimumOs_watchos() throws Exception { + checkMinimumOsLinkAndCompileArg_watchos(RULE_TYPE); + } + + @Test + public void testMinimumOs_invalid_nonVersion() throws Exception { + checkMinimumOs_invalid_nonVersion(RULE_TYPE); + } + + @Test + public void testMinimumOs_invalid_containsAlphabetic() throws Exception { + checkMinimumOs_invalid_containsAlphabetic(RULE_TYPE); + } + + @Test + public void testMinimumOs_invalid_tooManyComponents() throws Exception { + checkMinimumOs_invalid_tooManyComponents(RULE_TYPE); + } + + @Test + public void testGenfilesProtoGetsCorrectPath() throws Exception { + scratch.file( + "examples/BUILD", + "package(default_visibility = ['//visibility:public'])", + "apple_binary(", + " name = 'bin',", + " deps = [':objc_protos'],", + " platform_type = 'ios',", + ")", + "objc_proto_library(", + " name = 'objc_protos',", + " portable_proto_filters = ['filter.pbascii'],", + " deps = [':protos'],", + ")", + "proto_library(", + " name = 'protos',", + " srcs = ['genfile.proto'],", + ")", + "genrule(", + " name = 'copy_proto',", + " srcs = ['original.proto'],", + " outs = ['genfile.proto'],", + " cmd = '/bin/cp $< $@',", + ")"); + + useConfiguration("--ios_multi_cpus=armv7,arm64"); + + Action lipoAction = actionProducingArtifact("//examples:bin", "_lipobin"); + ArrayList<String> genfileRoots = new ArrayList<>(); + + for (Artifact archBinary : lipoAction.getInputs()) { + if (archBinary.getExecPathString().endsWith("bin_bin")) { + Artifact protoLib = + getFirstArtifactEndingWith( + getGeneratingAction(archBinary).getInputs(), "BundledProtos_0.a"); + Artifact protoObject = + getFirstArtifactEndingWith( + getGeneratingAction(protoLib).getInputs(), "Genfile.pbobjc.o"); + Artifact protoObjcSource = + getFirstArtifactEndingWith( + getGeneratingAction(protoObject).getInputs(), "Genfile.pbobjc.m"); + Artifact protoSource = + getFirstArtifactEndingWith( + getGeneratingAction(protoObjcSource).getInputs(), "genfile.proto"); + genfileRoots.add(protoSource.getRoot().getExecPathString()); + } + } + + // Make sure there are genrules for both arm64 and armv7 configurations. + Collections.sort(genfileRoots); + assertThat(genfileRoots).hasSize(2); + assertThat(genfileRoots.get(0)).contains("arm64"); + assertThat(genfileRoots.get(1)).contains("armv7"); + } + + @Test + public void testDifferingProtoDepsPerArchitecture() throws Exception { + scratch.file( + "examples/BUILD", + "package(default_visibility = ['//visibility:public'])", + "apple_binary(", + " name = 'bin',", + " deps = [':objc_protos'],", + " platform_type = 'ios',", + ")", + "objc_proto_library(", + " name = 'objc_protos',", + " portable_proto_filters = ['filter.pbascii'],", + " deps = [':protos'],", + ")", + "proto_library(", + " name = 'protos',", + " srcs = select({", + " ':armv7': [ 'one.proto', ],", + " '//conditions:default': [ 'two.proto', ],", + " }),", + ")", + "config_setting(", + " name = 'armv7',", + " values = {'apple_split_cpu': 'armv7'},", + ")"); + + useConfiguration("--ios_multi_cpus=armv7,arm64"); + + Action lipoAction = actionProducingArtifact("//examples:bin", "_lipobin"); + + Artifact armv7Binary = getSingleArchBinary(lipoAction, "armv7"); + Artifact arm64Binary = getSingleArchBinary(lipoAction, "arm64");; + + Artifact armv7ProtoLib = + getFirstArtifactEndingWith( + getGeneratingAction(armv7Binary).getInputs(), "BundledProtos_0.a"); + Artifact armv7ProtoObject = + getFirstArtifactEndingWith( + getGeneratingAction(armv7ProtoLib).getInputs(), "One.pbobjc.o"); + Artifact armv7ProtoObjcSource = + getFirstArtifactEndingWith( + getGeneratingAction(armv7ProtoObject).getInputs(), "One.pbobjc.m"); + assertThat(getFirstArtifactEndingWith( + getGeneratingAction(armv7ProtoObjcSource).getInputs(), "one.proto")).isNotNull(); + + Artifact arm64ProtoLib = + getFirstArtifactEndingWith( + getGeneratingAction(arm64Binary).getInputs(), "BundledProtos_0.a"); + Artifact arm64ProtoObject = + getFirstArtifactEndingWith( + getGeneratingAction(arm64ProtoLib).getInputs(), "Two.pbobjc.o"); + Artifact arm64ProtoObjcSource = + getFirstArtifactEndingWith( + getGeneratingAction(arm64ProtoObject).getInputs(), "Two.pbobjc.m"); + assertThat(getFirstArtifactEndingWith( + getGeneratingAction(arm64ProtoObjcSource).getInputs(), "two.proto")).isNotNull(); + } + + private Artifact getSingleArchBinary(Action lipoAction, String arch) throws Exception { + for (Artifact archBinary : lipoAction.getInputs()) { + String execPath = archBinary.getExecPathString(); + if (execPath.endsWith("bin_bin") && execPath.contains(arch)) { + return archBinary; + } + } + throw new AssertionError("Lipo action does not contain an input binary from arch " + arch); + } + + private SkylarkDict<String, SkylarkDict<String, Artifact>> + generateAppleDebugOutputsSkylarkProviderMap() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _test_rule_impl(ctx):", + " dep = ctx.attr.deps[0]", + " provider = dep[apple_common.AppleDebugOutputs]", + " return struct(", + " outputs_map=provider.outputs_map,", + " )", + "test_rule = rule(implementation = _test_rule_impl,", + " attrs = {", + " 'deps': attr.label_list(", + " allow_files = False,", + " mandatory = False,", + " providers = [apple_common.AppleDebugOutputs],", + " )", + "})"); + + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'test_rule')", + "apple_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " platform_type = 'ios',", + ")", + "test_rule(", + " name = 'my_target',", + " deps = [':bin'],", + ")"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + // This cast is safe: struct providers are represented as SkylarkDict. + @SuppressWarnings("unchecked") + SkylarkDict<String, SkylarkDict<String, Artifact>> outputMap = + (SkylarkDict<String, SkylarkDict<String, Artifact>>) + skylarkTarget.get("outputs_map"); + return outputMap; + } + + private void checkAppleDebugSymbolProvider_DsymEntries( + SkylarkDict<String, SkylarkDict<String, Artifact>> outputMap) throws Exception { + assertThat(outputMap).containsKey("arm64"); + assertThat(outputMap).containsKey("armv7"); + + Map<String, Artifact> arm64 = outputMap.get("arm64"); + assertThat(arm64).containsEntry("bitcode_symbols", bitcodeSymbol("arm64")); + assertThat(arm64.get("dsym_binary").getExecPathString()).isEqualTo(dsymBinaryPath("arm64")); + + Map<String, Artifact> armv7 = outputMap.get("armv7"); + assertThat(armv7).containsEntry("bitcode_symbols", bitcodeSymbol("armv7")); + assertThat(armv7.get("dsym_binary").getExecPathString()).isEqualTo(dsymBinaryPath("armv7")); + + Map<String, Artifact> x8664 = outputMap.get("x86_64"); + // Simulator build has bitcode disabled. + assertThat(x8664).doesNotContainKey("bitcode_symbols"); + assertThat(x8664.get("dsym_binary").getExecPathString()).isEqualTo(dsymBinaryPath("x86_64")); + } + + private void checkAppleDebugSymbolProvider_LinkMapEntries( + SkylarkDict<String, SkylarkDict<String, Artifact>> outputMap) throws Exception { + assertThat(outputMap).containsKey("arm64"); + assertThat(outputMap).containsKey("armv7"); + + Map<String, Artifact> arm64 = outputMap.get("arm64"); + assertThat(arm64.get("linkmap").getExecPathString()).isEqualTo(linkmapPath("arm64")); + + Map<String, Artifact> armv7 = outputMap.get("armv7"); + assertThat(armv7.get("linkmap").getExecPathString()).isEqualTo(linkmapPath("armv7")); + + Map<String, Artifact> x8664 = outputMap.get("x86_64"); + assertThat(x8664.get("linkmap").getExecPathString()).isEqualTo(linkmapPath("x86_64")); + } + + @Test + public void testAppleDebugSymbolProviderWithDsymsExposedToSkylark() throws Exception { + useConfiguration( + "--apple_bitcode=embedded", "--apple_generate_dsym", "--ios_multi_cpus=armv7,arm64,x86_64"); + checkAppleDebugSymbolProvider_DsymEntries(generateAppleDebugOutputsSkylarkProviderMap()); + } + + @Test + public void testAppleDebugSymbolProviderWithLinkMapsExposedToSkylark() throws Exception { + useConfiguration( + "--apple_bitcode=embedded", + "--objc_generate_linkmap", + "--ios_multi_cpus=armv7,arm64,x86_64"); + checkAppleDebugSymbolProvider_LinkMapEntries(generateAppleDebugOutputsSkylarkProviderMap()); + } + + @Test + public void testAppleDebugSymbolProviderWithDsymsAndLinkMapsExposedToSkylark() throws Exception { + useConfiguration( + "--apple_bitcode=embedded", + "--objc_generate_linkmap", + "--apple_generate_dsym", + "--ios_multi_cpus=armv7,arm64,x86_64"); + + SkylarkDict<String, SkylarkDict<String, Artifact>> outputMap = + generateAppleDebugOutputsSkylarkProviderMap(); + checkAppleDebugSymbolProvider_DsymEntries(outputMap); + checkAppleDebugSymbolProvider_LinkMapEntries(outputMap); + } + + @Test + public void testFilesToCompileOutputGroup() throws Exception { + checkFilesToCompileOutputGroup(RULE_TYPE); + } + + @Test + public void testInstrumentedFilesProviderContainsDepsAndBundleLoaderFiles() throws Exception { + useConfiguration("--collect_code_coverage"); + scratch.file( + "examples/BUILD", + "package(default_visibility = ['//visibility:public'])", + "apple_binary(", + " name = 'bin',", + " deps = [':lib'],", + " platform_type = 'ios',", + ")", + "apple_binary(", + " name = 'bundle',", + " deps = [':bundle_lib'],", + " binary_type = '" + BinaryType.LOADABLE_BUNDLE + "',", + " bundle_loader = ':bin',", + " platform_type = 'ios',", + ")", + "objc_library(", + " name = 'lib',", + " srcs = ['lib.m'],", + ")", + "objc_library(", + " name = 'bundle_lib',", + " srcs = ['bundle_lib.m'],", + ")"); + + ConfiguredTarget bundleTarget = getConfiguredTarget("//examples:bundle"); + InstrumentedFilesProvider instrumentedFilesProvider = + bundleTarget.getProvider(InstrumentedFilesProvider.class); + assertThat(instrumentedFilesProvider).isNotNull(); + + assertThat(Artifact.toRootRelativePaths(instrumentedFilesProvider.getInstrumentedFiles())) + .containsAllOf("examples/lib.m", "examples/bundle_lib.m"); + } + + @Test + public void testAppleSdkWatchsimulatorPlatformEnv() throws Exception { + checkAppleSdkWatchsimulatorPlatformEnv(RULE_TYPE); + } + + @Test + public void testAppleSdkWatchosPlatformEnv() throws Exception { + checkAppleSdkWatchosPlatformEnv(RULE_TYPE); + } + + @Test + public void testAppleSdkTvsimulatorPlatformEnv() throws Exception { + checkAppleSdkTvsimulatorPlatformEnv(RULE_TYPE); + } + + @Test + public void testAppleSdkTvosPlatformEnv() throws Exception { + checkAppleSdkTvosPlatformEnv(RULE_TYPE); + } + + @Test + public void testLinkActionHasCorrectWatchosSimulatorMinVersion() throws Exception { + RULE_TYPE.scratchTarget(scratch, + "srcs", "['a.m']", + "platform_type", "'watchos'"); + useConfiguration( + "--watchos_cpus=i386", "--watchos_sdk_version=3.0", "--watchos_minimum_os=2.0"); + checkLinkMinimumOSVersion(ConfigurationDistinguisher.APPLEBIN_WATCHOS, "i386", + "-mwatchos-simulator-version-min=2.0"); + } + + @Test + public void testLinkActionHasCorrectWatchosMinVersion() throws Exception { + RULE_TYPE.scratchTarget(scratch, + "srcs", "['a.m']", + "platform_type", "'watchos'"); + useConfiguration( + "--watchos_cpus=armv7k", "--watchos_sdk_version=3.0", "--watchos_minimum_os=2.0"); + checkLinkMinimumOSVersion(ConfigurationDistinguisher.APPLEBIN_WATCHOS, "armv7k", + "-mwatchos-version-min=2.0"); + } + + @Test + public void testLinkActionHasCorrectTvosSimulatorMinVersion() throws Exception { + RULE_TYPE.scratchTarget(scratch, + "srcs", "['a.m']", + "platform_type", "'tvos'"); + useConfiguration( + "--tvos_cpus=x86_64", "--tvos_sdk_version=10.1", "--tvos_minimum_os=10.0"); + checkLinkMinimumOSVersion(ConfigurationDistinguisher.APPLEBIN_TVOS, "x86_64", + "-mtvos-simulator-version-min=10.0"); + } + + @Test + public void testLinkActionHasCorrectTvosMinVersion() throws Exception { + RULE_TYPE.scratchTarget(scratch, + "srcs", "['a.m']", + "platform_type", "'tvos'"); + useConfiguration( + "--tvos_cpus=arm64", "--tvos_sdk_version=10.1", "--tvos_minimum_os=10.0"); + checkLinkMinimumOSVersion(ConfigurationDistinguisher.APPLEBIN_TVOS, "arm64", + "-mtvos-version-min=10.0"); + } + + @Test + public void testWatchSimulatorLinkAction() throws Exception { + checkWatchSimulatorLinkAction(RULE_TYPE); + } + + @Test + public void testProtoBundlingAndLinking() throws Exception { + checkProtoBundlingAndLinking(RULE_TYPE); + } + + @Test + public void testAvoidDepsObjects() throws Exception { + checkAvoidDepsObjects(RULE_TYPE); + } + + @Test + public void testBundleLoaderPropagatesAppleExecutableBinaryProvider() throws Exception { + scratch.file( + "bin/BUILD", + "apple_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " hdrs = ['a.h'],", + " platform_type = 'ios',", + ")"); + scratch.file( + "test/BUILD", + "apple_binary(", + " name = 'test',", + " srcs = ['test.m'],", + " binary_type = 'loadable_bundle',", + " bundle_loader = '//bin:bin',", + " platform_type = 'ios',", + ")"); + ConfiguredTarget binTarget = getConfiguredTarget("//bin:bin"); + AppleExecutableBinaryProvider executableBinaryProvider = + (AppleExecutableBinaryProvider) binTarget.get( + AppleExecutableBinaryProvider.SKYLARK_CONSTRUCTOR.getKey()); + assertThat(executableBinaryProvider).isNotNull(); + + CommandAction testLinkAction = linkAction("//test:test"); + assertThat(testLinkAction.getInputs()) + .contains(executableBinaryProvider.getAppleExecutableBinary()); + } + + @Test + public void testLoadableBundleBinaryAddsRpathLinkOptWithNoBundleLoader() throws Exception { + scratch.file( + "test/BUILD", + "apple_binary(", + " name = 'test',", + " srcs = ['test.m'],", + " binary_type = 'loadable_bundle',", + " platform_type = 'ios',", + ")"); + + CommandAction testLinkAction = linkAction("//test:test"); + assertThat(Joiner.on(" ").join(testLinkAction.getArguments())) + .contains("@loader_path/Frameworks"); + } + + @Test + public void testLoadableBundleBinaryAddsRpathLinkOptWithBundleLoader() throws Exception { + scratch.file( + "bin/BUILD", + "apple_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " hdrs = ['a.h'],", + " platform_type = 'ios',", + ")"); + scratch.file( + "test/BUILD", + "apple_binary(", + " name = 'test',", + " srcs = ['test.m'],", + " binary_type = 'loadable_bundle',", + " bundle_loader = '//bin:bin',", + " platform_type = 'ios',", + ")"); + + CommandAction testLinkAction = linkAction("//test:test"); + assertThat(Joiner.on(" ").join(testLinkAction.getArguments())) + .contains("@loader_path/Frameworks"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/AppleDynamicLibraryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleDynamicLibraryTest.java new file mode 100644 index 0000000000..3feec504a3 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleDynamicLibraryTest.java @@ -0,0 +1,195 @@ +// 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.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.rules.objc.CompilationSupport.ExtraLinkArgs; +import com.google.devtools.build.lib.testutil.Scratch; +import java.io.IOException; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for apple_dyamic_library. */ +@RunWith(JUnit4.class) +public class AppleDynamicLibraryTest extends ObjcRuleTestCase { + static final RuleType RULE_TYPE = new RuleType("apple_binary") { + @Override + Iterable<String> requiredAttributes(Scratch scratch, String packageDir, + Set<String> alreadyAdded) throws IOException { + return Iterables.concat(ImmutableList.of("binary_type = 'dylib'"), + AppleBinaryTest.RULE_TYPE.requiredAttributes(scratch, packageDir, alreadyAdded)); + } + }; + + @Test + public void testCcDependencyLinkoptsArePropagatedToLinkAction() throws Exception { + checkCcDependencyLinkoptsArePropagatedToLinkAction(RULE_TYPE); + } + + @Test + public void testUnknownPlatformType() throws Exception { + checkError( + "package", + "test", + String.format(MultiArchSplitTransitionProvider.UNSUPPORTED_PLATFORM_TYPE_ERROR_FORMAT, + "meow_meow_os"), + "apple_binary(name = 'test', binary_type = 'dylib', srcs = [ 'a.m' ], " + + "platform_type = 'meow_meow_os')"); + } + + @Test + public void testProtoBundlingAndLinking() throws Exception { + checkProtoBundlingAndLinking(RULE_TYPE); + } + + @Test + public void testProtoBundlingWithTargetsWithNoDeps() throws Exception { + checkProtoBundlingWithTargetsWithNoDeps(RULE_TYPE); + } + + @Test + public void testCanUseCrosstool_singleArch() throws Exception { + checkLinkingRuleCanUseCrosstool_singleArch(RULE_TYPE); + } + + @Test + public void testCanUseCrosstool_multiArch() throws Exception { + checkLinkingRuleCanUseCrosstool_multiArch(RULE_TYPE); + } + + @Test + public void testAppleSdkIphoneosPlatformEnv() throws Exception { + checkAppleSdkIphoneosPlatformEnv(RULE_TYPE); + } + + @Test + public void testXcodeVersionEnv() throws Exception { + checkXcodeVersionEnv(RULE_TYPE); + } + + @Test + public void testAliasedLinkoptsThroughObjcLibrary() throws Exception { + checkAliasedLinkoptsThroughObjcLibrary(RULE_TYPE); + } + + @Test + public void testAppleSdkVersionEnv() throws Exception { + checkAppleSdkVersionEnv(RULE_TYPE); + } + + @Test + public void testNonDefaultAppleSdkVersionEnv() throws Exception { + checkNonDefaultAppleSdkVersionEnv(RULE_TYPE); + } + + @Test + public void testAppleSdkDefaultPlatformEnv() throws Exception { + checkAppleSdkDefaultPlatformEnv(RULE_TYPE); + } + + @Test + public void testAvoidDepsObjects_avoidViaCcLibrary() throws Exception { + checkAvoidDepsObjects_avoidViaCcLibrary(RULE_TYPE); + } + + @Test + public void testNoSrcs() throws Exception { + checkNoSrcs(RULE_TYPE); + } + + @Test + public void testLipoBinaryAction() throws Exception { + checkLipoBinaryAction(RULE_TYPE); + } + + @Test + public void testWatchSimulatorDepCompile() throws Exception { + checkWatchSimulatorDepCompile(RULE_TYPE); + } + + @Test + public void testMultiarchCcDep() throws Exception { + checkMultiarchCcDep(RULE_TYPE); + } + + @Test + public void testWatchSimulatorLipoAction() throws Exception { + checkWatchSimulatorLipoAction(RULE_TYPE); + } + + @Test + public void testLinkActionsWithSrcs() throws Exception { + checkLinkActionsWithSrcs(RULE_TYPE, + new ExtraLinkArgs("-dynamiclib")); + } + + @Test + public void testFrameworkDepLinkFlags() throws Exception { + checkFrameworkDepLinkFlags(RULE_TYPE, new ExtraLinkArgs("-dynamiclib")); + } + + @Test + public void testDylibDependencies() throws Exception { + checkDylibDependencies(RULE_TYPE, new ExtraLinkArgs("-dynamiclib")); + } + + @Test + public void testMinimumOs() throws Exception { + checkMinimumOsLinkAndCompileArg(RULE_TYPE); + } + + @Test + public void testMinimumOs_watchos() throws Exception { + checkMinimumOsLinkAndCompileArg_watchos(RULE_TYPE); + } + + @Test + public void testMinimumOs_invalid() throws Exception { + checkMinimumOs_invalid_nonVersion(RULE_TYPE); + } + + @Test + public void testAppleSdkWatchsimulatorPlatformEnv() throws Exception { + checkAppleSdkWatchsimulatorPlatformEnv(RULE_TYPE); + } + + @Test + public void testAppleSdkWatchosPlatformEnv() throws Exception { + checkAppleSdkWatchosPlatformEnv(RULE_TYPE); + } + + @Test + public void testAppleSdkTvsimulatorPlatformEnv() throws Exception { + checkAppleSdkTvsimulatorPlatformEnv(RULE_TYPE); + } + + @Test + public void testAppleSdkTvosPlatformEnv() throws Exception { + checkAppleSdkTvosPlatformEnv(RULE_TYPE); + } + + @Test + public void testWatchSimulatorLinkAction() throws Exception { + checkWatchSimulatorLinkAction(RULE_TYPE); + } + + @Test + public void testAvoidDepsObjects() throws Exception { + checkAvoidDepsObjects(RULE_TYPE); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/AppleStaticLibraryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleStaticLibraryTest.java new file mode 100644 index 0000000000..c410ce51cd --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleStaticLibraryTest.java @@ -0,0 +1,544 @@ +// 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 static com.google.devtools.build.lib.actions.util.ActionsTestUtil.getFirstArtifactEndingWith; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.LIPO; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.actions.SymlinkAction; +import com.google.devtools.build.lib.packages.util.MockObjcSupport; +import com.google.devtools.build.lib.rules.apple.AppleConfiguration.ConfigurationDistinguisher; +import com.google.devtools.build.lib.rules.cpp.CppLinkAction; +import com.google.devtools.build.lib.testutil.Scratch; +import java.io.IOException; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for apple_static_library. */ +@RunWith(JUnit4.class) +public class AppleStaticLibraryTest extends ObjcRuleTestCase { + static final RuleType RULE_TYPE = new RuleType("apple_static_library") { + @Override + Iterable<String> requiredAttributes(Scratch scratch, String packageDir, + Set<String> alreadyAdded) throws IOException { + ImmutableList.Builder<String> attributes = new ImmutableList.Builder<>(); + if (!alreadyAdded.contains("srcs") && !alreadyAdded.contains("non_arc_srcs")) { + scratch.file(packageDir + "/a.m"); + scratch.file(packageDir + "/private.h"); + attributes.add("srcs = ['a.m', 'private.h']"); + } + if (!alreadyAdded.contains("platform_type")) { + attributes.add("platform_type = 'ios'"); + } + return attributes.build(); + } + }; + + + @Test + public void testUnknownPlatformType() throws Exception { + checkError( + "package", + "test", + String.format(MultiArchSplitTransitionProvider.UNSUPPORTED_PLATFORM_TYPE_ERROR_FORMAT, + "meow_meow_os"), + "apple_static_library(name = 'test', srcs = [ 'a.m' ], platform_type = 'meow_meow_os')"); + } + + @Test + public void testCanUseCrosstool() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all"); + RULE_TYPE.scratchTarget(scratch, "srcs", "['a.m']"); + + // If the target is indeed using the c++ backend, then its archive action should be a + // CppLinkAction. + Action lipoLibAction = lipoLibAction("//x:x"); + Artifact archive = getFirstArtifactEndingWith(lipoLibAction.getInputs(), ".a"); + Action archiveAction = getGeneratingAction(archive); + assertThat(archiveAction).isInstanceOf(CppLinkAction.class); + } + + @Test + public void testCanUseCrosstool_multiArch() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all", + "--ios_multi_cpus=i386,x86_64"); + RULE_TYPE.scratchTarget(scratch, "srcs", "['a.m']"); + + // If the target is indeed using the c++ backend, then its archive action should be a + // CppLinkAction. + Action lipoLibAction = lipoLibAction("//x:x"); + Artifact archive = getFirstArtifactEndingWith(lipoLibAction.getInputs(), ".a"); + Action archiveAction = getGeneratingAction(archive); + assertThat(archiveAction).isInstanceOf(CppLinkAction.class); + } + + @Test + public void testSymlinkInsteadOfLipoSingleArch() throws Exception { + RULE_TYPE.scratchTarget(scratch, + "srcs", "['a.m']"); + + SymlinkAction action = (SymlinkAction) lipoLibAction("//x:x"); + CommandAction linkAction = linkLibAction("//x:x"); + + assertThat(action.getInputs()) + .containsExactly(Iterables.getOnlyElement(linkAction.getOutputs())); + } + + @Test + public void testAvoidDepsProviders() throws Exception { + scratch.file( + "package/BUILD", + "apple_static_library(", + " name = 'test',", + " deps = [':objcLib'],", + " platform_type = 'ios',", + " avoid_deps = [':avoidLib'],", + ")", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ], deps = [':avoidLib', ':baseLib'])", + "objc_library(", + " name = 'baseLib',", + " srcs = [ 'base.m' ],", + " sdk_frameworks = ['BaseSDK'],", + " resources = [':base.png']", + ")", + "objc_library(", + " name = 'avoidLib',", + " srcs = [ 'c.m' ],", + " sdk_frameworks = ['AvoidSDK'],", + " resources = [':avoid.png']", + ")"); + + ObjcProvider provider = providerForTarget("//package:test"); + // Do not remove SDK_FRAMEWORK or GENERAL_RESOURCE_FILE values in avoid_deps. + assertThat(provider.get(ObjcProvider.SDK_FRAMEWORK)) + .containsAllOf(new SdkFramework("AvoidSDK"), new SdkFramework("BaseSDK")); + assertThat(Artifact.toRootRelativePaths(provider.get(ObjcProvider.GENERAL_RESOURCE_FILE))) + .containsExactly("package/base.png"); + } + + @Test + public void testNoSrcs() throws Exception { + scratch.file("package/BUILD", + "apple_static_library(", + " name = 'test',", + " deps = [':objcLib'],", + " platform_type = 'ios',", + ")", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ])"); + useConfiguration("--xcode_version=5.8"); + + CommandAction action = linkLibAction("//package:test"); + assertThat(Artifact.toRootRelativePaths(action.getInputs())).contains("package/libobjcLib.a"); + } + + @Test + public void testLipoAction() throws Exception { + scratch.file("package/BUILD", + "apple_static_library(name = 'test',", + " platform_type = 'ios',", + " srcs = [ 'a.m' ])"); + + useConfiguration("--ios_multi_cpus=i386,x86_64"); + + CommandAction action = (CommandAction) lipoLibAction("//package:test"); + String i386Lib = + configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_IOS) + "package/test-fl.a"; + String x8664Lib = + configurationBin("x86_64", ConfigurationDistinguisher.APPLEBIN_IOS) + "package/test-fl.a"; + + assertThat(Artifact.toExecPaths(action.getInputs())) + .containsExactly(i386Lib, x8664Lib, MOCK_XCRUNWRAPPER_PATH); + + assertThat(action.getArguments()) + .containsExactly( + MOCK_XCRUNWRAPPER_PATH, + LIPO, + "-create", + i386Lib, + x8664Lib, + "-o", + execPathEndingWith(action.getOutputs(), "test_lipo.a")) + .inOrder(); + + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("package/test_lipo.a"); + assertRequiresDarwin(action); + } + + @Test + public void testWatchSimulatorDepCompile() throws Exception { + scratch.file( + "package/BUILD", + "apple_static_library(", + " name = 'test',", + " srcs = ['a.m'],", + " deps = [':objcLib'],", + " platform_type = 'watchos'", + ")", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ])"); + + Action lipoAction = lipoLibAction("//package:test"); + + String i386Bin = + configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_WATCHOS) + "package/test-fl.a"; + Artifact libArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), i386Bin); + CommandAction linkAction = (CommandAction) getGeneratingAction(libArtifact); + CommandAction objcLibCompileAction = + (CommandAction) + getGeneratingAction(getFirstArtifactEndingWith(linkAction.getInputs(), "libobjcLib.a")); + + assertAppleSdkPlatformEnv(objcLibCompileAction, "WatchSimulator"); + assertThat(objcLibCompileAction.getArguments()).containsAllOf("-arch_only", "i386").inOrder(); + } + + @Test + public void testMultiarchCcDep() throws Exception { + scratch.file( + "package/BUILD", + "apple_static_library(name = 'test',", + " srcs = [ 'a.m' ],", + " deps = [ ':cclib' ],", + " platform_type = 'ios')", + "cc_library(name = 'cclib', srcs = ['dep.c'])"); + + useConfiguration( + "--ios_multi_cpus=i386,x86_64", + "--experimental_disable_go", + "--experimental_disable_jvm", + "--crosstool_top=//tools/osx/crosstool:crosstool"); + + CommandAction action = (CommandAction) lipoLibAction("//package:test"); + String i386Prefix = + configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_IOS, + DEFAULT_IOS_SDK_VERSION); + String x8664Prefix = + configurationBin("x86_64", ConfigurationDistinguisher.APPLEBIN_IOS, + DEFAULT_IOS_SDK_VERSION); + + CommandAction i386BinAction = + (CommandAction) + getGeneratingAction( + getFirstArtifactEndingWith(action.getInputs(), i386Prefix + "package/test-fl.a")); + + CommandAction x8664BinAction = + (CommandAction) + getGeneratingAction( + getFirstArtifactEndingWith(action.getInputs(), x8664Prefix + "package/test-fl.a")); + + assertThat(Artifact.toExecPaths(i386BinAction.getInputs())) + .containsAllOf(i386Prefix + "package/libtest.a", i386Prefix + "package/libcclib.a"); + assertThat(Artifact.toExecPaths(x8664BinAction.getInputs())) + .containsAllOf(x8664Prefix + "package/libtest.a", x8664Prefix + "package/libcclib.a"); + } + + @Test + public void testWatchSimulatorLipoAction() throws Exception { + scratch.file("package/BUILD", + "apple_static_library(name = 'test', srcs = [ 'a.m' ], platform_type = 'watchos')"); + + // Tests that ios_multi_cpus and ios_cpu are completely ignored. + useConfiguration("--ios_multi_cpus=x86_64", "--ios_cpu=x86_64", "--watchos_cpus=i386,armv7k"); + + CommandAction action = (CommandAction) lipoLibAction("//package:test"); + String i386Bin = configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_WATCHOS) + + "package/test-fl.a"; + String armv7kBin = configurationBin("armv7k", ConfigurationDistinguisher.APPLEBIN_WATCHOS) + + "package/test-fl.a"; + + assertThat(Artifact.toExecPaths(action.getInputs())) + .containsExactly(i386Bin, armv7kBin, MOCK_XCRUNWRAPPER_PATH); + + assertContainsSublist(action.getArguments(), ImmutableList.of( + MOCK_XCRUNWRAPPER_PATH, LIPO, "-create")); + assertThat(action.getArguments()).containsAllOf(armv7kBin, i386Bin); + assertContainsSublist(action.getArguments(), ImmutableList.of( + "-o", execPathEndingWith(action.getOutputs(), "test_lipo.a"))); + + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("package/test_lipo.a"); + assertAppleSdkPlatformEnv(action, "WatchOS"); + assertRequiresDarwin(action); + } + + @Test + public void testProtoDeps() throws Exception { + scratch.file( + "protos/BUILD", + "proto_library(", + " name = 'protos_main',", + " srcs = ['data_a.proto', 'data_b.proto'],", + ")", + "proto_library(", + " name = 'protos_low_level',", + " srcs = ['data_b.proto'],", + ")", + "objc_proto_library(", + " name = 'objc_protos_main',", + " portable_proto_filters = ['filter_a.pbascii'],", + " deps = [':protos_main'],", + ")", + "objc_proto_library(", + " name = 'objc_protos_low_level',", + " portable_proto_filters = ['filter_b.pbascii'],", + " deps = [':protos_low_level'],", + ")"); + scratch.file( + "libs/BUILD", + "objc_library(", + " name = 'main_lib',", + " srcs = ['a.m'],", + " deps = ['//protos:objc_protos_main',]", + ")", + "objc_library(", + " name = 'apple_low_level_lib',", + " srcs = ['a.m'],", + " deps = ['//protos:objc_protos_low_level',]", + ")"); + + RULE_TYPE.scratchTarget( + scratch, + "deps", "['//libs:main_lib']", + "avoid_deps", "['//libs:apple_low_level_lib']"); + + CommandAction linkAction = linkLibAction("//x:x"); + Iterable<Artifact> linkActionInputs = linkAction.getInputs(); + + ImmutableList.Builder<Artifact> objects = ImmutableList.builder(); + for (Artifact binActionArtifact : linkActionInputs) { + if (binActionArtifact.getRootRelativePath().getPathString().endsWith(".a")) { + CommandAction subLinkAction = (CommandAction) getGeneratingAction(binActionArtifact); + for (Artifact linkActionArtifact : subLinkAction.getInputs()) { + if (linkActionArtifact.getRootRelativePath().getPathString().endsWith(".o")) { + objects.add(linkActionArtifact); + } + } + } + } + + ImmutableList<Artifact> objectFiles = objects.build(); + assertThat(getFirstArtifactEndingWith(objectFiles, "DataA.pbobjc.o")).isNotNull(); + assertThat(getFirstArtifactEndingWith(objectFiles, "DataB.pbobjc.o")).isNull(); + } + + @Test + public void testMinimumOs() throws Exception { + RULE_TYPE.scratchTarget(scratch, + "deps", "['//package:objcLib']", + "minimum_os_version", "'5.4'"); + scratch.file("package/BUILD", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ])"); + useConfiguration("--xcode_version=5.8"); + + CommandAction linkAction = linkLibAction("//x:x"); + CommandAction objcLibArchiveAction = (CommandAction) getGeneratingAction( + getFirstArtifactEndingWith(linkAction.getInputs(), "libobjcLib.a")); + CommandAction objcLibCompileAction = (CommandAction) getGeneratingAction( + getFirstArtifactEndingWith(objcLibArchiveAction.getInputs(), "b.o")); + + String compileArgs = Joiner.on(" ").join(objcLibCompileAction.getArguments()); + assertThat(compileArgs).contains("-mios-simulator-version-min=5.4"); + } + + @Test + public void testMinimumOs_watchos() throws Exception { + RULE_TYPE.scratchTarget(scratch, + "deps", "['//package:objcLib']", + "platform_type", "'watchos'", + "minimum_os_version", "'5.4'"); + scratch.file("package/BUILD", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ])"); + useConfiguration("--xcode_version=5.8"); + + CommandAction linkAction = linkLibAction("//x:x"); + CommandAction objcLibArchiveAction = (CommandAction) getGeneratingAction( + getFirstArtifactEndingWith(linkAction.getInputs(), "libobjcLib.a")); + CommandAction objcLibCompileAction = (CommandAction) getGeneratingAction( + getFirstArtifactEndingWith(objcLibArchiveAction.getInputs(), "b.o")); + + String compileArgs = Joiner.on(" ").join(objcLibCompileAction.getArguments()); + assertThat(compileArgs).contains("-mwatchos-simulator-version-min=5.4"); + } + + @Test + public void testMinimumOs_invalid_nonVersion() throws Exception { + checkMinimumOs_invalid_nonVersion(RULE_TYPE); + } + + @Test + public void testMinimumOs_invalid_containsAlphabetic() throws Exception { + checkMinimumOs_invalid_containsAlphabetic(RULE_TYPE); + } + + @Test + public void testMinimumOs_invalid_tooManyComponents() throws Exception { + checkMinimumOs_invalid_tooManyComponents(RULE_TYPE); + } + + @Test + public void testFilesToCompileOutputGroup() throws Exception { + checkFilesToCompileOutputGroup(RULE_TYPE); + } + + @Test + public void testAppleSdkVersionEnv() throws Exception { + scratch.file("package/BUILD", + "apple_static_library(name = 'test', srcs = [ 'a.m' ], platform_type = 'ios')"); + + CommandAction action = linkLibAction("//package:test"); + + assertAppleSdkVersionEnv(action); + } + + @Test + public void testNonDefaultAppleSdkVersionEnv() throws Exception { + scratch.file("package/BUILD", + "apple_static_library(name = 'test', srcs = [ 'a.m' ], platform_type = 'ios')"); + useConfiguration("--ios_sdk_version=8.1"); + + CommandAction action = linkLibAction("//package:test"); + + assertAppleSdkVersionEnv(action, "8.1"); + } + + @Test + public void testAppleSdkDefaultPlatformEnv() throws Exception { + scratch.file("package/BUILD", + "apple_static_library(name = 'test', srcs = [ 'a.m' ], platform_type = 'ios')"); + CommandAction action = linkLibAction("//package:test"); + + assertAppleSdkPlatformEnv(action, "iPhoneSimulator"); + } + + @Test + public void testAppleSdkIphoneosPlatformEnv() throws Exception { + scratch.file("package/BUILD", + "apple_static_library(name = 'test', srcs = [ 'a.m' ], platform_type = 'ios')"); + useConfiguration("--cpu=ios_arm64"); + + CommandAction action = linkLibAction("//package:test"); + + assertAppleSdkPlatformEnv(action, "iPhoneOS"); + } + + @Test + public void testAppleSdkWatchsimulatorPlatformEnv() throws Exception { + scratch.file( + "package/BUILD", + "apple_static_library(name = 'test', srcs = [ 'a.m' ], platform_type = 'watchos')"); + useConfiguration("--watchos_cpus=i386"); + + Action lipoAction = lipoLibAction("//package:test"); + + String i386Lib = + configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_WATCHOS) + "package/test-fl.a"; + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), i386Lib); + CommandAction linkAction = (CommandAction) getGeneratingAction(binArtifact); + + assertAppleSdkPlatformEnv(linkAction, "WatchSimulator"); + } + + @Test + public void testAppleSdkWatchosPlatformEnv() throws Exception { + scratch.file( + "package/BUILD", + "apple_static_library(name = 'test', srcs = [ 'a.m' ], platform_type = 'watchos')"); + useConfiguration("--watchos_cpus=armv7k"); + + Action lipoAction = lipoLibAction("//package:test"); + + String armv7kLib = + configurationBin("armv7k", ConfigurationDistinguisher.APPLEBIN_WATCHOS) + + "package/test-fl.a"; + Artifact libArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), armv7kLib); + CommandAction linkAction = (CommandAction) getGeneratingAction(libArtifact); + + assertAppleSdkPlatformEnv(linkAction, "WatchOS"); + } + + @Test + public void testXcodeVersionEnv() throws Exception { + scratch.file("package/BUILD", + "apple_static_library(name = 'test', srcs = [ 'a.m' ], platform_type = 'ios')"); + useConfiguration("--xcode_version=5.8"); + + CommandAction action = linkLibAction("//package:test"); + + assertXcodeVersionEnv(action, "5.8"); + } + + @Test + public void testWatchSimulatorLinkAction() throws Exception { + scratch.file( + "package/BUILD", + "apple_static_library(", + " name = 'test',", + " srcs = ['a.m'],", + " deps = [':objcLib'],", + " platform_type = 'watchos'", + ")", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ])"); + + // Tests that ios_multi_cpus and ios_cpu are completely ignored. + useConfiguration("--ios_multi_cpus=x86_64", "--ios_cpu=x86_64", "--watchos_cpus=i386"); + + Action lipoAction = lipoLibAction("//package:test"); + + String i386Bin = + configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_WATCHOS) + "package/test-fl.a"; + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), i386Bin); + CommandAction linkAction = (CommandAction) getGeneratingAction(binArtifact); + + assertAppleSdkPlatformEnv(linkAction, "WatchSimulator"); + assertThat(normalizeBashArgs(linkAction.getArguments())) + .containsAllOf("-arch_only", "i386") + .inOrder(); + } + + @Test + public void testAppleStaticLibraryProvider() throws Exception { + scratch.file( + "lib/BUILD", + "apple_static_library(", + " name = 'applelib',", + " srcs = ['a.m'],", + " platform_type = 'ios',", + " hdrs = ['a.h'],", + ")"); + ConfiguredTarget binTarget = getConfiguredTarget("//lib:applelib"); + AppleStaticLibraryProvider provider = + (AppleStaticLibraryProvider) binTarget.get( + AppleStaticLibraryProvider.SKYLARK_CONSTRUCTOR.getKey()); + assertThat(provider).isNotNull(); + assertThat(provider.getMultiArchArchive()).isNotNull(); + assertThat(provider.getDepsObjcProvider()).isNotNull(); + assertThat(provider.getMultiArchArchive()).isEqualTo( + Iterables.getOnlyElement( + provider.getDepsObjcProvider().get(ObjcProvider.MULTI_ARCH_LINKED_ARCHIVES))); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/AppleStubBinaryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleStubBinaryTest.java new file mode 100644 index 0000000000..d1927fd53d --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleStubBinaryTest.java @@ -0,0 +1,153 @@ +// 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.collect.ImmutableList; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.rules.apple.Platform.PlatformType; +import com.google.devtools.build.lib.testutil.Scratch; +import java.io.IOException; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for apple_stub_binary. */ +@RunWith(JUnit4.class) +public class AppleStubBinaryTest extends ObjcRuleTestCase { + + static final RuleType RULE_TYPE = + new RuleType("apple_stub_binary") { + @Override + Iterable<String> requiredAttributes( + Scratch scratch, String packageDir, Set<String> alreadyAdded) throws IOException { + ImmutableList.Builder<String> attributes = new ImmutableList.Builder<>(); + if (!alreadyAdded.contains("platform_type")) { + attributes.add("platform_type = 'ios'"); + } + return attributes.build(); + } + }; + + @Test + public void testCopyActionEnv() throws Exception { + RULE_TYPE.scratchTarget( + scratch, + "xcenv_based_path", + "'$(SDKROOT)/Library/Application Support/WatchKit/WK'", + "platform_type", + "'watchos'"); + + useConfiguration( + "--watchos_cpus=i386,armv7k", "--xcode_version=7.3", "--watchos_sdk_version=2.1"); + + CommandAction action = (CommandAction) lipoBinAction("//x:x"); + assertAppleSdkVersionEnv(action, "2.1"); + assertAppleSdkPlatformEnv(action, "WatchOS"); + assertXcodeVersionEnv(action, "7.3"); + } + + @Test + public void testFailsWithUndefinedVar() throws Exception { + String target = + RULE_TYPE.target( + scratch, + "x", + "x", + "xcenv_based_path", + "'$(NOT_ALLOWED)/Library/Application Support/WatchKit/WK'"); + useConfiguration("--xcode_version=7.3"); + + checkError("x", "x", "The stub binary path must be rooted at", target); + } + + @Test + public void testFailsIfPathDoesNotBeginWithVar() throws Exception { + String target = + RULE_TYPE.target( + scratch, "x", "x", "xcenv_based_path", "'/Library/Application Support/WatchKit/WK'"); + useConfiguration("--xcode_version=7.3"); + + checkError("x", "x", "The stub binary path must be rooted at", target); + } + + @Test + public void testFailsWithUnnormalizedPath() throws Exception { + String target = + RULE_TYPE.target( + scratch, + "x", + "x", + "xcenv_based_path", + "'$(SDKROOT)/../Library/Application Support/WatchKit/WK'"); + useConfiguration("--xcode_version=7.3"); + + checkError("x", "x", AppleStubBinary.PATH_NOT_NORMALIZED_ERROR, target); + } + + @Test + public void testPlatformSelectionIos() throws Exception { + checkObjcPropagatedResourcesRespectPlatform(PlatformType.IOS); + } + + @Test + public void testPlatformSelectionTvos() throws Exception { + checkObjcPropagatedResourcesRespectPlatform(PlatformType.TVOS); + } + + private void checkObjcPropagatedResourcesRespectPlatform(PlatformType platformType) + throws Exception { + String platformName = platformType.toString(); + + scratch.file("x/a.m"); + scratch.file("x/ios.txt"); + scratch.file("x/tvos.txt"); + scratch.file( + "x/BUILD", + "apple_stub_binary(", + " name = 'bin',", + " platform_type = '" + platformName + "',", + " xcenv_based_path = '$(SDKROOT)/foo/bar',", + " deps = [':lib'],", + ")", + "", + "config_setting(name = 'ios', values = {'apple_platform_type': 'ios'})", + "config_setting(name = 'tvos', values = {'apple_platform_type': 'tvos'})", + "", + "objc_library(", + " name = 'lib',", + " srcs = ['a.m'],", + " resources = select({", + " ':ios': ['ios.txt'],", + " ':tvos': ['tvos.txt'],", + " })", + ")"); + + ConfiguredTarget target = getConfiguredTarget("//x:bin"); + ObjcProvider objc = (ObjcProvider) target.get(ObjcProvider.OBJC_SKYLARK_PROVIDER_NAME); + + // The propagated objc provider should only contain one file, and that file is the one selected + // for the given platform type. + NestedSet<BundleableFile> bundleFiles = objc.get(ObjcProvider.BUNDLE_FILE); + assertThat(bundleFiles).hasSize(1); + + BundleableFile bundleFile = bundleFiles.iterator().next(); + assertThat(bundleFile.getBundled().getFilename()).isEqualTo(platformName + ".txt"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/AppleToolchainSelectionTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleToolchainSelectionTest.java new file mode 100644 index 0000000000..b062851303 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleToolchainSelectionTest.java @@ -0,0 +1,183 @@ +// 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 static com.google.devtools.build.lib.actions.util.ActionsTestUtil.getFirstArtifactEndingWith; + +import com.google.common.base.Joiner; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.util.ScratchAttributeWriter; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.rules.apple.AppleConfiguration.ConfigurationDistinguisher; +import com.google.devtools.build.lib.rules.cpp.CppConfiguration; +import com.google.devtools.build.lib.rules.cpp.CppLinkAction; +import com.google.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for the use of the OSX crosstool. */ +@RunWith(JUnit4.class) +public class AppleToolchainSelectionTest extends ObjcRuleTestCase { + + @Override + protected void useConfiguration(String... args) throws Exception { + useConfiguration(ObjcCrosstoolMode.LIBRARY, args); + } + + /** + * Returns the given target in the configuration that it would be given by this + * {@code BuildViewTestCase}'s {@code Transitions}, were the target a top-level target. + */ + private ConfiguredTarget getTopLevelConfiguredTarget(ConfiguredTarget target) + throws InterruptedException { + BuildConfiguration topLevelConfig = getAppleCrosstoolConfiguration(); + return getConfiguredTarget(target.getLabel(), topLevelConfig); + } + + /** + * Returns the action that produces the artifact with the given label and suffix, in a output + * directory consistent with that action being registered by a top-level target. + */ + private CommandAction actionProducingArtifactForTopLevelTarget(String targetLabel, + String artifactSuffix) throws Exception { + ConfiguredTarget libraryTarget = getConfiguredTarget(targetLabel); + ConfiguredTarget topLevelLibraryTarget = getTopLevelConfiguredTarget(libraryTarget); + Label parsedLabel = Label.parseAbsolute(targetLabel); + Artifact linkedLibrary = getBinArtifact( + parsedLabel.getName() + artifactSuffix, + topLevelLibraryTarget); + return (CommandAction) getGeneratingAction(linkedLibrary); + } + + @Test + public void testToolchainSelectionDefault() throws Exception { + createLibraryTargetWriter("//a:lib").write(); + CppConfiguration cppConfig = + getAppleCrosstoolConfiguration().getFragment(CppConfiguration.class); + + assertThat(cppConfig.getCrosstoolTopPathFragment().toString()) + .isEqualTo("tools/osx/crosstool"); + assertThat(cppConfig.getToolchainIdentifier()) + .isEqualTo("ios_x86_64"); + } + + @Test + public void testToolchainSelectionIosDevice() throws Exception { + useConfiguration("--cpu=ios_armv7"); + createLibraryTargetWriter("//a:lib").write(); + CppConfiguration cppConfig = + getAppleCrosstoolConfiguration().getFragment(CppConfiguration.class); + + assertThat(cppConfig.getCrosstoolTopPathFragment().toString()) + .isEqualTo("tools/osx/crosstool"); + assertThat(cppConfig.getToolchainIdentifier()) + .isEqualTo("ios_armv7"); + } + + @Test + public void testToolchainSelectionCcDepDefault() throws Exception { + useConfiguration( + "--experimental_disable_jvm", + "--experimental_disable_go"); + ScratchAttributeWriter + .fromLabelString(this, "cc_library", "//b:lib") + .setList("srcs", "b.cc") + .write(); + createBinaryTargetWriter("//a:bin") + .setList("srcs", "a.m") + .setList("deps", "//b:lib") + .write(); + + CommandAction linkAction = actionProducingArtifactForTopLevelTarget("//a:bin", "_bin"); + Artifact ccArchive = getFirstArtifactEndingWith(linkAction.getInputs(), "liblib.a"); + CommandAction ccArchiveAction = (CommandAction) getGeneratingAction(ccArchive); + Artifact ccObjectFile = getFirstArtifactEndingWith(ccArchiveAction.getInputs(), ".o"); + CommandAction ccCompileAction = (CommandAction) getGeneratingAction(ccObjectFile); + assertThat(ccCompileAction.getArguments()).contains("tools/osx/crosstool/iossim/wrapped_clang"); + } + + @Test + public void testToolchainSelectionCcDepDevice() throws Exception { + useConfiguration("--cpu=ios_armv7"); + ScratchAttributeWriter + .fromLabelString(this, "cc_library", "//b:lib") + .setList("srcs", "b.cc") + .write(); + createBinaryTargetWriter("//a:bin") + .setList("srcs", "a.m") + .setList("deps", "//b:lib") + .write(); + CommandAction linkAction = actionProducingArtifactForTopLevelTarget("//a:bin", "_bin"); + Artifact ccArchive = getFirstArtifactEndingWith(linkAction.getInputs(), "liblib.a"); + CommandAction ccArchiveAction = (CommandAction) getGeneratingAction(ccArchive); + Artifact ccObjectFile = getFirstArtifactEndingWith(ccArchiveAction.getInputs(), ".o"); + CommandAction ccCompileAction = (CommandAction) getGeneratingAction(ccObjectFile); + assertThat(ccCompileAction.getArguments()).contains("tools/osx/crosstool/ios/wrapped_clang"); + } + + @Test + public void testToolchainSelectionMultiArchIos() throws Exception { + useConfiguration( + "--experimental_disable_jvm", + "--ios_multi_cpus=armv7,arm64"); + ScratchAttributeWriter + .fromLabelString(this, "cc_library", "//b:lib") + .setList("srcs", "a.cc") + .write(getAppleCrosstoolConfiguration()); + ScratchAttributeWriter + .fromLabelString(this, "apple_binary", "//a:bin") + .set("platform_type", "'ios'") + .setList("deps", "//b:lib") + .write(); + Action lipoAction = actionProducingArtifact("//a:bin", "_lipobin"); + String armv64Bin = + configurationBin("arm64", ConfigurationDistinguisher.APPLEBIN_IOS, + DEFAULT_IOS_SDK_VERSION) + + "a/bin_bin"; + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), armv64Bin); + CommandAction linkAction = getGeneratingSpawnAction(binArtifact); + CppLinkAction objcLibArchiveAction = (CppLinkAction) getGeneratingAction( + getFirstArtifactEndingWith(linkAction.getInputs(), "liblib.a")); + assertThat(Joiner.on(" ").join(objcLibArchiveAction.getArgv())).contains("ios_arm64"); + } + + @Test + public void testToolchainSelectionMultiArchWatchos() throws Exception { + useConfiguration( + "--experimental_disable_jvm", + "--ios_multi_cpus=armv7,arm64", + "--watchos_cpus=armv7k"); + ScratchAttributeWriter + .fromLabelString(this, "cc_library", "//b:lib") + .setList("srcs", "a.cc") + .write(getAppleCrosstoolConfiguration()); + ScratchAttributeWriter + .fromLabelString(this, "apple_binary", "//a:bin") + .setList("deps", "//b:lib") + .set("platform_type", "\"watchos\"") + .write(); + + CommandAction linkAction = linkAction("//a:bin"); + CppLinkAction objcLibCompileAction = (CppLinkAction) getGeneratingAction( + getFirstArtifactEndingWith(linkAction.getInputs(), "liblib.a")); + assertThat(Joiner.on(" ").join(objcLibCompileAction.getArgv())).contains("watchos_armv7k"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/AppleWatch1ExtensionTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleWatch1ExtensionTest.java new file mode 100644 index 0000000000..9bad38b731 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleWatch1ExtensionTest.java @@ -0,0 +1,1005 @@ +// 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 static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_BUNDLE_ID_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_ICON_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_INFOPLISTS_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_PROVISIONING_PROFILE_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_BUNDLE_ID_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_INFOPLISTS_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_PROVISIONING_PROFILE_ATTR; + +import com.google.common.base.Joiner; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multiset; +import com.google.devtools.build.lib.actions.Action; +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.SpawnAction; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.rules.apple.AppleConfiguration.ConfigurationDistinguisher; +import com.google.devtools.build.lib.testutil.Scratch; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.BundleFile; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.Control; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.MergeZip; +import com.google.devtools.build.xcode.plmerge.proto.PlMergeProtos; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for apple_watch1_extension. */ +@RunWith(JUnit4.class) +public class AppleWatch1ExtensionTest extends ObjcRuleTestCase { + private static final RuleType RULE_TYPE = new RuleType("apple_watch1_extension") { + @Override + Iterable<String> requiredAttributes( + Scratch scratch, String packageDir, Set<String> alreadyAdded) throws IOException { + ImmutableList.Builder<String> attributes = new ImmutableList.Builder<>(); + if (!alreadyAdded.contains("binary")) { + scratch.file(packageDir + "/extension_binary/a.m"); + scratch.file(packageDir + "/extension_binary/BUILD", + "apple_watch_extension_binary(", + " name = 'extension_binary',", + " srcs = ['a.m'],", + ")"); + attributes.add(String.format("binary = '//%s/extension_binary'", packageDir)); + } + if (!alreadyAdded.contains("app_name")) { + attributes.add("app_name = 'y'"); + } + return attributes.build(); + } + }; + + protected static final BinaryRuleTypePair RULE_TYPE_PAIR = + new BinaryRuleTypePair( + AppleWatchExtensionBinaryTest.RULE_TYPE, + RULE_TYPE, + ReleaseBundlingSupport.EXTENSION_BUNDLE_DIR_FORMAT); + + private ConfiguredTarget addMockExtensionAndLibs(String... extraExtAttributes) + throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + scratch.file("x/a.m"); + scratch.file("x/BUILD", + "apple_watch_extension_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " deps = ['//lib1:lib1', '//lib2:lib2'],", + ")", + "", + "apple_watch1_extension(", + " name = 'x',", + " app_name = 'y',", + " binary = ':bin',", + Joiner.on(',').join(extraExtAttributes), + ")"); + return getConfiguredTarget("//x:x"); + } + + private void addEntitlements() throws Exception { + scratch.file("x/ext_entitlements.entitlements"); + scratch.file("x/app_entitlements.entitlements"); + addMockExtensionAndLibs( + "ext_entitlements = 'ext_entitlements.entitlements'", + "app_entitlements = 'app_entitlements.entitlements'"); + } + + private Action watchApplicationIpaGeneratingAction() throws Exception { + return getGeneratingAction(getBinArtifact("_watch/x/y.ipa", + "//x:x")); + } + + @Test + public void testExtensionSigningAction() throws Exception { + useConfiguration("--cpu=ios_armv7"); + addEntitlements(); + SpawnAction action = (SpawnAction) getGeneratingActionForLabel("//x:x.ipa"); + assertRequiresDarwin(action); + assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())) + .containsExactly("x.entitlements", "foo.mobileprovision", "x.unprocessed.ipa"); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())).containsExactly("x/x.ipa"); + } + + @Test + public void testApplicationSigningAction() throws Exception { + useConfiguration("--cpu=ios_armv7"); + addEntitlements(); + SpawnAction action = (SpawnAction) watchApplicationIpaGeneratingAction(); + assertRequiresDarwin(action); + assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())) + .containsExactly("x-y.entitlements", "foo.mobileprovision", "x-y.unprocessed.ipa"); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("x/_watch/x/y.ipa"); + } + + @Test + public void testExtensionSigningWithCertName() throws Exception { + useConfiguration("--cpu=ios_armv7", "--ios_signing_cert_name=Foo Bar"); + addEntitlements(); + SpawnAction action = (SpawnAction) getGeneratingActionForLabel("//x:x.ipa"); + assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())) + .containsExactly("x.entitlements", "foo.mobileprovision", "x.unprocessed.ipa"); + assertThat(Joiner.on(' ').join(action.getArguments())).contains("--sign \"Foo Bar\""); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())).containsExactly("x/x.ipa"); + } + + @Test + public void testApplicationSigningWithCertName() throws Exception { + useConfiguration("--cpu=ios_armv7", "--ios_signing_cert_name=Foo Bar"); + addEntitlements(); + SpawnAction action = (SpawnAction) watchApplicationIpaGeneratingAction(); + assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())) + .containsExactly("x-y.entitlements", "foo.mobileprovision", "x-y.unprocessed.ipa"); + assertThat(Joiner.on(' ').join(action.getArguments())).contains("--sign \"Foo Bar\""); + + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("x/_watch/x/y.ipa"); + } + + @Test + public void testSigning_simulatorBuild() throws Exception { + checkSigningSimulatorBuild(RULE_TYPE_PAIR, false); + } + + @Test + public void testSigning_simulatorBuild_multiCpu() throws Exception { + checkSigningSimulatorBuild(RULE_TYPE_PAIR, true); + } + + @Test + public void testProvisioningProfile_deviceBuild() throws Exception { + checkProvisioningProfileDeviceBuild(RULE_TYPE_PAIR, false); + } + + @Test + public void testProvisioningProfile_deviceBuild_multiCpu() throws Exception { + checkProvisioningProfileDeviceBuild(RULE_TYPE_PAIR, true); + } + + @Test + public void testUserSpecifiedExtensionProvisioningProfile_deviceBuild() throws Exception { + checkProvisioningProfileUserSpecified(RULE_TYPE_PAIR, false, + WATCH_EXT_PROVISIONING_PROFILE_ATTR); + } + + @Test + public void testUserSpecifiedApplicationProvisioningProfile_deviceBuild() throws Exception { + checkSpecifiedApplicationProvisioningProfile(false); + } + + @Test + public void testUserSpecifiedExtensionProvisioningProfile_deviceBuild_multiCpu() + throws Exception { + checkProvisioningProfileUserSpecified(RULE_TYPE_PAIR, true, + WATCH_EXT_PROVISIONING_PROFILE_ATTR); + } + + @Test + public void testUserSpecifiedApplicationProvisioningProfile_deviceBuild_multiCpu() + throws Exception { + checkSpecifiedApplicationProvisioningProfile(true); + } + + private void checkSpecifiedApplicationProvisioningProfile(boolean useMultiCpu) throws Exception { + setArtifactPrefix("y"); + if (useMultiCpu) { + useConfiguration("--ios_multi_cpus=armv7,arm64", "--cpu=ios_i386"); + } else { + useConfiguration("--cpu=ios_armv7"); + } + + addCustomProvisioningProfile(RULE_TYPE_PAIR, WATCH_APP_PROVISIONING_PROFILE_ATTR); + getConfiguredTarget("//x:x"); + + Artifact defaultProvisioningProfile = + getFileConfiguredTarget("//tools/objc:foo.mobileprovision").getArtifact(); + Artifact customProvisioningProfile = + getFileConfiguredTarget("//custom:pp.mobileprovision").getArtifact(); + Action signingAction = watchApplicationIpaGeneratingAction(); + assertThat(signingAction.getInputs()).contains(customProvisioningProfile); + assertThat(signingAction.getInputs()).doesNotContain(defaultProvisioningProfile); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + Map<String, String> profiles = mobileProvisionProfiles(control); + Map<String, String> expectedProfiles = ImmutableMap.of( + customProvisioningProfile.getExecPathString(), + ReleaseBundlingSupport.PROVISIONING_PROFILE_BUNDLE_FILE); + assertThat(profiles).isEqualTo(expectedProfiles); + } + + @Test + public void testExtensionMergeControlAction() throws Exception { + addMockExtensionAndLibs("ext_infoplists = ['Info.plist']"); + Action mergeAction = bundleMergeAction("//x:x"); + Action action = bundleMergeControlAction("//x:x"); + assertThat(action.getInputs()).isEmpty(); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())).containsExactly( + "x/x.ipa-control"); + assertThat(bundleMergeControl("//x:x")) + .isEqualTo( + BundleMergeProtos.Control.newBuilder() + .addBundleFile( + BundleFile.newBuilder() + .setSourceFile(execPathEndingWith(mergeAction.getInputs(), "x_lipobin")) + .setBundlePath("x") + .setExternalFileAttribute(BundleableFile.EXECUTABLE_EXTERNAL_FILE_ATTRIBUTE) + .build()) + .setBundleRoot("PlugIns/x.appex") + .addMergeZip( + MergeZip.newBuilder() + .setEntryNamePrefix("PlugIns/x.appex/") + .setSourcePath( + execPathEndingWith(mergeAction.getInputs(), "_watch/x/y.zip")) + .build()) + .setBundleInfoPlistFile( + getMergedInfoPlist(getConfiguredTarget("//x:x")).getExecPathString()) + .setOutFile(execPathEndingWith(mergeAction.getOutputs(), "x.unprocessed.ipa")) + .setMinimumOsVersion(DEFAULT_IOS_SDK_VERSION.toString()) + .setSdkVersion(DEFAULT_IOS_SDK_VERSION.toString()) + .setPlatform("IOS_SIMULATOR") + .setFallbackBundleIdentifier("example.ext.x") + .build()); + } + + @Test + public void testApplicationMergeControlAction() throws Exception { + setArtifactPrefix("y"); + addMockExtensionAndLibs("app_infoplists = ['Info.plist']"); + Action mergeAction = bundleMergeAction("//x:x"); + Action action = bundleMergeControlAction("//x:x"); + assertThat(action.getInputs()).isEmpty(); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())).containsExactly( + "x/x-y.ipa-control"); + assertThat(bundleMergeControl("//x:x")) + .isEqualTo( + BundleMergeProtos.Control.newBuilder() + .setBundleRoot("Payload/y.app") + .addMergeZip( + MergeZip.newBuilder() + .setEntryNamePrefix("Payload/y.app/") + .setSourcePath( + getBinArtifact("_watch/x/WatchKitStub.zip", "//x:x") + .getExecPathString()) + .build()) + .setBundleInfoPlistFile( + getMergedInfoPlist(getConfiguredTarget("//x:x")).getExecPathString()) + .setOutFile(execPathEndingWith(mergeAction.getOutputs(), "x-y.unprocessed.ipa")) + .setMinimumOsVersion(DEFAULT_IOS_SDK_VERSION.toString()) + .setSdkVersion(DEFAULT_IOS_SDK_VERSION.toString()) + .setPlatform("IOS_SIMULATOR") + .setFallbackBundleIdentifier("example.app.y") + .build()); + } + + @Test + public void testMergeExtensionBundleAction() throws Exception { + RULE_TYPE_PAIR.scratchTargets(scratch, + "ext_infoplists", "['Info.plist']"); + SpawnAction action = bundleMergeAction("//x:x"); + assertThat(Artifact.toRootRelativePaths(action.getInputs())) + .containsExactly( + MOCK_BUNDLEMERGE_PATH, + "x/x_lipobin", + "x/x.ipa-control", + "x/x-MergedInfo.plist", + "x/_watch/x/y.zip"); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("x/x.unprocessed.ipa"); + assertNotRequiresDarwin(action); + assertThat(action.getEnvironment()).isEmpty(); + assertThat(action.getArguments()) + .containsExactly( + MOCK_BUNDLEMERGE_PATH, + execPathEndingWith(action.getInputs(), "x.ipa-control")) + .inOrder(); + } + + @Test + public void testMergeApplicationBundleAction() throws Exception { + RULE_TYPE_PAIR.scratchTargets(scratch, + "app_infoplists", "['Info.plist']"); + setArtifactPrefix("y"); + SpawnAction action = bundleMergeAction("//x:x"); + assertThat(Artifact.toRootRelativePaths(action.getInputs())) + .containsExactly( + MOCK_BUNDLEMERGE_PATH, + "x/x-y.ipa-control", + "x/x-y-MergedInfo.plist", + "x/_watch/x/WatchKitStub.zip"); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("x/x-y.unprocessed.ipa"); + assertNotRequiresDarwin(action); + assertThat(action.getEnvironment()).isEmpty(); + assertThat(action.getArguments()) + .containsExactly( + MOCK_BUNDLEMERGE_PATH, + execPathEndingWith(action.getInputs(), "x-y.ipa-control")) + .inOrder(); + } + + protected List<BuildConfiguration> getExtensionConfigurations() throws InterruptedException { + return getSplitConfigurations(getTargetConfiguration(), + AppleWatch1Extension.MINIMUM_OS_AND_SPLIT_ARCH_TRANSITION); + } + + @Test + public void testErrorForAppIconGivenWithNoAssetCatalog() throws Exception { + checkAssetCatalogAttributeError(RULE_TYPE, WATCH_APP_ICON_ATTR, WATCH_EXT_INFOPLISTS_ATTR, + "['pl.plist']"); + } + + @Override + protected void checkCollectsAssetCatalogsTransitively(BinaryRuleTypePair ruleTypePair) + throws Exception { + scratch.file("lib/ac.xcassets/foo"); + scratch.file("lib/ac.xcassets/bar"); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .set("asset_catalogs", "glob(['ac.xcassets/**'])") + .write(); + + scratch.file("x/ac.xcassets/baz"); + scratch.file("x/ac.xcassets/42"); + ruleTypePair.scratchTargets(scratch, + "deps", "['//lib:lib']", + "app_asset_catalogs", "glob(['ac.xcassets/**'])"); + + // Test that the actoolzip Action for extension has arguments and inputs obtained from + // dependencies. + SpawnAction extensionActoolZipAction = actoolZipActionForIpa("//x:x"); + assertThat(Artifact.toExecPaths(extensionActoolZipAction.getInputs())).containsExactly( + "lib/ac.xcassets/foo", "lib/ac.xcassets/bar", + MOCK_ACTOOLWRAPPER_PATH); + assertContainsSublist(extensionActoolZipAction.getArguments(), + ImmutableList.of("lib/ac.xcassets")); + + // Test that the actoolzip Action for application has arguments and inputs obtained from + // dependencies. + SpawnAction applicationActoolZipAction = (SpawnAction) getGeneratingAction( + getBinArtifact("x-y.actool.zip", "//x:x")); + assertThat(Artifact.toExecPaths(applicationActoolZipAction.getInputs())).containsExactly( + "x/ac.xcassets/baz", "x/ac.xcassets/42", + MOCK_ACTOOLWRAPPER_PATH); + assertContainsSublist(applicationActoolZipAction.getArguments(), + ImmutableList.of("x/ac.xcassets")); + } + + @Test + public void testCollectsAssetCatalogsTransitively() throws Exception { + checkCollectsAssetCatalogsTransitively(RULE_TYPE_PAIR); + } + + private void addTargetWithAssetCatalogs() throws IOException { + scratch.file("x/foo.xcassets/foo"); + scratch.file("x/foo.xcassets/bar"); + scratch.file("x/a.m"); + scratch.file("x/BUILD", + "apple_watch_extension_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + ")", + "", + "apple_watch1_extension(", + " name = 'x',", + " app_name = 'y',", + " app_asset_catalogs = ['foo.xcassets/foo', 'bar.xcassets/bar'],", + " binary = ':bin',", + ")"); + } + + @Test + public void testActoolActionCorrectness() throws Exception { + addTargetWithAssetCatalogs(); + setArtifactPrefix("y"); + checkActoolActionCorrectness(DEFAULT_IOS_SDK_VERSION, + TargetDeviceFamily.WATCH.getNameInRule().toLowerCase(), "iphonesimulator"); + } + + @Test + public void testPassesFamiliesToActool() throws Exception { + checkPassesFamiliesToActool(RULE_TYPE_PAIR, AppleWatch1ExtensionRule.WATCH_EXT_FAMILIES_ATTR); + } + + @Test + public void testPassesFamiliesToIbtool() throws Exception { + checkPassesFamiliesToIbtool(RULE_TYPE_PAIR, AppleWatch1ExtensionRule.WATCH_EXT_FAMILIES_ATTR); + } + + @Test + public void testReportsErrorsForInvalidFamiliesAttribute() throws Exception { + checkReportsErrorsForInvalidFamiliesAttribute(RULE_TYPE, + AppleWatch1ExtensionRule.WATCH_EXT_FAMILIES_ATTR); + } + + @Test + public void testMergeActionsWithAssetCatalog() throws Exception { + addTargetWithAssetCatalogs(); + setArtifactPrefix("y"); + Artifact actoolZipOut = getBinArtifact("x-y.actool.zip", "//x:x"); + assertThat(bundleMergeAction("//x:x").getInputs()).contains(actoolZipOut); + + BundleMergeProtos.Control mergeControl = bundleMergeControl("//x:x"); + assertThat(mergeControl.getMergeZipList()) + .containsExactly( + MergeZip.newBuilder() + .setEntryNamePrefix("Payload/y.app/") + .setSourcePath(actoolZipOut.getExecPathString()) + .build(), + MergeZip.newBuilder() + .setEntryNamePrefix("Payload/y.app/") + .setSourcePath(getBinArtifact("_watch/x/WatchKitStub.zip", "//x:x") + .getExecPathString()) + .build()); + } + + private void addBinAndLibWithRawResources() throws Exception { + addBinAndLibWithResources( + "resources", "resource1.txt", "ja.lproj/resource2.txt", "apple_watch_extension_binary"); + scratch.file("app_resource.txt"); + scratch.file("ext_resource.txt"); + scratch.file("x/BUILD", + "apple_watch1_extension(", + " name = 'x',", + " app_name = 'y',", + " binary = '//bin:bin',", + " app_resources = ['app_resource.txt'],", + " ext_resources = ['ext_resource.txt'],", + ")"); + } + + private void addBinAndLibWithStrings() throws Exception { + addBinAndLibWithResources( + "strings", "foo.strings", "ja.lproj/bar.strings", "apple_watch_extension_binary"); + scratch.file("app.strings"); + scratch.file("x/BUILD", + "apple_watch1_extension(", + " name = 'x',", + " app_name = 'y',", + " binary = '//bin:bin',", + " app_strings = ['app.strings'],", + ")"); + } + + @Test + public void testCollectsRawResourceFilesTransitively() throws Exception { + addBinAndLibWithRawResources(); + checkCollectsResourceFilesTransitively( + "//x:x", + ImmutableList.of("lib/resource1.txt", "bin/ja.lproj/resource2.txt"), + ImmutableList.of("lib/resource1.txt"), + ImmutableSetMultimap.<String, Multiset<String>>of( + "bin_bin", ImmutableMultiset.of("bin/ja.lproj/resource2.txt", "lib/resource1.txt"), + "x_x", ImmutableMultiset.of("bin/ja.lproj/resource2.txt", "lib/resource1.txt", + "x/ext_resource.txt"), + "lib_lib", ImmutableMultiset.of("lib/resource1.txt"), + "y__x", ImmutableMultiset.of("x/app_resource.txt"))); + } + + @Test + public void testCollectsStringsFilesTransitively() throws Exception { + addBinAndLibWithStrings(); + checkCollectsResourceFilesTransitively( + "//x:x", + ImmutableList.of("x/lib/foo.strings.binary", "x/bin/ja.lproj/bar.strings.binary"), + ImmutableList.of("lib/foo.strings.binary"), + ImmutableSetMultimap.<String, Multiset<String>>of( + "bin_bin", ImmutableMultiset.of("bin/ja.lproj/bar.strings", "lib/foo.strings"), + "x_x", ImmutableMultiset.of("bin/ja.lproj/bar.strings", "lib/foo.strings"), + "lib_lib", ImmutableMultiset.of("lib/foo.strings"), + "y__x", ImmutableMultiset.of("x/app.strings"))); + } + + @Test + public void testResourceFilesMergedInBundle() throws Exception { + addBinAndLibWithRawResources(); + checkBundleablesAreMerged("//x:x", + ImmutableListMultimap.of( + "resource1.txt", "resource1.txt", + "ja.lproj/resource2.txt", "ja.lproj/resource2.txt")); + } + + @Test + public void testStringsFilesMergedInBundle() throws Exception { + addBinAndLibWithStrings(); + checkBundleablesAreMerged("//x:x", + ImmutableListMultimap.of( + "foo.strings.binary", "foo.strings", + "ja.lproj/bar.strings.binary", "ja.lproj/bar.strings")); + } + + @Test + public void testPlistRequiresDotInName() throws Exception { + String errorMessage = "'//x:Infoplist' does not produce any apple_watch1_extension " + + "ext_infoplists files (expected .plist)"; + checkError("x", "x", + errorMessage, + "apple_watch_extension_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + ")", + "", + "apple_watch1_extension(", + " name = 'x',", + " app_name = 'y',", + " ext_infoplists = ['Infoplist'],", + " binary = ':bin',", + ")"); + } + + @Test + public void testIncludesStoryboardOutputZipsAsMergeZipsForApplication() throws Exception { + addStoryboards(); + setArtifactPrefix("y"); + Artifact libsbOutputZip = getBinArtifact("x-y/libsb.storyboard.zip", "//x:x"); + + Control mergeControl = bundleMergeControl("//x:x"); + assertThat(mergeControl.getMergeZipList()).containsExactly( + MergeZip.newBuilder() + .setEntryNamePrefix("Payload/y.app/") + .setSourcePath(libsbOutputZip.getExecPathString()) + .build(), + MergeZip.newBuilder() + .setEntryNamePrefix("Payload/y.app/") + .setSourcePath(getBinArtifact("_watch/x/WatchKitStub.zip", "//x:x") + .getExecPathString()) + .build()); + } + + protected void addStoryboards() throws Exception { + scratch.file("lib/libsb.storyboard"); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("storyboards", "libsb.storyboard") + .write(); + + scratch.file("bndl/bndlsb.storyboard"); + scratch.file("bndl/BUILD", + "objc_bundle_library(", + " name = 'bndl',", + " storyboards = ['ext_bndlsb.storyboard'],", + ")"); + + scratch.file("x/xsb.storyboard"); + RULE_TYPE_PAIR.scratchTargets(scratch, + "storyboards", "['ext.storyboard']", + "app_deps", "['//lib:lib']", + "bundles", "['//bndl:bndl']"); + getConfiguredTarget("//x:x"); + } + + @Test + public void testMergesPartialInfoplists() throws Exception { + scratch.file("x/primary-Info.plist"); + setArtifactPrefix("y"); + RULE_TYPE_PAIR.scratchTargets(scratch, + "app_asset_catalogs", "['foo.xcassets/bar']", + "app_infoplists", "['primary-Info.plist']"); + + String targetName = "//x:x"; + ConfiguredTarget target = getConfiguredTarget(targetName); + PlMergeProtos.Control control = plMergeControl(targetName); + + Artifact merged = getBinArtifact("x-y-MergedInfo.plist", target); + Artifact actoolPartial = getBinArtifact("x-y.actool-PartialInfo.plist", "//x:x"); + + Artifact versionInfoplist = getBinArtifact("plists/x-y-version.plist", target); + Artifact environmentInfoplist = getBinArtifact("plists/x-y-environment.plist", target); + Artifact automaticInfoplist = getBinArtifact("plists/x-y-automatic.plist", target); + + assertPlistMergeControlUsesSourceFiles( + control, + ImmutableList.<String>of( + "x/primary-Info.plist", + versionInfoplist.getExecPathString(), + environmentInfoplist.getExecPathString(), + automaticInfoplist.getExecPathString(), + actoolPartial.getExecPathString())); + assertThat(control.getOutFile()).isEqualTo(merged.getExecPathString()); + assertThat(control.getVariableSubstitutionMapMap()) + .containsExactlyEntriesIn(variableSubstitutionsForWatchApplication()); + assertThat(control.getFallbackBundleId()).isEqualTo("example.app.y"); + } + + @Test + public void testNibZipsMergedIntoBundle() throws Exception { + checkNibZipsMergedIntoBundle(RULE_TYPE_PAIR); + } + + @Test + public void testPassesExtensionFallbackBundleIdToBundleMerging() throws Exception { + scratch.file("bin/a.m"); + scratch.file("bin/Ext-Info.plist"); + + RULE_TYPE.scratchTarget(scratch, + WATCH_EXT_INFOPLISTS_ATTR, "['Ext-Info.plist']"); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + + assertThat(control.hasPrimaryBundleIdentifier()).isFalse(); + assertThat(control.getFallbackBundleIdentifier()).isEqualTo("example.ext.x"); + } + + @Test + public void testPassesApplicationFallbackBundleIdToBundleMerging() throws Exception { + setArtifactPrefix("y"); + scratch.file("bin/a.m"); + scratch.file("bin/App-Info.plist"); + + RULE_TYPE.scratchTarget(scratch, + WATCH_APP_INFOPLISTS_ATTR, "['App-Info.plist']"); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + + assertThat(control.hasPrimaryBundleIdentifier()).isFalse(); + assertThat(control.getFallbackBundleIdentifier()).isEqualTo("example.app.y"); + } + + @Test + public void testPassesExtensionPrimaryBundleIdToBundleMerging() throws Exception { + scratch.file("bin/a.m"); + scratch.file("bin/Ext-Info.plist"); + + RULE_TYPE.scratchTarget(scratch, + WATCH_EXT_INFOPLISTS_ATTR, "['Ext-Info.plist']", + WATCH_EXT_BUNDLE_ID_ATTR, "'com.bundle.ext.id'"); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + + assertThat(control.getPrimaryBundleIdentifier()).isEqualTo("com.bundle.ext.id"); + assertThat(control.hasFallbackBundleIdentifier()).isFalse(); + } + + @Test + public void testPassesApplicationPrimaryBundleIdToBundleMerging() throws Exception { + setArtifactPrefix("y"); + scratch.file("bin/a.m"); + scratch.file("bin/App-Info.plist"); + + RULE_TYPE.scratchTarget(scratch, + WATCH_APP_INFOPLISTS_ATTR, "['App-Info.plist']", + WATCH_APP_BUNDLE_ID_ATTR, "'com.bundle.app.id'"); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + + assertThat(control.getPrimaryBundleIdentifier()).isEqualTo("com.bundle.app.id"); + assertThat(control.hasFallbackBundleIdentifier()).isFalse(); + } + + @Test + public void testMultiPlatformBuild_fails() throws Exception { + checkBinaryActionMultiPlatform_fails(RULE_TYPE_PAIR); + } + + @Test + public void testMultiArchitectureResources() throws Exception { + useConfiguration("--ios_multi_cpus=i386,x86_64"); + RULE_TYPE_PAIR.scratchTargets(scratch, "resources", "['foo.png']"); + + assertThat(Artifact.toRootRelativePaths(bundleMergeAction("//x:x").getInputs())) + .containsExactly( + "x/foo.png", + "x/x_lipobin", + "tools/objc/bundlemerge", + "x/x.ipa-control", + "x/x-MergedInfo.plist", + "x/_watch/x/y.zip"); + } + + @Override + protected void addCommonResources(BinaryRuleTypePair ruleTypePair) throws Exception { + ruleTypePair.scratchTargets(scratch, + "strings", "['foo.strings']", + "storyboards", "['baz.storyboard']"); + } + + @Test + public void testMultiCpuCompiledResources() throws Exception { + checkMultiCpuCompiledResources(RULE_TYPE_PAIR); + } + + @Test + public void testConvertStringsActions() throws Exception { + checkConvertStringsAction(RULE_TYPE_PAIR); + } + + @Test + public void testCompileXibActions() throws Exception { + checkCompileXibActions(RULE_TYPE_PAIR, DEFAULT_IOS_SDK_VERSION, "iphone"); + } + + @Test + public void testRegistersStoryboardCompileActions() throws Exception { + checkRegistersStoryboardCompileActions(RULE_TYPE_PAIR, DEFAULT_IOS_SDK_VERSION, "iphone"); + } + + @Test + public void testMultiCpuCompiledResourcesFromGenrule() throws Exception { + checkMultiCpuCompiledResourcesFromGenrule(RULE_TYPE_PAIR); + } + + @Test + public void testMultiCpuGeneratedResourcesFromGenrule() throws Exception { + checkMultiCpuGeneratedResourcesFromGenrule(RULE_TYPE_PAIR); + } + + @Test + public void testTwoStringsOneBundlePath() throws Exception { + checkTwoStringsOneBundlePath(RULE_TYPE_PAIR, "x"); + } + + @Test + public void testTwoResourcesOneBundlePath() throws Exception { + checkTwoResourcesOneBundlePath(RULE_TYPE_PAIR, "x"); + } + + @Test + public void testSameStringsTwice() throws Exception { + checkSameStringsTwice(RULE_TYPE_PAIR, "x"); + } + + @Test + public void testExtensionReplacesMinimumOsInBundleMerge() throws Exception { + useConfiguration("--ios_minimum_os=7.1"); + addMockExtensionAndLibs("ext_infoplists = ['Info.plist']"); + + assertThat(bundleMergeControl("//x:x").getMinimumOsVersion()) + .isEqualTo(WatchUtils.MINIMUM_OS_VERSION.toString()); + } + + @Test + public void testExtensionReplacesMinimumOsVersionInBundleMergeAtMost82() throws Exception { + useConfiguration("--ios_minimum_os=8.3"); + addMockExtensionAndLibs("ext_infoplists = ['Info.plist']"); + + assertThat(bundleMergeControl("//x:x").getMinimumOsVersion()) + .isEqualTo("8.3"); + } + + @Test + public void testCheckExtensionPrimaryBundleIdInMergedPlist() throws Exception { + RULE_TYPE_PAIR.scratchTargets(scratch, + WATCH_EXT_INFOPLISTS_ATTR, "['Info.plist']", + WATCH_EXT_BUNDLE_ID_ATTR, "'com.ext.bundle.id'"); + scratch.file("ext/Info.plist"); + + checkBundleIdFlagsInPlistMergeAction( + Optional.of("com.ext.bundle.id"), + getVariableSubstitutionArguments(RULE_TYPE_PAIR), + "example.ext.x"); + } + + @Test + public void testCheckApplicationPrimaryBundleIdInMergedPlist() throws Exception { + setArtifactPrefix("y"); + RULE_TYPE_PAIR.scratchTargets(scratch, + WATCH_APP_INFOPLISTS_ATTR, "['Info.plist']", + WATCH_APP_BUNDLE_ID_ATTR, "'com.app.bundle.id'"); + scratch.file("app/Info.plist"); + + checkBundleIdFlagsInPlistMergeAction( + Optional.of("com.app.bundle.id"), + variableSubstitutionsForWatchApplication(), + "example.app.y"); + } + + @Test + public void testCheckExtensionFallbackBundleIdInMergedPlist() throws Exception { + RULE_TYPE_PAIR.scratchTargets(scratch, + WATCH_EXT_INFOPLISTS_ATTR, "['Info.plist']"); + scratch.file("ext/Info.plist"); + + checkBundleIdFlagsInPlistMergeAction( + Optional.<String>absent(), + getVariableSubstitutionArguments(RULE_TYPE_PAIR), + "example.ext.x"); + } + + @Test + public void testCheckApplicationFallbackBundleIdInMergedPlist() throws Exception { + setArtifactPrefix("y"); + RULE_TYPE_PAIR.scratchTargets(scratch, + WATCH_APP_INFOPLISTS_ATTR, "['Info.plist']"); + scratch.file("app/Info.plist"); + + checkBundleIdFlagsInPlistMergeAction( + Optional.<String>absent(), + variableSubstitutionsForWatchApplication(), + "example.app.y"); + } + + private ImmutableMap<String, String> variableSubstitutionsForWatchApplication() { + return new ImmutableMap.Builder<String, String>() + .put("EXECUTABLE_NAME", "y") + .put("BUNDLE_NAME", "y.app") + .put("PRODUCT_NAME", "y") + .build(); + } + + protected void checkExtensionReplacesMinimumOsInCompilation() throws Exception { + addMockExtensionAndLibs("ext_infoplists = ['Info.plist']"); + + Action lipoAction = lipoBinAction("//x:x"); + + for (Artifact bin : lipoAction.getInputs()) { + CommandAction action = (CommandAction) getGeneratingAction(bin); + if (action == null) { + continue; + } + assertThat(generatingArgumentsToString(action)) + .contains("-mios-simulator-version-min=" + WatchUtils.MINIMUM_OS_VERSION); + assertThat(generatingArgumentsToString(action)) + .doesNotContain("-mios-simulator-version-min=7.1"); + } + } + + private String generatingArgumentsToString(CommandAction generatingAction) { + return Joiner.on(' ').join(generatingAction.getArguments()); + } + + protected void checkExtensionDoesNotReplaceMinimumOsInCompilation() throws Exception { + addMockExtensionAndLibs("ext_infoplists = ['Info.plist']"); + + Action lipoAction = lipoBinAction("//x:x"); + + for (Artifact bin : lipoAction.getInputs()) { + CommandAction action = (CommandAction) getGeneratingAction(bin); + if (action == null) { + continue; + } + assertThat(generatingArgumentsToString(action)).contains("-mios-simulator-version-min=8.3"); + assertThat(generatingArgumentsToString(action)) + .doesNotContain("-mios-simulator-version-min=" + DEFAULT_IOS_SDK_VERSION); + } + } + + @Test + public void testGenruleWithoutJavaCcDeps() throws Exception { + checkGenruleWithoutJavaCcDependency(RULE_TYPE_PAIR); + } + + @Test + public void testCcDependencyWithProtoDependencyMultiArch() throws Exception { + checkCcDependencyWithProtoDependencyMultiArch( + RULE_TYPE_PAIR, ConfigurationDistinguisher.WATCH_OS1_EXTENSION); + } + + @Test + public void testAutomaticPlistEntries() throws Exception { + checkAutomaticPlistEntries(RULE_TYPE); + } + + @Test + public void testBundleMergeInputContainsPlMergeOutput() throws Exception { + checkBundleMergeInputContainsPlMergeOutput(RULE_TYPE); + } + + @Test + public void testMergeBundleActionsWithNestedBundle() throws Exception { + BuildConfiguration extensionConfiguration = + Iterables.getOnlyElement(getExtensionConfigurations()); + checkMergeBundleActionsWithNestedBundle(RULE_TYPE_PAIR, extensionConfiguration); + } + + @Test + public void testIncludesStoryboardOutputZipsAsMergeZipsForExtension() throws Exception { + BuildConfiguration configuration = Iterables.getOnlyElement(getExtensionConfigurations()); + addStoryboards(); + + Artifact extBndlsbOutputZip = + getBinArtifact( + "bndl/ext_bndlsb.storyboard.zip", getConfiguredTarget("//bndl:bndl", configuration)); + Artifact extsbOutputZip = getBinArtifact("x/ext.storyboard.zip", "//x:x"); + + String bundleDir = RULE_TYPE_PAIR.getBundleDir(); + Control mergeControl = bundleMergeControl("//x:x"); + assertThat(mergeControl.getMergeZipList()) + .containsExactly( + MergeZip.newBuilder() + .setEntryNamePrefix(bundleDir + "/") + .setSourcePath(extsbOutputZip.getExecPathString()) + .build(), + MergeZip.newBuilder() + .setEntryNamePrefix(bundleDir + "/") + .setSourcePath(getBinArtifact("_watch/x/y.zip", "//x:x").getExecPathString()) + .build()); + + Control nestedMergeControl = Iterables.getOnlyElement(mergeControl.getNestedBundleList()); + assertThat(nestedMergeControl.getMergeZipList()) + .containsExactly( + MergeZip.newBuilder() + .setEntryNamePrefix(bundleDir + "/bndl.bundle/") + .setSourcePath(extBndlsbOutputZip.getExecPathString()) + .build()); + } + + @Test + public void testCcDependency() throws Exception { + checkCcDependency(RULE_TYPE_PAIR, ConfigurationDistinguisher.WATCH_OS1_EXTENSION); + } + + @Test + public void testCcDependencyMultiArch() throws Exception { + checkCcDependencyMultiArch(RULE_TYPE_PAIR, ConfigurationDistinguisher.WATCH_OS1_EXTENSION); + } + + @Test + public void testCcDependencyWithProtoDependency() throws Exception { + checkCcDependencyWithProtoDependency( + RULE_TYPE_PAIR, ConfigurationDistinguisher.WATCH_OS1_EXTENSION); + } + + @Test + public void testCcDependencyAndJ2objcDependency() throws Exception { + checkCcDependencyAndJ2objcDependency( + RULE_TYPE_PAIR, ConfigurationDistinguisher.WATCH_OS1_EXTENSION); + } + + @Test + public void testMultiArchitectureFanOut() throws Exception { + checkBinaryLipoActionMultiCpu(RULE_TYPE_PAIR, ConfigurationDistinguisher.WATCH_OS1_EXTENSION); + } + + @Test + public void testGenruleDependencyMultiArch() throws Exception { + checkGenruleDependencyMultiArch(RULE_TYPE_PAIR, ConfigurationDistinguisher.WATCH_OS1_EXTENSION); + } + + @Test + public void testExtensionReplacesMinimumOsInCompilation() throws Exception { + useConfiguration("--ios_minimum_os=7.1"); + checkExtensionReplacesMinimumOsInCompilation(); + } + + @Test + public void testExtensionReplacesMinimumOsInCompilationAtMost82() throws Exception { + useConfiguration("--ios_minimum_os=8.3"); + checkExtensionDoesNotReplaceMinimumOsInCompilation(); + } + + @Test + public void testExtensionReplacesMinimumOsInCompilationMultiArch() throws Exception { + useConfiguration("--ios_minimum_os=7.1", "--ios_multi_cpus=i386,x86_64"); + checkExtensionReplacesMinimumOsInCompilation(); + } + + @Test + public void testExtensionReplacesMinimumOsInCompilationAtMost82MultiArch() throws Exception { + useConfiguration("--ios_minimum_os=8.3", "--ios_multi_cpus=i386,x86_64"); + checkExtensionDoesNotReplaceMinimumOsInCompilation(); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/AppleWatch2ExtensionTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleWatch2ExtensionTest.java new file mode 100644 index 0000000000..0e187eccdb --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleWatch2ExtensionTest.java @@ -0,0 +1,973 @@ +// 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 static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_BUNDLE_ID_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_ICON_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_INFOPLISTS_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_PROVISIONING_PROFILE_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_BUNDLE_ID_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_INFOPLISTS_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_PROVISIONING_PROFILE_ATTR; + +import com.google.common.base.Joiner; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.ObjectArrays; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Artifact; +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.SpawnAction; +import com.google.devtools.build.lib.rules.apple.DottedVersion; +import com.google.devtools.build.lib.testutil.Scratch; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.BundleFile; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.Control; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.MergeZip; +import com.google.devtools.build.xcode.plmerge.proto.PlMergeProtos; +import java.io.IOException; +import java.util.Map; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for apple_watch2_extension. */ +@RunWith(JUnit4.class) +public class AppleWatch2ExtensionTest extends ObjcRuleTestCase { + private static final RuleType RULE_TYPE = + new RuleType("apple_watch2_extension") { + @Override + Iterable<String> requiredAttributes( + Scratch scratch, String packageDir, Set<String> alreadyAdded) throws IOException { + ImmutableList.Builder<String> attributes = new ImmutableList.Builder<>(); + if (!alreadyAdded.contains("binary")) { + scratch.file(packageDir + "/extension_binary/a.m"); + scratch.file( + packageDir + "/extension_binary/BUILD", + "apple_binary(", + " name = 'extension_binary',", + " srcs = ['a.m'],", + " platform_type = 'watchos'", + ")"); + attributes.add(String.format("binary = '//%s/extension_binary'", packageDir)); + } + if (!alreadyAdded.contains("app_name")) { + attributes.add("app_name = 'y'"); + } + return attributes.build(); + } + }; + + protected static final BinaryRuleTypePair RULE_TYPE_PAIR = + new BinaryRuleTypePair( + AppleBinaryTest.RULE_TYPE, RULE_TYPE, ReleaseBundlingSupport.EXTENSION_BUNDLE_DIR_FORMAT); + + private ConfiguredTarget addMockExtensionAndLibs(String... extraExtAttributes) throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + + scratch.file("x/a.m"); + scratch.file( + "x/BUILD", + "apple_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " deps = ['//lib1:lib1', '//lib2:lib2'],", + " platform_type = 'watchos'", + ")", + "", + "apple_watch2_extension(", + " name = 'x',", + " app_name = 'y',", + " binary = ':bin',", + Joiner.on(',').join(extraExtAttributes), + ")"); + return getConfiguredTarget("//x:x"); + } + + private void addEntitlements() throws Exception { + scratch.file("x/ext_entitlements.entitlements"); + scratch.file("x/app_entitlements.entitlements"); + addMockExtensionAndLibs( + "ext_entitlements = 'ext_entitlements.entitlements'", + "app_entitlements = 'app_entitlements.entitlements'"); + } + + private Action watchApplicationIpaGeneratingAction() throws Exception { + return getGeneratingAction(getBinArtifact("y.ipa", "//x:x")); + } + + @Test + public void testExtensionSigningAction() throws Exception { + useConfiguration("--cpu=ios_armv7", "--watchos_cpus=armv7k"); + addEntitlements(); + SpawnAction action = (SpawnAction) ipaGeneratingAction(); + assertRequiresDarwin(action); + assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())) + .containsExactly("x.entitlements", "foo.mobileprovision", "x.unprocessed.ipa"); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())).containsExactly("x/x.ipa"); + } + + @Test + public void testApplicationSigningAction() throws Exception { + useConfiguration("--cpu=ios_armv7", "--watchos_cpus=armv7k"); + addEntitlements(); + SpawnAction action = (SpawnAction) watchApplicationIpaGeneratingAction(); + assertRequiresDarwin(action); + assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())) + .containsExactly("x-y.entitlements", "foo.mobileprovision", "x-y.unprocessed.ipa"); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())).containsExactly("x/y.ipa"); + } + + @Test + public void testExtensionSigningWithCertName() throws Exception { + useConfiguration("--cpu=ios_armv7", "--ios_signing_cert_name=Foo Bar", "--watchos_cpus=armv7k"); + addEntitlements(); + SpawnAction action = (SpawnAction) ipaGeneratingAction(); + assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())) + .containsExactly("x.entitlements", "foo.mobileprovision", "x.unprocessed.ipa"); + assertThat(Joiner.on(' ').join(action.getArguments())).contains("--sign \"Foo Bar\""); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())).containsExactly("x/x.ipa"); + } + + @Test + public void testApplicationSigningWithCertName() throws Exception { + useConfiguration("--cpu=ios_armv7", "--ios_signing_cert_name=Foo Bar", "--watchos_cpus=armv7k"); + addEntitlements(); + SpawnAction action = (SpawnAction) watchApplicationIpaGeneratingAction(); + assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())) + .containsExactly("x-y.entitlements", "foo.mobileprovision", "x-y.unprocessed.ipa"); + assertThat(Joiner.on(' ').join(action.getArguments())).contains("--sign \"Foo Bar\""); + + assertThat(Artifact.toRootRelativePaths(action.getOutputs())).containsExactly("x/y.ipa"); + } + + @Test + public void testSigning_simulatorBuild() throws Exception { + checkSigningSimulatorBuild(RULE_TYPE_PAIR, false); + } + + @Test + public void testSigning_simulatorBuild_multiCpu() throws Exception { + checkSigningSimulatorBuild(RULE_TYPE_PAIR, true); + } + + @Test + public void testProvisioningProfile_deviceBuild() throws Exception { + checkProvisioningProfileDeviceBuild(RULE_TYPE_PAIR, false); + } + + @Test + public void testProvisioningProfile_deviceBuild_multiCpu() throws Exception { + checkProvisioningProfileDeviceBuild(RULE_TYPE_PAIR, true); + } + + @Test + public void testUserSpecifiedExtensionProvisioningProfile_deviceBuild() throws Exception { + checkProvisioningProfileUserSpecified( + RULE_TYPE_PAIR, false, WATCH_EXT_PROVISIONING_PROFILE_ATTR); + } + + @Test + public void testUserSpecifiedApplicationProvisioningProfile_deviceBuild() throws Exception { + checkSpecifiedApplicationProvisioningProfile(false); + } + + @Test + public void testUserSpecifiedExtensionProvisioningProfile_deviceBuild_multiCpu() + throws Exception { + checkProvisioningProfileUserSpecified( + RULE_TYPE_PAIR, true, WATCH_EXT_PROVISIONING_PROFILE_ATTR); + } + + @Test + public void testUserSpecifiedApplicationProvisioningProfile_deviceBuild_multiCpu() + throws Exception { + checkSpecifiedApplicationProvisioningProfile(true); + } + + private void checkSpecifiedApplicationProvisioningProfile(boolean useMultiCpu) throws Exception { + setArtifactPrefix("y"); + if (useMultiCpu) { + useConfiguration("--ios_multi_cpus=armv7,arm64", "--cpu=ios_i386", "--watchos_cpus=armv7k"); + } else { + useConfiguration("--cpu=ios_armv7", "--watchos_cpus=armv7k"); + } + + addCustomProvisioningProfile(RULE_TYPE_PAIR, WATCH_APP_PROVISIONING_PROFILE_ATTR); + getConfiguredTarget("//x:x"); + + Artifact defaultProvisioningProfile = + getFileConfiguredTarget("//tools/objc:foo.mobileprovision").getArtifact(); + Artifact customProvisioningProfile = + getFileConfiguredTarget("//custom:pp.mobileprovision").getArtifact(); + Action signingAction = watchApplicationIpaGeneratingAction(); + assertThat(signingAction.getInputs()).contains(customProvisioningProfile); + assertThat(signingAction.getInputs()).doesNotContain(defaultProvisioningProfile); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + Map<String, String> profiles = mobileProvisionProfiles(control); + Map<String, String> expectedProfiles = + ImmutableMap.of( + customProvisioningProfile.getExecPathString(), + ReleaseBundlingSupport.PROVISIONING_PROFILE_BUNDLE_FILE); + assertThat(profiles).isEqualTo(expectedProfiles); + } + + @Test + public void testExtensionMergeControlAction() throws Exception { + addMockExtensionAndLibs("ext_infoplists = ['Info.plist']"); + useConfiguration("--watchos_sdk_version=2.2"); + Action mergeAction = bundleMergeAction("//x:x"); + Action action = bundleMergeControlAction("//x:x"); + assertThat(action.getInputs()).isEmpty(); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("x/x.ipa-control"); + assertThat(bundleMergeControl("//x:x")) + .isEqualTo( + BundleMergeProtos.Control.newBuilder() + .addBundleFile( + BundleFile.newBuilder() + .setSourceFile(execPathEndingWith(mergeAction.getInputs(), "bin_lipobin")) + .setBundlePath("x") + .setExternalFileAttribute(BundleableFile.EXECUTABLE_EXTERNAL_FILE_ATTRIBUTE) + .build()) + .setBundleRoot("PlugIns/x.appex") + .setBundleInfoPlistFile( + getMergedInfoPlist(getConfiguredTarget("//x:x")).getExecPathString()) + .setOutFile(execPathEndingWith(mergeAction.getOutputs(), "x.unprocessed.ipa")) + .setMinimumOsVersion("2.2") + .setSdkVersion(DEFAULT_IOS_SDK_VERSION.toString()) + .setPlatform("IOS_SIMULATOR") + .setFallbackBundleIdentifier("example.ext.x") + .build()); + } + + @Test + public void testApplicationMergeControlAction() throws Exception { + setArtifactPrefix("y"); + addMockExtensionAndLibs("app_infoplists = ['Info.plist']"); + useConfiguration("--watchos_sdk_version=2.2"); + Action mergeAction = bundleMergeAction("//x:x"); + Action action = bundleMergeControlAction("//x:x"); + assertThat(action.getInputs()).isEmpty(); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("x/x-y.ipa-control"); + assertThat(bundleMergeControl("//x:x")) + .isEqualTo( + BundleMergeProtos.Control.newBuilder() + .setBundleRoot("Watch/y.app") + .addMergeZip( + MergeZip.newBuilder() + .setEntryNamePrefix("Watch/y.app/") + .setSourcePath(getBinArtifact("x.ipa", "//x:x").getExecPathString()) + .build()) + .addMergeZip( + MergeZip.newBuilder() + .setEntryNamePrefix("Watch/y.app/") + .setSourcePath( + getBinArtifact("_watch/x/WatchKitStub.zip", "//x:x") + .getExecPathString()) + .build()) + .setBundleInfoPlistFile( + getMergedInfoPlist(getConfiguredTarget("//x:x")).getExecPathString()) + .setOutFile(execPathEndingWith(mergeAction.getOutputs(), "x-y.unprocessed.ipa")) + .setMinimumOsVersion("2.2") + .setSdkVersion(DEFAULT_IOS_SDK_VERSION.toString()) + .setPlatform("IOS_SIMULATOR") + .setFallbackBundleIdentifier("example.app.y") + .build()); + } + + @Test + public void testMergeExtensionBundleAction() throws Exception { + RULE_TYPE_PAIR.scratchTargets(scratch, "ext_infoplists", "['Info.plist']"); + SpawnAction action = bundleMergeAction("//x:x"); + assertThat(Artifact.toRootRelativePaths(action.getInputs())) + .containsExactly( + MOCK_BUNDLEMERGE_PATH, "x/bin_lipobin", "x/x.ipa-control", "x/x-MergedInfo.plist"); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("x/x.unprocessed.ipa"); + assertNotRequiresDarwin(action); + assertThat(action.getEnvironment()).isEmpty(); + assertThat(action.getArguments()) + .containsExactly( + MOCK_BUNDLEMERGE_PATH, execPathEndingWith(action.getInputs(), "x.ipa-control")) + .inOrder(); + } + + @Test + public void testMergeApplicationBundleAction() throws Exception { + RULE_TYPE_PAIR.scratchTargets(scratch, "app_infoplists", "['Info.plist']"); + setArtifactPrefix("y"); + SpawnAction action = bundleMergeAction("//x:x"); + assertThat(Artifact.toRootRelativePaths(action.getInputs())) + .containsExactly( + MOCK_BUNDLEMERGE_PATH, + "x/x-y.ipa-control", + "x/x-y-MergedInfo.plist", + "x/_watch/x/WatchKitStub.zip", + "x/x.ipa"); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("x/x-y.unprocessed.ipa"); + assertNotRequiresDarwin(action); + assertThat(action.getEnvironment()).isEmpty(); + assertThat(action.getArguments()) + .containsExactly( + MOCK_BUNDLEMERGE_PATH, execPathEndingWith(action.getInputs(), "x-y.ipa-control")) + .inOrder(); + } + + @Test + public void testErrorForAppIconGivenWithNoAssetCatalog() throws Exception { + checkAssetCatalogAttributeError( + RULE_TYPE, WATCH_APP_ICON_ATTR, WATCH_EXT_INFOPLISTS_ATTR, "['pl.plist']"); + } + + @Override + protected void checkCollectsAssetCatalogsTransitively(BinaryRuleTypePair ruleTypePair) + throws Exception { + scratch.file("lib/ac.xcassets/foo"); + scratch.file("lib/ac.xcassets/bar"); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .set("asset_catalogs", "glob(['ac.xcassets/**'])") + .write(); + + + scratch.file("x/ac.xcassets/baz"); + scratch.file("x/ac.xcassets/42"); + ruleTypePair.scratchTargets( + scratch, "deps", "['//lib:lib']", "app_asset_catalogs", "glob(['ac.xcassets/**'])"); + + // Test that the actoolzip Action for extension has arguments and inputs obtained from + // dependencies. + SpawnAction extensionActoolZipAction = actoolZipActionForIpa("//x:x"); + assertThat(Artifact.toExecPaths(extensionActoolZipAction.getInputs())) + .containsExactly("lib/ac.xcassets/foo", "lib/ac.xcassets/bar", MOCK_ACTOOLWRAPPER_PATH); + assertContainsSublist( + extensionActoolZipAction.getArguments(), ImmutableList.of("lib/ac.xcassets")); + + // Test that the actoolzip Action for application has arguments and inputs obtained from + // dependencies. + SpawnAction applicationActoolZipAction = + (SpawnAction) getGeneratingAction(getBinArtifact("x-y.actool.zip", "//x:x")); + assertThat(Artifact.toExecPaths(applicationActoolZipAction.getInputs())) + .containsExactly("x/ac.xcassets/baz", "x/ac.xcassets/42", MOCK_ACTOOLWRAPPER_PATH); + assertContainsSublist( + applicationActoolZipAction.getArguments(), ImmutableList.of("x/ac.xcassets")); + } + + @Test + public void testCollectsAssetCatalogsTransitively() throws Exception { + checkCollectsAssetCatalogsTransitively(RULE_TYPE_PAIR); + } + + private void addTargetWithAssetCatalogs() throws IOException { + scratch.file("x/foo.xcassets/foo"); + scratch.file("x/foo.xcassets/bar"); + scratch.file("x/a.m"); + scratch.file( + "x/BUILD", + "apple_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " platform_type = 'watchos',", + ")", + "", + "apple_watch2_extension(", + " name = 'x',", + " app_name = 'y',", + " app_asset_catalogs = ['foo.xcassets/foo', 'bar.xcassets/bar'],", + " binary = ':bin',", + ")"); + } + + @Test + public void testActoolActionCorrectness() throws Exception { + addTargetWithAssetCatalogs(); + setArtifactPrefix("y"); + useConfiguration("--watchos_sdk_version=2.2"); + checkActoolActionCorrectness( + DottedVersion.fromString("2.2"), TargetDeviceFamily.WATCH.getNameInRule().toLowerCase(), + "watchsimulator"); + } + + @Test + public void testMergeActionsWithAssetCatalog() throws Exception { + addTargetWithAssetCatalogs(); + setArtifactPrefix("y"); + Artifact actoolZipOut = getBinArtifact("x-y.actool.zip", "//x:x"); + assertThat(bundleMergeAction("//x:x").getInputs()).contains(actoolZipOut); + + BundleMergeProtos.Control mergeControl = bundleMergeControl("//x:x"); + assertThat(mergeControl.getMergeZipList()) + .containsAllOf( + MergeZip.newBuilder() + .setEntryNamePrefix("Watch/y.app/") + .setSourcePath(getBinArtifact("x.ipa", "//x:x").getExecPathString()) + .build(), + MergeZip.newBuilder() + .setEntryNamePrefix("Watch/y.app/") + .setSourcePath( + getBinArtifact("_watch/x/WatchKitStub.zip", "//x:x").getExecPathString()) + .build()); + } + + private void addBinAndLibWithRawResources() throws Exception { + addBinAndLibWithResources( + "resources", "resource1.txt", "ja.lproj/resource2.txt", "apple_binary", + "platform_type = 'watchos'"); + scratch.file("app_resource.txt"); + scratch.file("ext_resource.txt"); + scratch.file( + "x/BUILD", + "apple_watch2_extension(", + " name = 'x',", + " app_name = 'y',", + " binary = '//bin:bin',", + " app_resources = ['app_resource.txt'],", + " ext_resources = ['ext_resource.txt'],", + ")"); + } + + private void addBinAndLibWithStrings() throws Exception { + addBinAndLibWithResources("strings", "foo.strings", "ja.lproj/bar.strings", "apple_binary", + "platform_type = 'watchos'"); + scratch.file("app.strings"); + scratch.file( + "x/BUILD", + "apple_watch2_extension(", + " name = 'x',", + " app_name = 'y',", + " binary = '//bin:bin',", + " app_strings = ['app.strings'],", + ")"); + } + + @Test + public void testCollectsRawResourceFilesTransitively() throws Exception { + addBinAndLibWithRawResources(); + Action mergeBundleAction = bundleMergeAction("//x:x"); + + assertThat(Artifact.toRootRelativePaths(mergeBundleAction.getInputs())) + .containsAllOf("lib/resource1.txt", "bin/ja.lproj/resource2.txt"); + } + + @Test + public void testCollectsStringsFilesTransitively() throws Exception { + addBinAndLibWithStrings(); + + Action mergeBundleAction = bundleMergeAction("//x:x"); + + assertThat(Artifact.toRootRelativePaths(mergeBundleAction.getInputs())) + .containsAllOf("x/lib/foo.strings.binary", "x/bin/ja.lproj/bar.strings.binary"); + } + + @Test + public void testResourceFilesMergedInBundle() throws Exception { + addBinAndLibWithRawResources(); + checkBundleablesAreMerged( + "//x:x", + ImmutableListMultimap.of( + "resource1.txt", "resource1.txt", + "ja.lproj/resource2.txt", "ja.lproj/resource2.txt")); + } + + @Test + public void testStringsFilesMergedInBundle() throws Exception { + addBinAndLibWithStrings(); + checkBundleablesAreMerged( + "//x:x", + ImmutableListMultimap.of( + "foo.strings.binary", "foo.strings", + "ja.lproj/bar.strings.binary", "ja.lproj/bar.strings")); + } + + @Test + public void testPlistRequiresDotInName() throws Exception { + String errorMessage = + "'//x:Infoplist' does not produce any apple_watch2_extension " + + "ext_infoplists files (expected .plist)"; + checkError( + "x", + "x", + errorMessage, + "apple_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " platform_type = 'watchos'", + ")", + "", + "apple_watch2_extension(", + " name = 'x',", + " app_name = 'y',", + " ext_infoplists = ['Infoplist'],", + " binary = ':bin',", + ")"); + } + + @Test + public void testIncludesStoryboardOutputZipsAsMergeZipsForApplication() throws Exception { + addStoryboards(); + setArtifactPrefix("y"); + Artifact libsbOutputZip = getBinArtifact("x-y/appsb.storyboard.zip", "//x:x"); + + Control mergeControl = bundleMergeControl("//x:x"); + assertThat(mergeControl.getMergeZipList()) + .contains( + MergeZip.newBuilder() + .setEntryNamePrefix("Watch/y.app/") + .setSourcePath(libsbOutputZip.getExecPathString()) + .build()); + } + + protected void addStoryboards() throws Exception { + scratch.file("lib/libsb.storyboard"); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("storyboards", "libsb.storyboard") + .write(); + + scratch.file("bndl/bndlsb.storyboard"); + scratch.file( + "bndl/BUILD", + "objc_bundle_library(", + " name = 'bndl',", + " storyboards = ['ext_bndlsb.storyboard'],", + ")"); + + scratch.file( + "x/BUILD", + "apple_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " bundles = ['//bndl:bndl'],", + " deps = ['//lib:lib'],", + " storyboards = ['ext.storyboard'],", + " platform_type = 'watchos',", + ")", + "", + "apple_watch2_extension(", + " name = 'x',", + " app_name = 'y',", + " app_asset_catalogs = ['foo.xcassets/foo', 'bar.xcassets/bar'],", + " app_storyboards = ['appsb.storyboard'],", + " binary = ':bin',", + ")"); + + scratch.file("x/appsb.storyboard"); + getConfiguredTarget("//x:x"); + } + + @Test + public void testMergesPartialInfoplists() throws Exception { + scratch.file("x/primary-Info.plist"); + setArtifactPrefix("y"); + RULE_TYPE_PAIR.scratchTargets( + scratch, + "app_asset_catalogs", + "['foo.xcassets/bar']", + "app_infoplists", + "['primary-Info.plist']"); + + String targetName = "//x:x"; + ConfiguredTarget target = getConfiguredTarget(targetName); + PlMergeProtos.Control control = plMergeControl(targetName); + + Artifact merged = getBinArtifact("x-y-MergedInfo.plist", target); + Artifact actoolPartial = getBinArtifact("x-y.actool-PartialInfo.plist", "//x:x"); + + Artifact versionInfoplist = getBinArtifact("plists/x-y-version.plist", target); + Artifact environmentInfoplist = getBinArtifact("plists/x-y-environment.plist", target); + Artifact automaticInfoplist = getBinArtifact("plists/x-y-automatic.plist", target); + + assertPlistMergeControlUsesSourceFiles( + control, + ImmutableList.<String>of( + "x/primary-Info.plist", + versionInfoplist.getExecPathString(), + environmentInfoplist.getExecPathString(), + automaticInfoplist.getExecPathString(), + actoolPartial.getExecPathString())); + assertThat(control.getOutFile()).isEqualTo(merged.getExecPathString()); + assertThat(control.getVariableSubstitutionMapMap()) + .containsExactlyEntriesIn(variableSubstitutionsForWatchApplication()); + assertThat(control.getFallbackBundleId()).isEqualTo("example.app.y"); + } + + @Test + public void testNibZipsMergedIntoBundle() throws Exception { + checkNibZipsMergedIntoBundle(RULE_TYPE_PAIR); + } + + @Test + public void testPassesExtensionFallbackBundleIdToBundleMerging() throws Exception { + scratch.file("bin/a.m"); + scratch.file("bin/Ext-Info.plist"); + + RULE_TYPE.scratchTarget(scratch, WATCH_EXT_INFOPLISTS_ATTR, "['Ext-Info.plist']"); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + + assertThat(control.hasPrimaryBundleIdentifier()).isFalse(); + assertThat(control.getFallbackBundleIdentifier()).isEqualTo("example.ext.x"); + } + + @Test + public void testPassesApplicationFallbackBundleIdToBundleMerging() throws Exception { + setArtifactPrefix("y"); + scratch.file("bin/a.m"); + scratch.file("bin/App-Info.plist"); + + RULE_TYPE.scratchTarget(scratch, WATCH_APP_INFOPLISTS_ATTR, "['App-Info.plist']"); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + + assertThat(control.hasPrimaryBundleIdentifier()).isFalse(); + assertThat(control.getFallbackBundleIdentifier()).isEqualTo("example.app.y"); + } + + @Test + public void testPassesExtensionPrimaryBundleIdToBundleMerging() throws Exception { + scratch.file("bin/a.m"); + scratch.file("bin/Ext-Info.plist"); + + RULE_TYPE.scratchTarget( + scratch, + WATCH_EXT_INFOPLISTS_ATTR, + "['Ext-Info.plist']", + WATCH_EXT_BUNDLE_ID_ATTR, + "'com.bundle.ext.id'"); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + + assertThat(control.getPrimaryBundleIdentifier()).isEqualTo("com.bundle.ext.id"); + assertThat(control.hasFallbackBundleIdentifier()).isFalse(); + } + + @Test + public void testPassesApplicationPrimaryBundleIdToBundleMerging() throws Exception { + setArtifactPrefix("y"); + scratch.file("bin/a.m"); + scratch.file("bin/App-Info.plist"); + + RULE_TYPE.scratchTarget( + scratch, + WATCH_APP_INFOPLISTS_ATTR, + "['App-Info.plist']", + WATCH_APP_BUNDLE_ID_ATTR, + "'com.bundle.app.id'"); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + + assertThat(control.getPrimaryBundleIdentifier()).isEqualTo("com.bundle.app.id"); + assertThat(control.hasFallbackBundleIdentifier()).isFalse(); + } + + @Test + public void testMultiPlatformBuild_fails() throws Exception { + checkBinaryActionMultiPlatform_fails(RULE_TYPE_PAIR); + } + + @Test + public void testMultiArchitectureResources() throws Exception { + useConfiguration("--ios_multi_cpus=i386,x86_64"); + RULE_TYPE_PAIR.scratchTargets(scratch, "resources", "['foo.png']"); + + assertThat(Artifact.toRootRelativePaths(bundleMergeAction("//x:x").getInputs())) + .containsExactly( + "x/foo.png", + "x/bin_lipobin", + "tools/objc/bundlemerge", + "x/x.ipa-control", + "x/x-MergedInfo.plist"); + } + + @Test + public void testDeviceSimulatorMismatch() throws Exception { + useConfiguration("--ios_multi_cpus=i386,x86_64", "--watchos_cpus=armv7k"); + checkError( + "x", + "x", + "Building a watch extension for watch device architectures [armv7k] " + + "requires a device ios architecture. Found [i386,x86_64] instead.", + "apple_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " platform_type = 'watchos',", + ")", + "", + "apple_watch2_extension(", + " name = 'x',", + " app_name = 'y',", + " binary = ':bin',", + ")"); + } + + @Override + protected void addCommonResources(BinaryRuleTypePair ruleTypePair) throws Exception { + ruleTypePair.scratchTargets( + scratch, "strings", "['foo.strings']", "storyboards", "['baz.storyboard']"); + } + + @Test + public void testMultiCpuCompiledResources() throws Exception { + checkMultiCpuCompiledResources(RULE_TYPE_PAIR); + } + + @Test + public void testConvertStringsActions() throws Exception { + checkConvertStringsAction(RULE_TYPE_PAIR); + } + + @Test + public void testCompileXibActions() throws Exception { + useConfiguration("--watchos_sdk_version=2.2"); + checkCompileXibActions(RULE_TYPE_PAIR, DottedVersion.fromString("2.2"), "watch"); + } + + @Test + public void testRegistersStoryboardCompileActions() throws Exception { + useConfiguration("--watchos_sdk_version=2.2"); + checkRegistersStoryboardCompileActions(RULE_TYPE_PAIR, DottedVersion.fromString("2.2"), + "watch"); + } + + @Test + public void testMultiCpuCompiledResourcesFromGenrule() throws Exception { + checkMultiCpuCompiledResourcesFromGenrule(RULE_TYPE_PAIR); + } + + @Test + public void testMultiCpuGeneratedResourcesFromGenrule() throws Exception { + checkMultiCpuGeneratedResourcesFromGenrule(RULE_TYPE_PAIR); + } + + @Test + public void testTwoStringsOneBundlePath() throws Exception { + checkTwoStringsOneBundlePath(RULE_TYPE_PAIR, "x"); + } + + @Test + public void testTwoResourcesOneBundlePath() throws Exception { + checkTwoResourcesOneBundlePath(RULE_TYPE_PAIR, "x"); + } + + @Test + public void testWatchSdkDefaultMinVersion() throws Exception { + useConfiguration("--ios_minimum_os=7.1", "--watchos_sdk_version=2.4"); + addMockExtensionAndLibs("ext_infoplists = ['Info.plist']"); + + assertThat(bundleMergeControl("//x:x").getMinimumOsVersion()).isEqualTo("2.4"); + } + + @Test + public void testWatchSdkMinimumOs() throws Exception { + useConfiguration("--ios_minimum_os=7.1", "--watchos_sdk_version=2.2", + "--watchos_minimum_os=2.0"); + addMockExtensionAndLibs("ext_infoplists = ['Info.plist']"); + + assertThat(bundleMergeControl("//x:x").getMinimumOsVersion()).isEqualTo("2.0"); + } + + @Test + public void testCheckExtensionPrimaryBundleIdInMergedPlist() throws Exception { + RULE_TYPE_PAIR.scratchTargets( + scratch, + WATCH_EXT_INFOPLISTS_ATTR, + "['Info.plist']", + WATCH_EXT_BUNDLE_ID_ATTR, + "'com.ext.bundle.id'"); + scratch.file("ext/Info.plist"); + + checkBundleIdFlagsInPlistMergeAction( + Optional.of("com.ext.bundle.id"), + getVariableSubstitutionArguments(RULE_TYPE_PAIR), + "example.ext.x"); + } + + @Test + public void testCheckApplicationPrimaryBundleIdInMergedPlist() throws Exception { + setArtifactPrefix("y"); + RULE_TYPE_PAIR.scratchTargets( + scratch, + WATCH_APP_INFOPLISTS_ATTR, + "['Info.plist']", + WATCH_APP_BUNDLE_ID_ATTR, + "'com.app.bundle.id'"); + scratch.file("app/Info.plist"); + + checkBundleIdFlagsInPlistMergeAction( + Optional.of("com.app.bundle.id"), + variableSubstitutionsForWatchApplication(), + "example.app.y"); + } + + @Test + public void testCheckExtensionFallbackBundleIdInMergedPlist() throws Exception { + RULE_TYPE_PAIR.scratchTargets(scratch, WATCH_EXT_INFOPLISTS_ATTR, "['Info.plist']"); + scratch.file("ext/Info.plist"); + + checkBundleIdFlagsInPlistMergeAction( + Optional.<String>absent(), + getVariableSubstitutionArguments(RULE_TYPE_PAIR), + "example.ext.x"); + } + + @Test + public void testCheckApplicationFallbackBundleIdInMergedPlist() throws Exception { + setArtifactPrefix("y"); + RULE_TYPE_PAIR.scratchTargets(scratch, WATCH_APP_INFOPLISTS_ATTR, "['Info.plist']"); + scratch.file("app/Info.plist"); + + checkBundleIdFlagsInPlistMergeAction( + Optional.<String>absent(), variableSubstitutionsForWatchApplication(), "example.app.y"); + } + + @Test + public void testSameStringsTwice() throws Exception { + String targets = + RULE_TYPE.target( + scratch, + "x", + "bndl", + "app_resources", + "['Resources/en.lproj/foo.strings']", + "app_strings", + "['Resources/en.lproj/foo.strings']"); + checkError( + "x", + "bndl", + "The same file was included multiple times in this rule: x/Resources/en.lproj/foo.strings", + targets); + } + + private ImmutableMap<String, String> variableSubstitutionsForWatchApplication() { + return new ImmutableMap.Builder<String, String>() + .put("EXECUTABLE_NAME", "y") + .put("BUNDLE_NAME", "y.app") + .put("PRODUCT_NAME", "y") + .build(); + } + + protected void createSwiftBinaryTarget(String... lines) throws Exception { + scratch.file("x/main.m"); + + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def swift_rule_impl(ctx):", + " return struct(objc=apple_common.new_objc_provider(uses_swift=True))", + "swift_rule = rule(implementation = swift_rule_impl, attrs = {})"); + + String[] impl = + ObjectArrays.concat( + new String[] { + "load('//examples/rule:apple_rules.bzl', 'swift_rule')", + "swift_rule(name='swift_bin')", + "apple_binary(", + " name = 'x',", + " srcs = ['main.m'],", + " deps = [':swift_bin'],", + " platform_type = 'watchos',", + ")", + "", + }, + lines, + String.class); + scratch.file("x/BUILD", impl); + } + + @Test + public void testAutomaticPlistEntries() throws Exception { + checkAutomaticPlistEntries(RULE_TYPE); + } + + @Test + public void testBundleMergeInputContainsPlMergeOutput() throws Exception { + checkBundleMergeInputContainsPlMergeOutput(RULE_TYPE); + } + + @Test + // Regression test for b/30916137. Verifies that all tools are available in the watch2 extension + // rule to handle bundling of swift objects. + public void testSwiftSrcs() throws Exception { + createSwiftBinaryTarget( + "apple_watch2_extension(", + " name = 'ext',", + " app_name = 'y',", + " app_asset_catalogs = ['foo.xcassets/foo', 'bar.xcassets/bar'],", + " binary = ':x',", + ")"); + + getConfiguredTarget("//x:x"); + } + + @Override + protected Action ipaGeneratingAction() throws Exception { + ConfiguredTarget test = getConfiguredTarget("//x:x"); + return getGeneratingAction(getBinArtifact("x.ipa", test)); + } + + @Test + public void testMergeBundleActionsWithNestedBundle() throws Exception { + checkMergeBundleActionsWithNestedBundle(RULE_TYPE_PAIR, getTargetConfiguration()); + } + + @Test + public void testIncludesStoryboardOutputZipsAsMergeZipsForExtension() throws Exception { + addStoryboards(); + + Artifact libsbOutputZip = getBinArtifact("x/libsb.storyboard.zip", "//x:x"); + Artifact extBndlsbOutputZip = + getBinArtifact("bndl/ext_bndlsb.storyboard.zip", getConfiguredTarget("//bndl:bndl")); + Artifact extsbOutputZip = getBinArtifact("x/ext.storyboard.zip", "//x:x"); + + String bundleDir = RULE_TYPE_PAIR.getBundleDir(); + Control mergeControl = bundleMergeControl("//x:x"); + assertThat(mergeControl.getMergeZipList()) + .containsExactly( + MergeZip.newBuilder() + .setEntryNamePrefix(bundleDir + "/") + .setSourcePath(libsbOutputZip.getExecPathString()) + .build(), + MergeZip.newBuilder() + .setEntryNamePrefix(bundleDir + "/") + .setSourcePath(extsbOutputZip.getExecPathString()) + .build()); + + Control nestedMergeControl = Iterables.getOnlyElement(mergeControl.getNestedBundleList()); + assertThat(nestedMergeControl.getMergeZipList()) + .containsExactly( + MergeZip.newBuilder() + .setEntryNamePrefix(bundleDir + "/bndl.bundle/") + .setSourcePath(extBndlsbOutputZip.getExecPathString()) + .build()); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/AppleWatchExtensionBinaryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleWatchExtensionBinaryTest.java new file mode 100644 index 0000000000..d317b82658 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/AppleWatchExtensionBinaryTest.java @@ -0,0 +1,171 @@ +// 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 static com.google.devtools.build.lib.rules.objc.BinaryLinkingTargetFactory.REQUIRES_AT_LEAST_ONE_LIBRARY_OR_SOURCE_FILE; + +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.RunfilesProvider; +import com.google.devtools.build.lib.analysis.config.CompilationMode; +import com.google.devtools.build.lib.rules.objc.CompilationSupport.ExtraLinkArgs; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for apple_watch_extension_binary. */ +@RunWith(JUnit4.class) +public class AppleWatchExtensionBinaryTest extends ObjcRuleTestCase { + static final RuleType RULE_TYPE = new OnlyNeedsSourcesRuleType("apple_watch_extension_binary"); + protected static final ExtraLinkArgs EXTRA_LINK_ARGS = + new ExtraLinkArgs("-fapplication-extension", "-framework", "WatchKit"); + + @Test + public void testCreate_runfiles() throws Exception { + scratch.file("x/a.m"); + RULE_TYPE.scratchTarget(scratch, "srcs", "['a.m']"); + ConfiguredTarget binary = getConfiguredTarget("//x:x"); + RunfilesProvider runfiles = binary.getProvider(RunfilesProvider.class); + assertThat(runfiles.getDefaultRunfiles().getArtifacts()).isEmpty(); + assertThat(Artifact.toRootRelativePaths(runfiles.getDataRunfiles().getArtifacts())) + .containsExactly("x/x_bin"); + } + + @Test + public void testCreate_errorForNoSourceOrDep() throws Exception { + checkError("x", "x", REQUIRES_AT_LEAST_ONE_LIBRARY_OR_SOURCE_FILE, + "apple_watch_extension_binary(name='x')"); + } + + @Test + public void testCompileWithDotMFileInHeaders() throws Exception { + checkCompileWithDotMFileInHeaders(RULE_TYPE); + } + + @Test + public void testLinksFrameworksOfSelfAndTransitiveDependencies() throws Exception { + checkLinksFrameworksOfSelfAndTransitiveDependencies(RULE_TYPE); + } + + @Test + public void testLinksWeakFrameworksOfSelfAndTransitiveDependencies() throws Exception { + checkLinksWeakFrameworksOfSelfAndTransitiveDependencies(RULE_TYPE); + } + + @Test + public void testLinksDylibsTransitively() throws Exception { + checkLinksDylibsTransitively(RULE_TYPE); + } + + @Test + public void testPopulatesCompilationArtifacts() throws Exception { + checkPopulatesCompilationArtifacts(RULE_TYPE); + } + + @Test + public void testErrorsWrongFileTypeForSrcsWhenCompiling() throws Exception { + checkErrorsWrongFileTypeForSrcsWhenCompiling(RULE_TYPE); + } + + @Test + public void testObjcCopts() throws Exception { + checkObjcCopts(RULE_TYPE); + } + + @Test + public void testObjcCopts_argumentOrdering() throws Exception { + checkObjcCopts_argumentOrdering(RULE_TYPE); + } + + @Test + public void testAllowVariousNonBlacklistedTypesInHeaders() throws Exception { + checkAllowVariousNonBlacklistedTypesInHeaders(RULE_TYPE); + } + + @Test + public void testWarningForBlacklistedTypesInHeaders() throws Exception { + checkWarningForBlacklistedTypesInHeaders(RULE_TYPE); + } + + @Test + public void testCppSourceCompilesWithCppFlags() throws Exception { + checkCppSourceCompilesWithCppFlags(RULE_TYPE); + } + + @Test + public void testProtoBundlingAndLinking() throws Exception { + checkProtoBundlingAndLinking(RULE_TYPE); + } + + @Test + public void testProtoBundlingWithTargetsWithNoDeps() throws Exception { + checkProtoBundlingWithTargetsWithNoDeps(RULE_TYPE); + } + + @Test + public void testLinkingRuleCanUseCrosstool() throws Exception { + checkLinkingRuleCanUseCrosstool(RULE_TYPE); + } + + @Test + public void testBinaryStrippings() throws Exception { + checkBinaryStripAction(RULE_TYPE); + } + + @Test + public void testCompilationActionsForDebug() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.DBG, CodeCoverageMode.NONE); + } + + @Test + public void testCompilationActionsForOptimized() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.OPT, CodeCoverageMode.NONE); + } + + @Test + public void testClangCoptsForDebugModeWithoutGlib() throws Exception { + checkClangCoptsForDebugModeWithoutGlib(RULE_TYPE); + } + + @Test + public void testLinkActionCorrect() throws Exception { + checkLinkActionCorrect(RULE_TYPE, EXTRA_LINK_ARGS); + } + + @Test + public void testCompilationActionsForDebugInGcovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.DBG, + CodeCoverageMode.GCOV); + } + + @Test + public void testCompilationActionsForOptimizedInGcovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.OPT, + CodeCoverageMode.GCOV); + } + + @Test + public void testCompilationActionsForDebugInLlvmCovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.DBG, + CodeCoverageMode.LLVMCOV); + } + + @Test + public void testCompilationActionsForOptimizedInLlvmCovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.OPT, + CodeCoverageMode.LLVMCOV); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/BinaryRuleType.java b/src/test/java/com/google/devtools/build/lib/rules/objc/BinaryRuleType.java new file mode 100644 index 0000000000..50e1608a13 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/BinaryRuleType.java @@ -0,0 +1,44 @@ +// 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.devtools.build.lib.testutil.Scratch; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * This exists for convenience for any rule type requires the exact same attributes as + * {@code objc_binary}. If some rule ever changes to require more or fewer attributes, it is OK + * to stop using this class. + */ +final class BinaryRuleType extends RuleType { + BinaryRuleType(String ruleTypeName) { + super(ruleTypeName); + } + + @Override + Iterable<String> requiredAttributes( + Scratch scratch, String packageName, Set<String> alreadyAdded) throws IOException { + List<String> attributes = new ArrayList<>(); + if (!alreadyAdded.contains("srcs") && !alreadyAdded.contains("non_arc_srcs")) { + scratch.file(packageName + "/a.m"); + scratch.file(packageName + "/private.h"); + attributes.add("srcs = ['a.m', 'private.h']"); + } + return attributes; + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/BinaryRuleTypePair.java b/src/test/java/com/google/devtools/build/lib/rules/objc/BinaryRuleTypePair.java new file mode 100644 index 0000000000..57eeef63ef --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/BinaryRuleTypePair.java @@ -0,0 +1,163 @@ +// 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.devtools.build.lib.rules.objc.AppleWatch1ExtensionRule.WATCH_APP_DEPS_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.BundlingRule.FAMILIES_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.BundlingRule.INFOPLIST_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.APP_ICON_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.BUNDLE_ID_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.ENTITLEMENTS_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.LAUNCH_IMAGE_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.LAUNCH_STORYBOARD_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.PROVISIONING_PROFILE_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_ASSET_CATALOGS_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_BUNDLE_ID_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_INFOPLISTS_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_PROVISIONING_PROFILE_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_STORYBOARDS_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_BUNDLE_ID_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_INFOPLISTS_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_PROVISIONING_PROFILE_ATTR; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.testutil.Scratch; +import com.google.devtools.build.lib.util.Preconditions; +import java.io.IOException; +import java.util.Arrays; + +/** + * Represents a pair of rule types, one being a binary and one an bundling type. + * + * <p>TODO(bazel-team): Remove this class and refactor tests using it when the binary rule types and + * bundling rule types are merged. + */ +final class BinaryRuleTypePair { + private final RuleType binaryType; + private final RuleType bundlingType; + private final String bundleDirFormat; + private final String bundleName; + + /** + * Creates a rule pair. + * + * @param binaryType rule type for the binary half of the pair (e.g. ios_extension_binary) + * @param bundlingType rule type for wrapper (e.g. ios_extension) + * @param bundleDirFormat path format for location inside of bundle (e.g. Plugins/%s.appex), + * will be substituted with bundle name + * @param bundleName name of the bundle + */ + BinaryRuleTypePair(RuleType binaryType, RuleType bundlingType, String bundleDirFormat, + String bundleName) { + this.binaryType = binaryType; + this.bundlingType = bundlingType; + this.bundleDirFormat = bundleDirFormat; + this.bundleName = bundleName; + } + + /** + * Creates a binary rule pair with bundle name of "x". + */ + BinaryRuleTypePair(RuleType binaryType, RuleType bundlingType, String bundleDirFormat) { + this(binaryType, bundlingType, bundleDirFormat, "x"); + } + + /** + * Returns the name of this bundle. + */ + String getBundleName() { + return bundleName; + } + + /** + * Returns a bundle dir path by combining {@code bundleDirFormat} and {@code bundleName}. + */ + String getBundleDir() { + return String.format(bundleDirFormat, bundleName); + } + + /** + * Returns the binary type as it appears in {@code BUILD} files, such as {@code objc_binary}. + */ + RuleType getBinaryRuleType() { + return binaryType; + } + + /** + * Returns the bundling type as it appears in {@code BUILD} files, such as + * {@code ios_application}. + */ + RuleType getBundlingRuleType() { + return bundlingType; + } + + /** + * Generates the String necessary to define a bundling and binary rule of these types. + * This includes an "x" (bundling) and "bin" (binary) target in the given package, setting binary + * attributes in {@code checkSpecificAttrs} on the binary target, and all other attributes on the + * bundling target. + */ + String targets(Scratch scratch, String packageName, String... checkSpecificAttrs) + throws IOException { + Preconditions.checkArgument(checkSpecificAttrs.length % 2 == 0, + "An even number of attribute parameters (kev and value pairs) is required but got: %s", + Arrays.asList(checkSpecificAttrs)); + + ImmutableList.Builder<String> binaryAttributes = new ImmutableList.Builder<>(); + ImmutableList.Builder<String> bundlingAttributes = new ImmutableList.Builder<>(); + bundlingAttributes.add("binary", "':bin'"); + + for (int i = 0; i < checkSpecificAttrs.length; i += 2) { + String attributeName = checkSpecificAttrs[i]; + String value = checkSpecificAttrs[i + 1]; + switch (attributeName) { + case APP_ICON_ATTR: + case LAUNCH_IMAGE_ATTR: + case LAUNCH_STORYBOARD_ATTR: + case BUNDLE_ID_ATTR: + case FAMILIES_ATTR: + case PROVISIONING_PROFILE_ATTR: + case ENTITLEMENTS_ATTR: + case INFOPLIST_ATTR: + case AppleWatch1ExtensionRule.WATCH_EXT_FAMILIES_ATTR: + case WATCH_EXT_INFOPLISTS_ATTR: + case WATCH_APP_INFOPLISTS_ATTR: + case WATCH_APP_PROVISIONING_PROFILE_ATTR: + case WATCH_EXT_PROVISIONING_PROFILE_ATTR: + case WATCH_EXT_BUNDLE_ID_ATTR: + case WATCH_APP_BUNDLE_ID_ATTR: + case WATCH_APP_STORYBOARDS_ATTR: + case WATCH_APP_ASSET_CATALOGS_ATTR: + case WATCH_APP_DEPS_ATTR: + bundlingAttributes.add(attributeName, value); + break; + default: + binaryAttributes.add(attributeName, value); + } + } + return binaryType.target(scratch, packageName, "bin", + binaryAttributes.build().toArray(new String[0])) + + "\n" + + bundlingType.target(scratch, packageName, "x", + bundlingAttributes.build().toArray(new String[0])); + } + + /** + * Creates targets at //x:x and //x:bin which are the only targets in the BUILD file. + */ + void scratchTargets(Scratch scratch, String... checkSpecificAttrs) throws IOException { + scratch.file("x/BUILD", targets(scratch, "x", checkSpecificAttrs)); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ExperimentalObjcBinaryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ExperimentalObjcBinaryTest.java new file mode 100644 index 0000000000..c40d547be3 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ExperimentalObjcBinaryTest.java @@ -0,0 +1,301 @@ +// 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 static com.google.devtools.build.lib.rules.objc.LegacyCompilationSupport.AUTOMATIC_SDK_FRAMEWORKS; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.util.ScratchAttributeWriter; +import com.google.devtools.build.lib.packages.util.MockObjcSupport; +import com.google.devtools.build.lib.rules.apple.AppleToolchain; +import com.google.devtools.build.lib.rules.apple.Platform; +import com.google.devtools.build.lib.rules.cpp.CppCompileAction; +import com.google.devtools.build.lib.rules.cpp.CppLinkAction; +import com.google.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for linking targets with the OSX crosstool. */ +@RunWith(JUnit4.class) +public class ExperimentalObjcBinaryTest extends ObjcRuleTestCase { + static final RuleType RULE_TYPE = new BinaryRuleType("objc_binary"); + + private static final String WRAPPED_CLANG = "wrapped_clang"; + private static final String WRAPPED_CLANGPLUSPLUS = "wrapped_clang++"; + + private ConfiguredTarget addMockBinAndLibs(List<String> srcs) throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + + return createBinaryTargetWriter("//bin:bin") + .setAndCreateFiles("srcs", srcs) + .setList("deps", "//lib1:lib1", "//lib2:lib2") + .write(); + } + + private ImmutableList<String> automaticSdkFrameworks() { + ImmutableList.Builder<String> result = ImmutableList.<String>builder(); + for (SdkFramework framework : AUTOMATIC_SDK_FRAMEWORKS) { + result.add("-framework " + framework.getName()); + } + return result.build(); + } + + @Test + public void testDeviceBuild() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all", + "--experimental_disable_go", + "--cpu=ios_armv7", + "--ios_cpu=armv7"); + Platform platform = Platform.IOS_DEVICE; + + addMockBinAndLibs(ImmutableList.of("a.m")); + CommandAction action = linkAction("//bin:bin"); + assertRequiresDarwin(action); + assertThat(Artifact.toRootRelativePaths(action.getInputs())) + .containsExactly( + "bin/libbin.a", + "lib1/liblib1.a", + "lib2/liblib2.a", + "bin/bin-linker.objlist"); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())).containsExactly("bin/bin_bin"); + verifyObjlist( + action, + "bin-linker.objlist", + execPathEndingWith(action.getInputs(), "libbin.a"), + execPathEndingWith(action.getInputs(), "liblib1.a"), + execPathEndingWith(action.getInputs(), "liblib2.a")); + assertThat(action.getArguments()) + .containsExactlyElementsIn( + new ImmutableList.Builder<String>() + .add("tools/osx/crosstool/ios/" + WRAPPED_CLANG) + .add("-F" + AppleToolchain.sdkDir() + AppleToolchain.DEVELOPER_FRAMEWORK_PATH) + .add("-F" + frameworkDir(platform)) + .add("-isysroot") + .add(AppleToolchain.sdkDir()) + // TODO(b/35853671): Factor out "-lc++" + .add("-lc++") + .add("-target", "armv7-apple-ios") + .add("-miphoneos-version-min=" + DEFAULT_IOS_SDK_VERSION) + .addAll(automaticSdkFrameworks()) + .add("-arch armv7") + .add("-Xlinker", "-objc_abi_version", "-Xlinker", "2") + .add("-Xlinker", "-rpath", "-Xlinker", "@executable_path/Frameworks") + .add("-fobjc-link-runtime") + .add("-ObjC") + .add("-filelist " + execPathEndingWith(action.getInputs(), "bin-linker.objlist")) + .add("-o " + Iterables.getOnlyElement(Artifact.toExecPaths(action.getOutputs()))) + .build()) + .inOrder(); + } + + @Test + public void testSimulatorBuild() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all", + "--experimental_disable_go", + "--cpu=ios_x86_64", + "--ios_cpu=x86_64"); + Platform platform = Platform.IOS_SIMULATOR; + + addMockBinAndLibs(ImmutableList.of("a.m")); + CommandAction action = linkAction("//bin:bin"); + assertThat(action.getArguments()) + .containsAllOf( + "tools/osx/crosstool/iossim/" + WRAPPED_CLANG, + "-arch x86_64", + "-mios-simulator-version-min=" + DEFAULT_IOS_SDK_VERSION, + "-F" + frameworkDir(platform)); + } + + @Test + public void testAlwaysLinkCcDependenciesAreForceLoaded() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all", + "--experimental_disable_go", + "--cpu=ios_armv7", + "--ios_cpu=armv7"); + + scratch.file( + "bin/BUILD", + "cc_library(", + " name = 'cclib1',", + " srcs = ['dep1.c'],", + " alwayslink = 1,", + ")", + "cc_library(", + " name = 'cclib2',", + " srcs = ['dep2.c'],", + " deps = [':cclib1'],", + ")", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + " deps = [':cclib2'],", + ")"); + + // cclib1 is force loaded. + assertThat(Joiner.on(" ").join(linkAction("//bin").getArguments())) + .containsMatch(Pattern.compile(" -force_load [^\\s]+/libcclib1.lo\\b")); + } + + @Test + public void testObjcPlusPlusLinkAction() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all", + "--experimental_disable_go", + "--cpu=ios_armv7", + "--ios_cpu=armv7"); + + addMockBinAndLibs(ImmutableList.of("a.mm")); + + CommandAction action = linkAction("//bin:bin"); + assertThat(action.getArguments()) + .containsAllOf( + "tools/osx/crosstool/ios/" + WRAPPED_CLANGPLUSPLUS, + "-stdlib=libc++", + "-std=gnu++11"); + } + + @Test + public void testUnstrippedArtifactGeneratedForBinaryStripping() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all", + "--experimental_disable_go", + "--objc_enable_binary_stripping", + "--compilation_mode=opt"); + addMockBinAndLibs(ImmutableList.of("a.m")); + Action linkAction = actionProducingArtifact("//bin:bin", "_bin_unstripped"); + Action stripAction = actionProducingArtifact("//bin:bin", "_bin"); + assertThat(linkAction).isNotNull(); + assertThat(stripAction).isNotNull(); + } + + + @Test + public void testDeadStripLinkArguments() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all", + "--experimental_disable_go", + "--cpu=ios_armv7", + "--ios_cpu=armv7", + "--objc_enable_binary_stripping", + "--compilation_mode=opt"); + addMockBinAndLibs(ImmutableList.of("a.mm")); + CommandAction linkAction = + (CommandAction) actionProducingArtifact("//bin:bin", "_bin_unstripped"); + assertThat(linkAction.getArguments()) + .containsAllOf("-dead_strip", "-no_dead_strip_inits_and_terms"); + } + + @Test + public void testDeadStripLinkArgumentsNotPresentIfStrippingNotEnabled() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all", + "--experimental_disable_go", + "--cpu=ios_armv7", + "--ios_cpu=armv7", + "--compilation_mode=opt"); + addMockBinAndLibs(ImmutableList.of("a.mm")); + CommandAction linkAction = + (CommandAction) actionProducingArtifact("//bin:bin", "_bin"); + assertThat(linkAction.getArguments()) + .containsNoneOf("--dead_strip", "--no_dead_strip_inits_and_terms"); + } + + @Test + public void testDeadStripLinkArgumentsNotPresentIfCompilationModeFastbuild() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all", + "--experimental_disable_go", + "--cpu=ios_armv7", + "--ios_cpu=armv7", + "--objc_enable_binary_stripping", + "--compilation_mode=fastbuild"); + addMockBinAndLibs(ImmutableList.of("a.mm")); + CommandAction linkAction = + (CommandAction) actionProducingArtifact("//bin:bin", "_bin"); + assertThat(linkAction.getArguments()) + .containsNoneOf("--dead_strip", "--no_dead_strip_inits_and_terms"); + } + + @Test + public void testCompileEnv() throws Exception { + MockObjcSupport.createCrosstoolPackage(mockToolsConfig); + useConfiguration( + ObjcCrosstoolMode.LIBRARY, + "--experimental_disable_go", + "--experimental_disable_jvm", + "--ios_sdk_version=2.9", + "--xcode_version=5.0", + "--cpu=ios_x86_64"); + ScratchAttributeWriter.fromLabelString(this, "objc_library", "//main:lib") + .setList("srcs", "a.m") + .write(); + + CppCompileAction compileAction = (CppCompileAction) compileAction("//main:lib", "a.o"); + + Map<String, String> environment = compileAction.getEnvironment(); + assertThat(environment).containsEntry("XCODE_VERSION_OVERRIDE", "5.0"); + assertThat(environment).containsEntry("APPLE_SDK_VERSION_OVERRIDE", "2.9"); + } + + @Test + public void testArchiveEnv() throws Exception { + MockObjcSupport.createCrosstoolPackage(mockToolsConfig); + useConfiguration( + ObjcCrosstoolMode.LIBRARY, + "--experimental_disable_go", + "--experimental_disable_jvm", + "--ios_sdk_version=2.9", + "--xcode_version=5.0", + "--cpu=ios_x86_64"); + ScratchAttributeWriter.fromLabelString(this, "objc_library", "//main:lib") + .setList("srcs", "a.m") + .write(); + + CppLinkAction archiveAction = (CppLinkAction) archiveAction("//main:lib"); + + Map<String, String> environment = archiveAction.getEnvironment(); + assertThat(environment).containsEntry("XCODE_VERSION_OVERRIDE", "5.0"); + assertThat(environment).containsEntry("APPLE_SDK_VERSION_OVERRIDE", "2.9"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/HeaderDiscoveryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/HeaderDiscoveryTest.java new file mode 100644 index 0000000000..7b99bca8aa --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/HeaderDiscoveryTest.java @@ -0,0 +1,86 @@ +// 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.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.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for discovery of used headers during compilation. */ +@RunWith(JUnit4.class) +public class HeaderDiscoveryTest extends ObjcRuleTestCase { + + @Override + protected void useConfiguration(String... args) throws Exception { + // Don't test crosstool for header discovery + useConfiguration(ObjcCrosstoolMode.OFF, args); + } + + private NestedSet<Artifact> getDiscoveredInputsForDotdFile(String... lines) throws Exception { + createLibraryTargetWriter("//objc:lib") + .setList("srcs", "a.m") + .setList("hdrs", "used.h", "not_used.h") + .write(); + CommandAction commandAction = compileAction("//objc:lib", "a.o"); + assertThat(commandAction).isInstanceOf(ObjcCompileAction.class); + ObjcCompileAction objcCompileAction = (ObjcCompileAction) commandAction; + + Artifact dotdFile = ActionsTestUtil.getOutput(objcCompileAction, "a.d"); + scratch.file(dotdFile.getPath().toString(), lines); + return objcCompileAction.discoverInputsFromDotdFiles( + directories.getExecRoot(), view.getArtifactFactory()); + } + + @Test + public void testObjcHeaderDiscoveryFindsInputs() throws Exception { + NestedSet<Artifact> discoveredInputs = + getDiscoveredInputsForDotdFile("a.o: \\", " objc/a.m \\", " objc/used.h \\"); + assertThat(Artifact.toExecPaths(discoveredInputs)).containsExactly("objc/a.m", "objc/used.h"); + } + + @Test + public void testObjcHeaderDiscoveryIgnoresOutsideExecRoot() throws Exception { + NestedSet<Artifact> discoveredInputs = + getDiscoveredInputsForDotdFile("a.o: \\", " /foo/a.h \\", " /bar/b.h"); + assertThat(Artifact.toExecPaths(discoveredInputs)).isEmpty(); + } + + @Test + public void testInputsArePruned() throws Exception { + NestedSet<Artifact> discoveredInputs = + getDiscoveredInputsForDotdFile("a.o: \\", " objc/a.m \\", " objc/used.h \\"); + ObjcCompileAction compileAction = (ObjcCompileAction) compileAction("//objc:lib", "a.o"); + compileAction.updateActionInputs(discoveredInputs); + + assertThat(Artifact.toExecPaths(compileAction.getInputs())).doesNotContain("objc/not_used.h"); + } + + @Test + public void testSrcsAreMandatoryInputs() throws Exception { + NestedSet<Artifact> discoveredInputs = + getDiscoveredInputsForDotdFile("a.o: \\", " objc/used.h"); + ObjcCompileAction compileAction = (ObjcCompileAction) compileAction("//objc:lib", "a.o"); + compileAction.updateActionInputs(discoveredInputs); + + assertThat(Artifact.toExecPaths(compileAction.getInputs())).contains("objc/a.m"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/HeaderThinningTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/HeaderThinningTest.java new file mode 100644 index 0000000000..cdd9d72764 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/HeaderThinningTest.java @@ -0,0 +1,271 @@ +// 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 static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact; +import com.google.devtools.build.lib.actions.Artifact.SpecialArtifactType; +import com.google.devtools.build.lib.actions.ExecException; +import com.google.devtools.build.lib.actions.UserExecException; +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.SpawnAction; +import com.google.devtools.build.lib.analysis.util.ScratchAttributeWriter; +import com.google.devtools.build.lib.packages.util.MockObjcSupport; +import com.google.devtools.build.lib.rules.cpp.CppCompileAction; +import com.google.devtools.build.lib.vfs.FileSystemUtils; +import com.google.devtools.build.lib.vfs.PathFragment; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for header thinning. */ +@RunWith(JUnit4.class) +public class HeaderThinningTest extends ObjcRuleTestCase { + + private static final String CPP_COMPILE_ACTION_RULE_TYPE = "objc_library"; + + @Before + public void beforeEach() throws Exception { + MockObjcSupport.createCrosstoolPackage(mockToolsConfig); + MockObjcSupport.setupAppleSdks(mockToolsConfig); + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_header_thinning", + "--objc_use_dotd_pruning", + "--xcode_version=" + MockObjcSupport.DEFAULT_XCODE_VERSION, + "--ios_sdk_version=" + MockObjcSupport.DEFAULT_IOS_SDK_VERSION); + } + + @Test + public void testCppCompileActionHeaderThinningCanDetermineAdditionalInputs() throws Exception { + Artifact sourceFile = getSourceArtifact("objc/a.m"); + CppCompileAction action = createCppCompileAction(sourceFile); + List<Artifact> expectedHeaders = + ImmutableList.of( + getSourceArtifact("objc/a.pch"), + getSourceArtifact("objc/b.h"), + getSourceArtifact("objc/c"), + getSourceArtifact("objc/d.hpp")); + HeaderThinning headerThinning = new HeaderThinning(getPotentialHeaders(expectedHeaders)); + writeToHeadersListFile(action, "objc/a.pch", "objc/b.h", "objc/c", "objc/d.hpp"); + + Iterable<Artifact> headersFound = headerThinning.determineAdditionalInputs(null, action, null); + assertThat(headersFound).containsExactlyElementsIn(expectedHeaders); + } + + @Test + public void testCppCompileActionHeaderThinningThrowsWhenUnknownHeaderFound() throws Exception { + Artifact sourceFile = getSourceArtifact("objc/a.m"); + CppCompileAction action = createCppCompileAction(sourceFile); + List<Artifact> expectedHeaders = + ImmutableList.of(getSourceArtifact("objc/a.h"), getSourceArtifact("objc/b.h")); + HeaderThinning headerThinning = new HeaderThinning(getPotentialHeaders(expectedHeaders)); + writeToHeadersListFile(action, "objc/a.h", "objc/b.h", "objc/c.h"); + + try { + headerThinning.determineAdditionalInputs(null, action, null); + fail("Exception was not thrown"); + } catch (ExecException e) { + assertThat(e).hasMessageThat().containsMatch("(objc/c.h)"); + assertThat(e).isInstanceOf(UserExecException.class); + } + } + + @Test + public void testCppCompileActionHeaderThinningFindsHeadersInTreeArtifacts() throws Exception { + Artifact sourceFile = getSourceArtifact("objc/a.m"); + CppCompileAction action = createCppCompileAction(sourceFile); + List<Artifact> expectedHeaders = + ImmutableList.of(getSourceArtifact("objc/a.h"), getTreeArtifact("tree/dir")); + HeaderThinning headerThinning = new HeaderThinning(getPotentialHeaders(expectedHeaders)); + writeToHeadersListFile(action, "objc/a.h", "tree/dir/c.h"); + + Iterable<Artifact> headersFound = headerThinning.determineAdditionalInputs(null, action, null); + assertThat(headersFound).containsExactlyElementsIn(expectedHeaders); + } + + @Test + public void testObjcCompileActionHeaderThinningCanFindRequiredHeaderInputs() throws Exception { + Artifact sourceFile = getSourceArtifact("objc/a.m"); + Artifact headersListFile = getHeadersListArtifact(sourceFile); + scratch.file( + headersListFile.getExecPathString(), "objc/a.pch", "objc/b.h", "objc/c", "objc/d.hpp"); + List<Artifact> expectedHeaders = + ImmutableList.of( + getSourceArtifact("objc/a.pch"), + getSourceArtifact("objc/b.h"), + getSourceArtifact("objc/c"), + getSourceArtifact("objc/d.hpp")); + + Iterable<Artifact> headersFound = + HeaderThinning.findRequiredHeaderInputs( + sourceFile, + headersListFile, + createHeaderFilesMap(getPotentialHeaders(expectedHeaders))); + assertThat(headersFound).containsExactlyElementsIn(expectedHeaders); + } + + private void writeToHeadersListFile(CppCompileAction action, String... lines) throws Exception { + Artifact headersListFile = null; + for (Artifact input : action.getMandatoryInputs()) { + if (input.getExtension().equals("headers_list")) { + headersListFile = input; + break; + } + } + assertThat(headersListFile).isNotNull(); + scratch.file(headersListFile.getPath().getPathString(), lines); + } + + /** + * Simple utility to populate some unused header Artifacts along with the ones we expect to find. + */ + private Iterable<Artifact> getPotentialHeaders(List<Artifact> expectedHeaders) { + return Iterables.concat( + ImmutableList.of( + getSourceArtifact("objc/unused.h"), + getSourceArtifact("some/other.h"), + getSourceArtifact("objc/foo.pch")), + expectedHeaders); + } + + private CppCompileAction createCppCompileAction(Artifact sourceFile) throws Exception { + String ownerLabel = "//objc:lib"; + ScratchAttributeWriter.fromLabelString(this, CPP_COMPILE_ACTION_RULE_TYPE, ownerLabel) + .setList("srcs", sourceFile.getFilename()) + .write(); + ConfiguredTarget target = getConfiguredTarget(ownerLabel); + List<CppCompileAction> allActions = + actionsTestUtil() + .findTransitivePrerequisitesOf( + ActionsTestUtil.getFirstArtifactEndingWith(getFilesToBuild(target), ".a"), + CppCompileAction.class); + for (CppCompileAction action : allActions) { + if (action.getSourceFile().getExecPath().equals(sourceFile.getExecPath())) { + return action; + } + } + return null; + } + + private Artifact getHeadersListArtifact(Artifact sourceFile) { + return getSourceArtifact( + FileSystemUtils.replaceExtension(sourceFile.getExecPath(), ".headers_list"), + sourceFile.getRoot()); + } + + private static Map<PathFragment, Artifact> createHeaderFilesMap(Iterable<Artifact> artifacts) { + ImmutableMap.Builder<PathFragment, Artifact> headerFilesMapBuilder = ImmutableMap.builder(); + for (Artifact artifact : artifacts) { + headerFilesMapBuilder.put(artifact.getExecPath(), artifact); + } + return headerFilesMapBuilder.build(); + } + + private Artifact getTreeArtifact(String name) { + Artifact treeArtifactBase = getSourceArtifact(name); + return new SpecialArtifact( + treeArtifactBase.getPath(), + treeArtifactBase.getRoot(), + treeArtifactBase.getExecPath(), + treeArtifactBase.getArtifactOwner(), + SpecialArtifactType.TREE); + } + + @Test + public void generatesHeaderScanningAction() throws Exception { + Set<SpawnAction> scanningActions = + createTargetAndGetHeaderScanningActions(ImmutableList.of("one.m", "two.m")); + assertThat(scanningActions).hasSize(1); + } + + @Test + public void generates2HeaderScanningActionsWhenObjcAndCppSources() throws Exception { + Set<SpawnAction> scanningActions = + createTargetAndGetHeaderScanningActions(ImmutableList.of("one.m", "two.cc")); + assertThat(scanningActions).hasSize(2); + } + + @Test + public void generatesMultipleHeaderScanningActionsForLargeTargets2() throws Exception { + validateGeneratesMultipleHeaderScanningActionsForLargeTargets( + 2, targetConfig.getFragment(ObjcConfiguration.class).objcHeaderThinningPartitionSize()); + } + + @Test + public void generatesMultipleHeaderScanningActionsForLargeTargets4() throws Exception { + validateGeneratesMultipleHeaderScanningActionsForLargeTargets( + 4, targetConfig.getFragment(ObjcConfiguration.class).objcHeaderThinningPartitionSize()); + } + + @Test + public void generatesMultipleHeaderScanningActionsForLargeTargets8() throws Exception { + validateGeneratesMultipleHeaderScanningActionsForLargeTargets( + 8, targetConfig.getFragment(ObjcConfiguration.class).objcHeaderThinningPartitionSize()); + } + + @Test + public void generatesMultipleHeaderScanningActionsForLargeTargetsCustomPartition() + throws Exception { + int partitionSize = 5; + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_header_thinning", + "--objc_header_thinning_partition_size=" + 5, + "--objc_use_dotd_pruning", + "--xcode_version=" + MockObjcSupport.DEFAULT_XCODE_VERSION, + "--ios_sdk_version=" + MockObjcSupport.DEFAULT_IOS_SDK_VERSION); + validateGeneratesMultipleHeaderScanningActionsForLargeTargets(12, partitionSize); + } + + private void validateGeneratesMultipleHeaderScanningActionsForLargeTargets( + int actionCount, int partitionSize) throws Exception { + ImmutableList.Builder<String> sourcesBuilder = ImmutableList.builder(); + for (int i = 0; i < partitionSize * actionCount; ++i) { + sourcesBuilder.add(String.format("source_%d.m", i)); + } + Set<SpawnAction> scanningActions = + createTargetAndGetHeaderScanningActions(sourcesBuilder.build()); + assertThat(scanningActions).hasSize(actionCount); + } + + private Set<SpawnAction> createTargetAndGetHeaderScanningActions(Iterable<String> sources) + throws Exception { + String ownerLabel = "//objc:lib"; + ScratchAttributeWriter.fromLabelString(this, CPP_COMPILE_ACTION_RULE_TYPE, ownerLabel) + .setList("srcs", sources) + .write(); + ConfiguredTarget target = getConfiguredTarget(ownerLabel); + List<SpawnAction> spawnActions = + actionsTestUtil() + .findTransitivePrerequisitesOf( + ActionsTestUtil.getFirstArtifactEndingWith(getFilesToBuild(target), ".a"), + SpawnAction.class); + return Sets.newHashSet( + Iterables.filter(spawnActions, a -> a.getMnemonic().equals("ObjcHeaderScanning"))); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/InterspersingTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/InterspersingTest.java new file mode 100644 index 0000000000..06ae707521 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/InterspersingTest.java @@ -0,0 +1,78 @@ +// 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 static org.junit.Assert.fail; + +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for the Interspersing utility. */ +@RunWith(JUnit4.class) +public class InterspersingTest { + @Test + public void beforeEach_empty() { + assertThat(Interspersing.beforeEach("foo", ImmutableList.<String>of())).isEmpty(); + } + + @Test + public void beforeEach_isLazy() { + Iterable<Integer> result = Interspersing.beforeEach(1011, Iterables.cycle(1, 2, 3)); + assertThat(Iterables.get(result, 0)).isEqualTo((Object) 1011); + assertThat(Iterables.get(result, 1)).isEqualTo((Object) 1); + assertThat(Iterables.get(result, 2)).isEqualTo((Object) 1011); + assertThat(Iterables.get(result, 3)).isEqualTo((Object) 2); + } + + @Test + public void beforeEach_throwsForNullWhat() { + try { + Interspersing.beforeEach(null, ImmutableList.of("a")); + fail("should have thrown"); + } catch (NullPointerException expected) {} + } + + @Test + public void prependEach_empty() { + assertThat(Interspersing.prependEach("foo", ImmutableList.<String>of())).isEmpty(); + } + + @Test + public void prependEach_isLazy() { + Iterable<String> result = Interspersing.prependEach( + "#", Iterables.cycle(1, 2, 3), Functions.toStringFunction()); + assertThat(Iterables.get(result, 0)).isEqualTo("#1"); + } + + @Test + public void prependEach_typicalList() { + Iterable<String> result = Interspersing.prependEach( + "*", ImmutableList.of("a", "b", "c")); + assertThat(result).containsExactly("*a", "*b", "*c").inOrder(); + } + + @Test + public void prependEach_throwsForNullWhat() { + try { + Interspersing.prependEach(null, ImmutableList.of("a")); + fail("should have thrown"); + } catch (NullPointerException expected) {} + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/IosApplicationTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/IosApplicationTest.java new file mode 100644 index 0000000000..a7dda26d2a --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/IosApplicationTest.java @@ -0,0 +1,1175 @@ +// 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 static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.APP_ICON_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.LAUNCH_IMAGE_ATTR; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Multiset; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.FileProvider; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.RunfilesProvider; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.rules.apple.AppleConfiguration; +import com.google.devtools.build.lib.rules.apple.AppleConfiguration.ConfigurationDistinguisher; +import com.google.devtools.build.lib.rules.apple.DottedVersion; +import com.google.devtools.build.lib.testutil.Scratch; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.BundleFile; +import com.google.devtools.build.xcode.plmerge.proto.PlMergeProtos; +import java.io.IOException; +import java.util.List; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for ios_application. */ +@RunWith(JUnit4.class) +public class IosApplicationTest extends ObjcRuleTestCase { + protected static final RuleType RULE_TYPE = + new RuleType("ios_application") { + @Override + Iterable<String> requiredAttributes( + Scratch scratch, String packageDir, Set<String> alreadyAdded) throws IOException { + ImmutableList.Builder<String> attributes = new ImmutableList.Builder<>(); + if (!alreadyAdded.contains("binary")) { + scratch.file(packageDir + "/bin/a.m"); + scratch.file(packageDir + "/bin/BUILD", "objc_binary(name = 'bin', srcs = ['a.m'])"); + attributes.add("binary = '//" + packageDir + "/bin:bin'"); + } + return attributes.build(); + } + }; + + protected static final BinaryRuleTypePair RULE_TYPE_PAIR = + new BinaryRuleTypePair( + ObjcBinaryTest.RULE_TYPE, RULE_TYPE, ReleaseBundlingSupport.APP_BUNDLE_DIR_FORMAT); + + private ConfiguredTarget addMockAppAndLibs(String... extraAppAttributes) + throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + scratch.file("x/a.m"); + scratch.file("x/x-Info.plist"); + scratch.file("x/BUILD", + "objc_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " deps = ['//lib1:lib1', '//lib2:lib2'],", + ")", + "", + "ios_application(", + " name = 'x',", + " binary = ':bin',", + Joiner.on(',').join(extraAppAttributes), + ")"); + return getConfiguredTarget("//x:x"); + } + + @Test + public void testSplitConfigurationProviders() throws Exception { + useConfiguration("--ios_multi_cpus=i386,x86_64"); + scratch.file("x/BUILD", + "objc_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + ")", + "", + "ios_application(", + " name = 'x',", + " infoplist = 'Info.plist',", + " binary = ':bin',", + ")"); + RuleContext ruleContext = getRuleContext(getConfiguredTarget("//x:x")); + ImmutableListMultimap<BuildConfiguration, ObjcProvider> prereqByConfig = + ruleContext.getPrerequisitesByConfiguration("binary", Mode.SPLIT, ObjcProvider.class); + List<String> childCpus = Lists.transform(prereqByConfig.keySet().asList(), + new Function<BuildConfiguration, String>() { + @Override + public String apply(BuildConfiguration config) { + return config.getFragment(AppleConfiguration.class).getIosCpu(); + } + }); + assertThat(childCpus).containsExactly("i386", "x86_64"); + } + + @Test + public void testRunfiles() throws Exception { + ConfiguredTarget application = addMockAppAndLibs(); + RunfilesProvider runfiles = application.getProvider(RunfilesProvider.class); + assertThat(runfiles.getDefaultRunfiles().getArtifacts()).isEmpty(); + assertThat(Artifact.toRootRelativePaths(runfiles.getDataRunfiles().getArtifacts())) + .containsExactly("x/x.ipa"); + } + + @Test + public void testFilesToRun() throws Exception { + checkFilesToRun(RULE_TYPE); + } + + @Test + public void testNoRunfilesSupportForDevice() throws Exception { + checkNoRunfilesSupportForDevice(RULE_TYPE); + } + + @Test + public void testGenerateRunnerScriptAction() throws Exception { + checkGenerateRunnerScriptAction(RULE_TYPE); + } + + @Test + public void testGenerateRunnerScriptAction_escaped() throws Exception { + checkGenerateRunnerScriptAction_escaped(RULE_TYPE); + } + + @Test + public void testSigningAction() throws Exception { + checkDeviceSigningAction(RULE_TYPE); + } + + @Test + public void testSigningWithCertName() throws Exception { + checkSigningWithCertName(RULE_TYPE); + } + + @Test + public void testPostProcessingAction() throws Exception { + checkPostProcessingAction(RULE_TYPE); + } + + @Test + public void testSigningAndPostProcessing() throws Exception { + checkSigningAndPostProcessing(RULE_TYPE); + } + + @Test + public void testSigning_simulatorBuild() throws Exception { + checkSigningSimulatorBuild(RULE_TYPE_PAIR, false); + } + + @Test + public void testSigning_simulatorBuild_multiCpu() throws Exception { + checkSigningSimulatorBuild(RULE_TYPE_PAIR, true); + } + + @Test + public void testProvisioningProfile_deviceBuild() throws Exception { + checkProvisioningProfileDeviceBuild(RULE_TYPE_PAIR, false); + } + + @Test + public void testProvisioningProfile_deviceBuild_multiCpu() throws Exception { + checkProvisioningProfileDeviceBuild(RULE_TYPE_PAIR, true); + } + + @Test + public void testUserSpecifiedProvisioningProfile_deviceBuild() throws Exception { + checkProvisioningProfileUserSpecified(RULE_TYPE_PAIR, false); + } + + @Test + public void testUserSpecifiedProvisioningProfile_deviceBuild_multiCpu() throws Exception { + checkProvisioningProfileUserSpecified(RULE_TYPE_PAIR, true); + } + + @Test + public void testMergeControlAction() throws Exception { + addMockAppAndLibs("infoplist = 'Info.plist'"); + Action mergeAction = bundleMergeAction("//x:x"); + Action action = bundleMergeControlAction("//x:x"); + assertThat(action.getInputs()).isEmpty(); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("x/x.ipa-control"); + assertThat(bundleMergeControl("//x:x")) + .isEqualTo( + BundleMergeProtos.Control.newBuilder() + .addBundleFile( + BundleFile.newBuilder() + .setSourceFile(execPathEndingWith(mergeAction.getInputs(), "x_lipobin")) + .setBundlePath("x") + .setExternalFileAttribute(BundleableFile.EXECUTABLE_EXTERNAL_FILE_ATTRIBUTE) + .build()) + .setBundleRoot(String.format(ReleaseBundlingSupport.APP_BUNDLE_DIR_FORMAT, "x")) + .setBundleInfoPlistFile(execPathEndingWith(mergeAction.getInputs(), "Info.plist")) + .setOutFile(execPathEndingWith(mergeAction.getOutputs(), "x.unprocessed.ipa")) + .setMinimumOsVersion(DEFAULT_IOS_SDK_VERSION.toString()) + .setSdkVersion(DEFAULT_IOS_SDK_VERSION.toString()) + .setPlatform("IOS_SIMULATOR") + .setFallbackBundleIdentifier("example.x") + .build()); + } + + @Test + public void testMergeBundleAction() throws Exception { + checkMergeBundleAction(RULE_TYPE_PAIR); + } + + @Test + public void testCheckPrimaryBundleIdInMergedPlist() throws Exception { + checkPrimaryBundleIdInMergedPlist(RULE_TYPE_PAIR); + } + + @Test + public void testCheckFallbackBundleIdInMergedPlist() throws Exception { + checkFallbackBundleIdInMergedPlist(RULE_TYPE_PAIR); + } + + @Test + public void testErrorForLaunchImageGivenWithNoAssetCatalog() throws Exception { + checkAssetCatalogAttributeError(RULE_TYPE, LAUNCH_IMAGE_ATTR); + } + + @Test + public void testErrorForAppIconGivenWithNoAssetCatalog() throws Exception { + checkAssetCatalogAttributeError(RULE_TYPE, APP_ICON_ATTR); + } + + @Test + public void testCollectsAssetCatalogsTransitively() throws Exception { + checkCollectsAssetCatalogsTransitively(RULE_TYPE_PAIR); + } + + @Test + public void testSpecifyAppIconAndLaunchImageUsingXcassetsOfDependency() throws Exception { + checkSpecifyAppIconAndLaunchImageUsingXcassetsOfDependency( + RULE_TYPE_PAIR, DEFAULT_IOS_SDK_VERSION); + } + + private void addTargetWithAssetCatalogs() throws IOException { + scratch.file("x/foo.xcassets/foo"); + scratch.file("x/foo.xcassets/bar"); + scratch.file("x/a.m"); + scratch.file("x/BUILD", + "objc_binary(", + " name = 'bin',", + " asset_catalogs = ['foo.xcassets/foo', 'bar.xcassets/bar'],", + " srcs = ['a.m'],", + ")", + "", + "ios_application(", + " name = 'x',", + " binary = ':bin',", + ")"); + } + + @Test + public void testActoolAction() throws Exception { + addTargetWithAssetCatalogs(); + checkActoolActionCorrectness(DEFAULT_IOS_SDK_VERSION); + } + + @Test + public void testPassesFamiliesToActool() throws Exception { + checkPassesFamiliesToActool(RULE_TYPE_PAIR); + } + + @Test + public void testPassesFamiliesToIbtool() throws Exception { + checkPassesFamiliesToIbtool(RULE_TYPE_PAIR); + } + + @Test + public void testReportsErrorsForInvalidFamiliesAttribute() throws Exception { + checkReportsErrorsForInvalidFamiliesAttribute(RULE_TYPE); + } + + @Test + public void testMergeActionsWithAssetCatalog() throws Exception { + // TODO(matvore): add this test to IosTestTest.java. + addTargetWithAssetCatalogs(); + checkMergeActionsWithAssetCatalog(RULE_TYPE_PAIR); + } + + private void addBinAndLibWithRawResources() throws Exception { + addBinAndLibWithResources( + "resources", "resource1.txt", "ja.lproj/resource2.txt", "objc_binary"); + scratch.file("x/BUILD", + "ios_application(", + " name = 'x',", + " binary = '//bin:bin',", + ")"); + } + + private void addBinAndLibWithStrings() throws Exception { + addBinAndLibWithResources( + "strings", "foo.strings", "ja.lproj/bar.strings", "objc_binary"); + scratch.file("x/BUILD", + "ios_application(", + " name = 'x',", + " binary = '//bin:bin',", + ")"); + } + + @Test + public void testCollectsRawResourceFilesTransitively() throws Exception { + addBinAndLibWithRawResources(); + checkCollectsResourceFilesTransitively( + "//x:x", + ImmutableList.of("lib/resource1.txt", "bin/ja.lproj/resource2.txt"), + ImmutableList.of("lib/resource1.txt"), + ImmutableSetMultimap.<String, Multiset<String>>of( + "bin_bin", + ImmutableMultiset.of("bin/ja.lproj/resource2.txt", "lib/resource1.txt"), + "bin_static_lib_bin", + ImmutableMultiset.of("bin/ja.lproj/resource2.txt", "lib/resource1.txt"), + "x_x", + ImmutableMultiset.of("bin/ja.lproj/resource2.txt", "lib/resource1.txt"), + "lib_lib", + ImmutableMultiset.of("lib/resource1.txt"))); + } + + @Test + public void testCollectsStringsFilesTransitively() throws Exception { + addBinAndLibWithStrings(); + checkCollectsResourceFilesTransitively( + "//x:x", + ImmutableList.of("x/lib/foo.strings.binary", "x/bin/ja.lproj/bar.strings.binary"), + ImmutableList.of("x/lib/foo.strings.binary"), + ImmutableSetMultimap.<String, Multiset<String>>of( + "bin_bin", + ImmutableMultiset.of("bin/ja.lproj/bar.strings", "lib/foo.strings"), + "bin_static_lib_bin", + ImmutableMultiset.of("bin/ja.lproj/bar.strings", "lib/foo.strings"), + "x_x", + ImmutableMultiset.of("bin/ja.lproj/bar.strings", "lib/foo.strings"), + "lib_lib", + ImmutableMultiset.of("lib/foo.strings"))); + } + + @Test + public void testResourceFilesMergedInBundle() throws Exception { + addBinAndLibWithRawResources(); + checkBundleablesAreMerged("//x:x", + ImmutableListMultimap.of( + "resource1.txt", "resource1.txt", + "ja.lproj/resource2.txt", "ja.lproj/resource2.txt")); + } + + @Test + public void testResourceFlattenedInBundle() throws Exception { + addBinAndLibWithResources( + "resources", "libres/resource1.txt", "binres/resource2.txt", "objc_binary"); + scratch.file("x/BUILD", + "ios_application(", + " name = 'x',", + " binary = '//bin:bin',", + ")"); + checkBundleablesAreMerged("//x:x", + ImmutableListMultimap.of( + "libres/resource1.txt", "resource1.txt", + "binres/resource2.txt", "resource2.txt")); + } + + @Test + public void testStructuredResourceFilesMergedInBundle() throws Exception { + addBinAndLibWithResources( + "structured_resources", "libres/resource1.txt", "binres/resource2.txt", "objc_binary"); + scratch.file("x/BUILD", + "ios_application(", + " name = 'x',", + " binary = '//bin:bin',", + ")"); + checkBundleablesAreMerged("//x:x", + ImmutableListMultimap.of( + "libres/resource1.txt", "libres/resource1.txt", + "binres/resource2.txt", "binres/resource2.txt")); + } + + @Test + public void testStringsFilesMergedInBundle() throws Exception { + addBinAndLibWithStrings(); + checkBundleablesAreMerged("//x:x", + ImmutableListMultimap.of( + "foo.strings.binary", "foo.strings", + "ja.lproj/bar.strings.binary", "ja.lproj/bar.strings")); + } + + @Test + public void testMergesXcdatamodelZips() throws Exception { + checkMergesXcdatamodelZips(RULE_TYPE_PAIR); + } + + @Test + public void testPlistRequiresDotInName() throws Exception { + checkError("x", "x", + "'//x:Infoplist' does not produce any ios_application infoplist files (expected .plist)", + "objc_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + ")", + "", + "ios_application(", + " name = 'x',", + " infoplist = 'Infoplist',", + " binary = ':bin',", + ")"); + } + + @Test + public void testPopulatesBundling() throws Exception { + scratch.file("x/x-Info.plist"); + scratch.file("x/a.m"); + scratch.file("x/assets.xcassets/1"); + scratch.file("x/BUILD", + "objc_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " asset_catalogs = ['assets.xcassets/1']", + ")", + "ios_application(", + " name = 'x',", + " binary = ':bin',", + " infoplist = 'x-Info.plist',", + ")"); + + PlMergeProtos.Control control = plMergeControl("//x:x"); + assertThat(control.getSourceFileList()) + .contains(getSourceArtifact("x/x-Info.plist").getExecPathString()); + + Artifact actoolzipOutput = getBinArtifact("x.actool.zip", "//x:x"); + assertThat(getGeneratingAction(actoolzipOutput).getInputs()) + .contains(getSourceArtifact("x/assets.xcassets/1")); + } + + @Test + public void testMergesPartialInfoplists() throws Exception { + checkMergesPartialInfoplists(RULE_TYPE_PAIR); + } + + @Test + public void testNibZipsMergedIntoBundle() throws Exception { + checkNibZipsMergedIntoBundle(RULE_TYPE_PAIR); + } + + @Test + public void testNoEntitlementsDefined() throws Exception { + checkNoEntitlementsDefined(RULE_TYPE); + } + + @Test + public void testEntitlementsDefined() throws Exception { + checkEntitlementsDefined(RULE_TYPE); + } + + @Test + public void testExtraEntitlements() throws Exception { + checkExtraEntitlements(RULE_TYPE); + } + + @Test + public void testDebugEntitlements() throws Exception { + checkDebugEntitlements(RULE_TYPE); + } + + @Test + public void testFastbuildDebugEntitlements() throws Exception { + checkFastbuildDebugEntitlements(RULE_TYPE); + } + + @Test + public void testOptNoDebugEntitlements() throws Exception { + checkOptNoDebugEntitlements(RULE_TYPE); + } + + @Test + public void testExplicitNoDebugEntitlements() throws Exception { + checkExplicitNoDebugEntitlements(RULE_TYPE); + } + + @Test + public void testMultiPlatformBuild_fails() throws Exception { + checkBinaryActionMultiPlatform_fails(RULE_TYPE_PAIR); + } + + @Test + public void testMultiArchitectureResources() throws Exception { + checkMultiCpuResourceInheritance(RULE_TYPE_PAIR); + } + + /** + * Regression test for b/27946171. Verifies that nodistinct_host_configuration functions in + * builds with more than one split transition. (In this case, both ios_application and + * ios_extension split into two child configurations.) + */ + @Test + public void testNoDistinctHostConfiguration() throws Exception { + useConfiguration("--ios_multi_cpus=i386,x86_64", "--nodistinct_host_configuration"); + scratch.file("x/BUILD", + "ios_extension_binary(", + " name = 'ext_bin',", + " srcs = ['ebin.m'],", + ")", + "", + "ios_extension(", + " name = 'ext',", + " binary = ':ext_bin',", + ")", + "", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + ")", + "", + "ios_application(", + " name = 'app',", + " binary = ':bin',", + " extensions = [':ext'],", + ")"); + + getConfiguredTarget("//x:app"); + + // Assert that only the deprecation warnings are emitted, but no other events. + assertContainsEventWithFrequency( + "This rule is deprecated. Please use the new Apple build rules " + + "(https://github.com/bazelbuild/rules_apple) to build Apple targets.", + 4); + } + + @Test + public void testApplicationExtensionSharedDependencyResourceActions() throws Exception { + useConfiguration("--ios_multi_cpus=i386,x86_64"); + scratch.file("x/BUILD", + "objc_library(", + " name = 'res',", + " xibs = ['interface.xib'],", + " storyboards = ['story.storyboard'],", + " datamodels = ['data.xcdatamodel/1'],", + " asset_catalogs = ['assets.xcassets/foo'],", + ")", + "", + "ios_extension_binary(", + " name = 'ext_bin',", + " srcs = ['ebin.m'],", + " deps = [':res'],", + ")", + "", + "ios_extension(", + " name = 'ext',", + " binary = ':ext_bin',", + ")", + "", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + " deps = [':res'],", + ")", + "", + "ios_application(", + " name = 'app',", + " binary = ':bin',", + " extensions = [':ext'],", + ")"); + + Action appIpaAction = bundleMergeAction("//x:app"); + + Action extIpaAction = bundleMergeAction("//x:ext"); + + Artifact appNibZip = Iterables.getOnlyElement(inputsEndingWith(appIpaAction, "nib.zip")); + Artifact extNibZip = Iterables.getOnlyElement(inputsEndingWith(extIpaAction, "nib.zip")); + assertThat(appNibZip.getExecPath()).isNotEqualTo(extNibZip.getExecPath()); + + Artifact appStoryboardZip = + Iterables.getOnlyElement(inputsEndingWith(appIpaAction, "story.storyboard.zip")); + Artifact extStoryboardZip = + Iterables.getOnlyElement(inputsEndingWith(extIpaAction, "story.storyboard.zip")); + assertThat(appStoryboardZip.getExecPath()).isNotEqualTo(extStoryboardZip.getExecPath()); + + Artifact appDatamodelZip = Iterables.getOnlyElement(inputsEndingWith(appIpaAction, "data.zip")); + Artifact extDatamodelZip = Iterables.getOnlyElement(inputsEndingWith(extIpaAction, "data.zip")); + assertThat(appDatamodelZip.getExecPath()).isNotEqualTo(extDatamodelZip.getExecPath()); + + Artifact appAssetZip = Iterables.getOnlyElement(inputsEndingWith(appIpaAction, "actool.zip")); + Artifact extAssetZip = Iterables.getOnlyElement(inputsEndingWith(extIpaAction, "actool.zip")); + assertThat(appAssetZip.getExecPath()).isNotEqualTo(extAssetZip.getExecPath()); + } + + @Test + public void testMultiCpuCompiledResources() throws Exception { + checkMultiCpuCompiledResources(RULE_TYPE_PAIR); + } + + @Test + public void testMomczipActions() throws Exception { + checkMomczipActions(RULE_TYPE_PAIR, DEFAULT_IOS_SDK_VERSION); + } + + @Test + public void testConvertStringsActions() throws Exception { + checkConvertStringsAction(RULE_TYPE_PAIR); + } + + @Test + public void testCompileXibActions() throws Exception { + checkCompileXibActions(RULE_TYPE_PAIR, DEFAULT_IOS_SDK_VERSION, "iphone"); + } + + @Test + public void testRegistersStoryboardCompileActions() throws Exception { + checkRegistersStoryboardCompileActions(RULE_TYPE_PAIR, DEFAULT_IOS_SDK_VERSION, "iphone"); + } + + @Test + public void testMultiCpuCompiledResourcesFromGenrule() throws Exception { + checkMultiCpuCompiledResourcesFromGenrule(RULE_TYPE_PAIR); + } + + @Test + public void testMultiCpuGeneratedResourcesFromGenrule() throws Exception { + checkMultiCpuGeneratedResourcesFromGenrule(RULE_TYPE_PAIR); + } + + @Test + public void testTwoStringsOneBundlePath() throws Exception { + //TODO(bazel-team): This error should be on //x:x but shows up on :bin right now until that + // doesn't support bundling anymore. + checkTwoStringsOneBundlePath(RULE_TYPE_PAIR, "bin"); + } + + @Test + public void testTwoResourcesOneBundlePath() throws Exception { + //TODO(bazel-team): This error should be on //x:x but shows up on :bin right now until that + // doesn't support bundling anymore. + checkTwoResourcesOneBundlePath(RULE_TYPE_PAIR, "bin"); + } + + @Test + public void testSameStringsTwice() throws Exception { + //TODO(bazel-team): This error should be on //x:x but shows up on :bin right now until that + // doesn't support bundling anymore. + checkSameStringsTwice(RULE_TYPE_PAIR, "bin"); + } + + @Test + public void testGenruleWithoutJavaCcDeps() throws Exception { + checkGenruleWithoutJavaCcDependency(RULE_TYPE_PAIR); + } + + @Test + public void testCcDependencyWithProtoDependencyMultiArch() throws Exception { + checkCcDependencyWithProtoDependencyMultiArch( + RULE_TYPE_PAIR, ConfigurationDistinguisher.IOS_APPLICATION); + } + + @Test + public void testAppleSdkVersionEnv() throws Exception { + RULE_TYPE_PAIR.scratchTargets(scratch); + + useConfiguration("--ios_multi_cpus=x86_64,i386"); + SpawnAction action = (SpawnAction) getGeneratingAction( + getBinArtifact("x_lipobin", getConfiguredTarget("//x:x"))); + + assertAppleSdkVersionEnv(action); + } + + @Test + public void testNonDefaultAppleSdkVersionEnv() throws Exception { + RULE_TYPE_PAIR.scratchTargets(scratch); + + useConfiguration("--ios_sdk_version=8.1", "--ios_multi_cpus=x86_64,i386"); + SpawnAction action = (SpawnAction) getGeneratingAction( + getBinArtifact("x_lipobin", getConfiguredTarget("//x:x"))); + + assertAppleSdkVersionEnv(action, "8.1"); + } + + @Test + public void testAppleSdkDefaultPlatformEnv() throws Exception { + RULE_TYPE_PAIR.scratchTargets(scratch); + + useConfiguration("--ios_multi_cpus=x86_64,i386"); + SpawnAction action = (SpawnAction) getGeneratingAction( + getBinArtifact("x_lipobin", getConfiguredTarget("//x:x"))); + + assertAppleSdkPlatformEnv(action, "iPhoneSimulator"); + } + + @Test + public void testAppleSdkDevicePlatformEnv() throws Exception { + RULE_TYPE_PAIR.scratchTargets(scratch); + + useConfiguration("--ios_multi_cpus=arm64,armv7"); + SpawnAction action = (SpawnAction) getGeneratingAction( + getBinArtifact("x_lipobin", getConfiguredTarget("//x:x"))); + + assertAppleSdkPlatformEnv(action, "iPhoneOS"); + } + + @Test + public void testXcodeVersionEnv() throws Exception { + RULE_TYPE_PAIR.scratchTargets(scratch); + + useConfiguration("--xcode_version=5.8", "--ios_multi_cpus=x86_64,i386"); + SpawnAction action = (SpawnAction) getGeneratingAction( + getBinArtifact("x_lipobin", getConfiguredTarget("//x:x"))); + + assertXcodeVersionEnv(action, "5.8"); + } + + @Test + public void testLaunchStoryboardIncluded() throws Exception { + checkLaunchStoryboardIncluded(RULE_TYPE_PAIR); + } + + @Test + public void testLaunchStoryboardXibIncluded() throws Exception { + checkLaunchStoryboardXib(RULE_TYPE_PAIR); + } + + @Test + public void testLaunchStoryboardLproj() throws Exception { + checkLaunchStoryboardLproj(RULE_TYPE_PAIR); + } + + @Test + public void testAutomaticPlistEntries() throws Exception { + checkAutomaticPlistEntries(RULE_TYPE); + } + + @Test + public void testBundleMergeInputContainsPlMergeOutput() throws Exception { + checkBundleMergeInputContainsPlMergeOutput(RULE_TYPE); + } + + @Test + public void testMultipleInfoPlists() throws Exception { + checkMultipleInfoPlists(RULE_TYPE); + } + + @Test + public void testInfoplistAndInfoplistsTogether() throws Exception { + checkInfoplistAndInfoplistsTogether(RULE_TYPE); + } + + @Test + public void testLateLoadedObjcFrameworkInFinalBundle() throws Exception { + scratch.file("x/Foo.framework/Foo"); + scratch.file("x/Foo.framework/Info.plist"); + scratch.file("x/Foo.framework/Headers/Foo.h"); + scratch.file("x/Foo.framework/Resources/bar.png"); + scratch.file( + "x/BUILD", + "objc_framework(", + " name = 'foo_framework',", + " framework_imports = glob(['Foo.framework/**']),", + " is_dynamic = 1,", + ")", + "", + "objc_binary(", + " name = 'bin',", + " srcs = [ 'a.m' ],", + " runtime_deps = [ ':foo_framework' ],", + ")", + "", + "ios_application(", + " name = 'x',", + " binary = ':bin',", + ")"); + + BundleMergeProtos.Control mergeControl = bundleMergeControl("//x:x"); + + assertThat(mergeControl.getBundleFileList()) + .containsAllOf( + BundleFile.newBuilder() + .setBundlePath("Frameworks/Foo.framework/Foo") + .setSourceFile(getSourceArtifact("x/Foo.framework/Foo").getExecPathString()) + .setExternalFileAttribute(BundleableFile.EXECUTABLE_EXTERNAL_FILE_ATTRIBUTE) + .build(), + BundleFile.newBuilder() + .setBundlePath("Frameworks/Foo.framework/Info.plist") + .setSourceFile(getSourceArtifact("x/Foo.framework/Info.plist").getExecPathString()) + .setExternalFileAttribute(BundleableFile.EXECUTABLE_EXTERNAL_FILE_ATTRIBUTE) + .build(), + BundleFile.newBuilder() + .setBundlePath("Frameworks/Foo.framework/Resources/bar.png") + .setSourceFile( + getSourceArtifact("x/Foo.framework/Resources/bar.png").getExecPathString()) + .setExternalFileAttribute(BundleableFile.DEFAULT_EXTERNAL_FILE_ATTRIBUTE) + .build()); + + assertThat(mergeControl.getBundleFileList()) + .doesNotContain( + BundleFile.newBuilder() + .setBundlePath("Frameworks/Foo.framework/Headers/Foo.h") + .setSourceFile( + getSourceArtifact("x/Foo.framework/Headers/Foo.h").getExecPathString()) + .setExternalFileAttribute(BundleableFile.DEFAULT_EXTERNAL_FILE_ATTRIBUTE) + .build()); + } + + @Test + public void testLateloadedObjcFrameworkSigned() throws Exception { + useConfiguration("--cpu=ios_arm64"); + + scratch.file("x/Foo.framework/Foo"); + scratch.file("x/Foo.framework/Info.plist"); + scratch.file("x/Foo.framework/Headers/Foo.h"); + scratch.file("x/Foo.framework/Resources/bar.png"); + scratch.file( + "x/BUILD", + "objc_framework(", + " name = 'foo_framework',", + " framework_imports = glob(['Foo.framework/**']),", + " is_dynamic = 1,", + ")", + "", + "objc_binary(", + " name = 'bin',", + " srcs = [ 'a.m' ],", + " runtime_deps = [ ':foo_framework' ],", + ")", + "", + "ios_application(", + " name = 'x',", + " binary = ':bin',", + ")"); + + SpawnAction signingAction = (SpawnAction) ipaGeneratingAction(); + + assertThat(normalizeBashArgs(signingAction.getArguments())) + .containsAllOf("--sign", "${t}/Payload/x.app/Frameworks/*", "--sign", "${t}/Payload/x.app") + .inOrder(); + } + + @Test + public void aspectOnSplitAttributeRegressionTest() throws Exception { + useConfiguration("--ios_multi_cpus=armv7,arm64"); + scratch.file("x/a.m"); + scratch.file("x/x-Info.plist"); + scratch.file( + "x/extension.bzl", + "def _my_aspect_impl(target, ctx):", + " if type(ctx.rule.attr.binary) != 'list':", + " fail('Expected a list for split')", + " if len(ctx.rule.attr.binary) != 2:", + " fail('Expected 2 items in split')", + " return struct()", + "my_aspect = aspect(_my_aspect_impl)", + "def _my_rule_impl(ctx):", + " pass", + "my_rule = rule(_my_rule_impl, attrs = { 'deps' : attr.label_list(aspects = [my_aspect]) })" + ); + scratch.file("x/BUILD", + "load(':extension.bzl', 'my_rule')", + "objc_binary(name = 'bin', srcs = ['a.m'], )", + "ios_application(name = 'x', binary = ':bin',)", + "my_rule(name = 'y', deps = [ ':x' ])" + ); + getConfiguredTarget("//x:y"); + } + + @Test + public void aspectOnSplitAttributeNoSplitRegressionTest() throws Exception { + useConfiguration("--ios_multi_cpus=arm64"); + scratch.file("x/a.m"); + scratch.file("x/x-Info.plist"); + scratch.file( + "x/extension.bzl", + "def _my_aspect_impl(target, ctx):", + " if type(ctx.rule.attr.binary) != 'list':", + " fail('Expected a list for split')", + " if len(ctx.rule.attr.binary) != 1:", + " fail('Expected 1 items in split')", + " return struct()", + "my_aspect = aspect(_my_aspect_impl)", + "def _my_rule_impl(ctx):", + " pass", + "my_rule = rule(_my_rule_impl, attrs = { 'deps' : attr.label_list(aspects = [my_aspect]) })" + ); + scratch.file("x/BUILD", + "load(':extension.bzl', 'my_rule')", + "objc_binary(name = 'bin', srcs = ['a.m'], )", + "ios_application(name = 'x', binary = ':bin',)", + "my_rule(name = 'y', deps = [ ':x' ])" + ); + getConfiguredTarget("//x:y"); + } + + @Test + public void testMergeBundleActionsWithNestedBundle() throws Exception { + checkMergeBundleActionsWithNestedBundle(RULE_TYPE_PAIR, targetConfig); + } + + @Test + public void testIncludesStoryboardOutputZipsAsMergeZips() throws Exception { + checkIncludesStoryboardOutputZipsAsMergeZips(RULE_TYPE_PAIR, targetConfig); + } + + @Test + public void testCcDependency() throws Exception { + checkCcDependency(RULE_TYPE_PAIR, ConfigurationDistinguisher.IOS_APPLICATION); + } + + @Test + public void testCcDependencyMultiArch() throws Exception { + checkCcDependencyMultiArch(RULE_TYPE_PAIR, ConfigurationDistinguisher.IOS_APPLICATION); + } + + @Test + public void testCCDependencyWithProtoDependency() throws Exception { + checkCcDependencyWithProtoDependency( + RULE_TYPE_PAIR, ConfigurationDistinguisher.IOS_APPLICATION); + } + + @Test + public void testCcDependencyAndJ2objcDependency() throws Exception { + checkCcDependencyAndJ2objcDependency( + RULE_TYPE_PAIR, ConfigurationDistinguisher.IOS_APPLICATION); + } + + @Test + public void testApplicationExtension() throws Exception { + // Including minimum OS version to trigger a special code path in extension split transitions + // which have a higher chance of conflicting with application transitions. See flag + // --DO_NOT_USE_configuration_distinguisher for details. + useConfiguration("--ios_multi_cpus=i386,x86_64", "--ios_minimum_os=8.1"); + DottedVersion minOsString = DottedVersion.fromString("8.1"); + scratch.file( + "x/BUILD", + "ios_extension_binary(", + " name = 'ext_bin',", + " srcs = ['ebin.m'],", + ")", + "", + "ios_extension(", + " name = 'ext',", + " binary = ':ext_bin',", + ")", + "", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + ")", + "", + "ios_application(", + " name = 'app',", + " binary = ':bin',", + " extensions = [':ext'],", + ")"); + + SpawnAction appLipoAction = + (SpawnAction) + getGeneratingAction( + getBinArtifact( + "app_lipobin", getConfiguredTarget("//x:app", getTargetConfiguration()))); + + assertThat(Artifact.toExecPaths(appLipoAction.getInputs())) + .containsExactly( + configurationBin("i386", ConfigurationDistinguisher.IOS_APPLICATION, minOsString) + + "x/bin_bin", + configurationBin("x86_64", ConfigurationDistinguisher.IOS_APPLICATION, minOsString) + + "x/bin_bin", + MOCK_XCRUNWRAPPER_PATH); + + SpawnAction extLipoAction = + (SpawnAction) + getGeneratingAction( + getBinArtifact( + "ext_lipobin", getConfiguredTarget("//x:ext", getTargetConfiguration()))); + + assertThat(Artifact.toExecPaths(extLipoAction.getInputs())) + .containsExactly( + configurationBin("i386", ConfigurationDistinguisher.IOS_EXTENSION, minOsString) + + "x/ext_bin_bin", + configurationBin("x86_64", ConfigurationDistinguisher.IOS_EXTENSION, minOsString) + + "x/ext_bin_bin", MOCK_XCRUNWRAPPER_PATH); + } + + @Test + public void testGenruleDependencyMultiArch() throws Exception { + checkGenruleDependencyMultiArch(RULE_TYPE_PAIR, ConfigurationDistinguisher.IOS_APPLICATION); + } + + @Test + public void testTargetHasCpuSpecificDsymFiles() throws Exception { + checkTargetHasCpuSpecificDsymFiles(RULE_TYPE); + } + + @Test + public void testTargetHasDsymPlist() throws Exception { + checkTargetHasDsymPlist(RULE_TYPE); + } + + @Test + public void testPropagatesDebugSymbolsFromExtensions() throws Exception { + useConfiguration("--ios_multi_cpus=i386,x86_64", "--apple_generate_dsym"); + scratch.file( + "x/BUILD", + "ios_extension_binary(", + " name = 'ext2_bin',", + " srcs = ['ebin.m'],", + ")", + "", + "ios_extension(", + " name = 'ext2',", + " binary = ':ext2_bin',", + ")", + "", + "ios_extension_binary(", + " name = 'ext_bin',", + " srcs = ['ebin.m'],", + ")", + "", + "ios_extension(", + " name = 'ext',", + " binary = ':ext_bin',", + ")", + "", + "apple_watch_extension_binary(", + " name = 'watch_bin',", + " srcs = ['a.m'],", + ")", + "", + "apple_watch1_extension(", + " name = 'watch_ext',", + " app_name = 'y',", + " binary = ':watch_bin',", + ")", + "", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + ")", + "", + "ios_application(", + " name = 'app',", + " binary = ':bin',", + " extensions = [':ext', ':ext2', ':watch_ext'],", + ")"); + + Iterable<Artifact> filesToBuild = + getConfiguredTarget("//x:app").getProvider(FileProvider.class).getFilesToBuild(); + assertThat(filesToBuild) + .containsAllOf( + getBinArtifact("app.app.dSYM/Contents/Resources/DWARF/app_i386", "//x:app"), + getBinArtifact("app.app.dSYM/Contents/Resources/DWARF/app_x86_64", "//x:app"), + getBinArtifact("app.app.dSYM/Contents/Info.plist", "//x:app"), + getBinArtifact("ext.app.dSYM/Contents/Resources/DWARF/ext_i386", "//x:app"), + getBinArtifact("ext.app.dSYM/Contents/Resources/DWARF/ext_x86_64", "//x:app"), + getBinArtifact("ext.app.dSYM/Contents/Info.plist", "//x:app"), + getBinArtifact("ext2.app.dSYM/Contents/Resources/DWARF/ext2_i386", "//x:app"), + getBinArtifact("ext2.app.dSYM/Contents/Resources/DWARF/ext2_x86_64", "//x:app"), + getBinArtifact("ext2.app.dSYM/Contents/Info.plist", "//x:app"), + getBinArtifact("watch_ext.app.dSYM/Contents/Resources/DWARF/watch_ext_i386", "//x:app"), + getBinArtifact( + "watch_ext.app.dSYM/Contents/Resources/DWARF/watch_ext_x86_64", "//x:app"), + getBinArtifact("watch_ext.app.dSYM/Contents/Info.plist", "//x:app")); + } + + @Test + public void testMultiArchitectureFanOut() throws Exception { + checkBinaryLipoActionMultiCpu(RULE_TYPE_PAIR, ConfigurationDistinguisher.IOS_APPLICATION); + } + + @Test + public void testMultiArchitectureWithConfigurableAttribute() throws Exception { + useConfiguration("--ios_multi_cpus=armv7,arm64", "--cpu=ios_i386"); + scratch.file( + "x/BUILD", + "config_setting(", + " name = 'i386',", + " values = {'cpu': 'ios_i386'},", + ")", + "", + "config_setting(", + " name = 'armv7',", + " values = {'cpu': 'ios_armv7'},", + ")", + "", + "objc_library(", + " name = 'libi386',", + " srcs = ['i386.m'],", + ")", + "", + "objc_library(", + " name = 'libarmv7',", + " srcs = ['armv7.m'],", + ")", + "", + "objc_library(", + " name = 'libdefault',", + " srcs = ['default.m'],", + ")", + "", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + " deps = select({", + " ':i386': [':libi386'],", + " ':armv7': [':libarmv7'],", + " '//conditions:default': [':libdefault'],", + " }),", + ")", + "", + "ios_application(", + " name = 'app',", + " binary = ':bin',", + ")"); + + CommandAction appLipoAction = + (CommandAction) + getGeneratingAction( + getBinArtifact( + "app_lipobin", getConfiguredTarget("//x:app", getTargetConfiguration()))); + + assertThat(Artifact.toExecPaths(appLipoAction.getInputs())) + .containsExactly( + configurationBin("armv7", ConfigurationDistinguisher.IOS_APPLICATION) + "x/bin_bin", + configurationBin("arm64", ConfigurationDistinguisher.IOS_APPLICATION) + "x/bin_bin", + MOCK_XCRUNWRAPPER_PATH); + + ImmutableSet.Builder<Artifact> binInputs = ImmutableSet.builder(); + for (Artifact bin : appLipoAction.getInputs()) { + CommandAction binAction = (CommandAction) getGeneratingAction(bin); + if (binAction != null) { + binInputs.addAll(binAction.getInputs()); + } + } + + assertThat(Artifact.toExecPaths(binInputs.build())) + .containsAllOf( + configurationBin("armv7", ConfigurationDistinguisher.IOS_APPLICATION) + + "x/liblibarmv7.a", + configurationBin("arm64", ConfigurationDistinguisher.IOS_APPLICATION) + + "x/liblibdefault.a"); + + assertThat(Artifact.toExecPaths(binInputs.build())) + .doesNotContain( + configurationBin("i386", ConfigurationDistinguisher.IOS_APPLICATION) + + "x/liblibi386.a"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/IosDeviceTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/IosDeviceTest.java new file mode 100644 index 0000000000..49b7540b69 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/IosDeviceTest.java @@ -0,0 +1,172 @@ +// 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.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget; +import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; +import com.google.devtools.build.lib.rules.apple.AppleCommandLineOptions; +import com.google.devtools.build.lib.rules.apple.DottedVersion; +import com.google.devtools.build.lib.rules.apple.XcodeVersionProperties; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for the {@code ios_device} rule. + */ +@RunWith(JUnit4.class) +public class IosDeviceTest extends BuildViewTestCase { + + @Test + public void testIosVersion_specified() throws Exception { + scratch.file("test/BUILD", + "ios_device(name = 'foo', ios_version = '42.0', type = 'IPHONE_6',)"); + + assertIosVersion("//test:foo", "42.0"); + } + + @Test + public void testIosVersion_default() throws Exception { + scratch.file("test/BUILD", + "ios_device(name = 'foo', type = 'IPHONE_6',)"); + + assertIosVersion("//test:foo", AppleCommandLineOptions.DEFAULT_IOS_SDK_VERSION); + } + + @Test + public void testIosVersion_flagValue() throws Exception { + scratch.file("test/BUILD", + "ios_device(name = 'foo', type = 'IPHONE_6',)"); + useConfiguration("--ios_sdk_version=42.3"); + + assertIosVersion("//test:foo", "42.3"); + } + + /** + * Tests that if {@code ios_device} specifies an xcode version that does not specify a {@code + * default_ios_sdk_version}, the ios sdk version of the device defaults to the default value of + * {@code default_ios_sdk_version} instead of the build configuration value. This is a confusing + * (perhaps convoluted) corner case. + */ + @Test + public void testXcodeVersion_noIosVersion() throws Exception { + scratch.file("test/BUILD", + "xcode_version(name = 'my_xcode', version = '15.2')", + "ios_device(name = 'foo', type = 'IPHONE_6', xcode = ':my_xcode')"); + useConfiguration("--xcode_version=2.1", "--ios_sdk_version=42.3"); + + assertXcodeVersion("//test:foo", "15.2"); + assertIosVersion("//test:foo", XcodeVersionProperties.DEFAULT_IOS_SDK_VERSION); + } + + @Test + public void testXcodeVersion_withIosVersion() throws Exception { + scratch.file("test/BUILD", + "xcode_version(name = 'my_xcode', version = '15.2', default_ios_sdk_version='17.8')", + "ios_device(name = 'foo', type = 'IPHONE_6', xcode = ':my_xcode')"); + useConfiguration("--xcode_version=2.1", "--ios_sdk_version=42.3"); + + assertXcodeVersion("//test:foo", "15.2"); + assertIosVersion("//test:foo", "17.8"); + } + + @Test + public void testXcodeVersion_iosVersionOverride() throws Exception { + scratch.file("test/BUILD", + "xcode_version(name = 'my_xcode', version = '15.2', default_ios_sdk_version='17.8')", + "ios_device(name = 'foo', type = 'IPHONE_6', ios_version='98.7', xcode = ':my_xcode')"); + useConfiguration("--xcode_version=2.1", "--ios_sdk_version=42.3"); + + assertXcodeVersion("//test:foo", "15.2"); + assertIosVersion("//test:foo", "98.7"); + } + + @Test + public void testType() throws Exception { + scratch.file("test/BUILD", + "ios_device(name = 'foo', type = 'IPHONE_6',)"); + + assertThat(view.hasErrors(getConfiguredTarget("//test:foo"))).isFalse(); + + ConfiguredTarget target = getConfiguredTarget("//test:foo"); + IosDeviceProvider provider = + (IosDeviceProvider) target.get(IosDeviceProvider.SKYLARK_CONSTRUCTOR.getKey()); + assertThat(provider.getType()).isEqualTo("IPHONE_6"); + } + + @Test + public void testIosDeviceAttributesCanBeReadFromSkylark() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def my_rule_impl(ctx):", + " ios_device_attr = ctx.attr.ios_device[apple_common.IosDevice]", + " return struct(", + " xcode_version=ios_device_attr.xcode_version,", + " ios_version=ios_device_attr.ios_version,", + " type=ios_device_attr.type", + " )", + "my_rule = rule(implementation = my_rule_impl,", + " attrs = {", + " 'ios_device': attr.label(),", + " },", + ")"); + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'my_rule')", + "my_rule(", + " name = 'my_target',", + " ios_device = ':my_device',", + ")", + "ios_device(", + " name = 'my_device',", + " type = 'IPHONE_8',", + " xcode = ':my_xcode',", + " ios_version='98.7'", + ")", + "xcode_version(", + " name = 'my_xcode',", + " version = '15.2'", + ")"); + + RuleConfiguredTarget skylarkTarget = + (RuleConfiguredTarget) getConfiguredTarget("//examples/apple_skylark:my_target"); + assertThat((String) skylarkTarget.get("xcode_version")).isEqualTo("15.2"); + assertThat((String) skylarkTarget.get("type")).isEqualTo("IPHONE_8"); + assertThat((String) skylarkTarget.get("ios_version")).isEqualTo("98.7"); + } + + private void assertXcodeVersion(String label, String version) throws Exception { + assertThat(view.hasErrors(getConfiguredTarget(label))).isFalse(); + + ConfiguredTarget target = getConfiguredTarget(label); + IosDeviceProvider provider = + (IosDeviceProvider) target.get(IosDeviceProvider.SKYLARK_CONSTRUCTOR.getKey()); + assertThat(provider.getXcodeVersion()).isEqualTo(DottedVersion.fromString(version)); + } + + private void assertIosVersion(String label, String version) throws Exception { + assertThat(view.hasErrors(getConfiguredTarget(label))).isFalse(); + + ConfiguredTarget target = getConfiguredTarget(label); + IosDeviceProvider provider = + (IosDeviceProvider) target.get(IosDeviceProvider.SKYLARK_CONSTRUCTOR.getKey()); + assertThat(provider.getIosVersion()).isEqualTo(DottedVersion.fromString(version)); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/IosExtensionBinaryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/IosExtensionBinaryTest.java new file mode 100644 index 0000000000..1de925d0e4 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/IosExtensionBinaryTest.java @@ -0,0 +1,211 @@ +// 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 static com.google.devtools.build.lib.rules.objc.BinaryLinkingTargetFactory.REQUIRES_AT_LEAST_ONE_LIBRARY_OR_SOURCE_FILE; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.ASSET_CATALOG; + +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.RunfilesProvider; +import com.google.devtools.build.lib.analysis.config.CompilationMode; +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.objc.CompilationSupport.ExtraLinkArgs; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for ios_extension_binary. */ +@RunWith(JUnit4.class) +public class IosExtensionBinaryTest extends ObjcRuleTestCase { + static final RuleType RULE_TYPE = new OnlyNeedsSourcesRuleType("ios_extension_binary"); + protected static final ExtraLinkArgs EXTRA_LINK_ARGS = + new ExtraLinkArgs("-e", "_NSExtensionMain", "-fapplication-extension"); + + @Before + public final void initializeToolsConfigMock() throws Exception { + MockProtoSupport.setup(mockToolsConfig); + MockObjcSupport.setupObjcProto(mockToolsConfig); + } + + @Test + public void testCreate_runfiles() throws Exception { + scratch.file("x/a.m"); + RULE_TYPE.scratchTarget(scratch, "srcs", "['a.m']"); + ConfiguredTarget binary = getConfiguredTarget("//x:x"); + RunfilesProvider runfiles = binary.getProvider(RunfilesProvider.class); + assertThat(runfiles.getDefaultRunfiles().getArtifacts()).isEmpty(); + assertThat(Artifact.toRootRelativePaths(runfiles.getDataRunfiles().getArtifacts())) + .containsExactly("x/x_bin"); + } + + @Test + public void testCreate_errorForNoSourceOrDep() throws Exception { + checkError("x", "x", REQUIRES_AT_LEAST_ONE_LIBRARY_OR_SOURCE_FILE, + "ios_extension_binary(name='x')"); + } + + @Test + public void testCompileWithDotMFileInHeaders() throws Exception { + checkCompileWithDotMFileInHeaders(RULE_TYPE); + } + + @Test + public void testObjcProviderExportsAssetCatalogs() throws Exception { + scratch.file("x/BUILD", + "ios_extension_binary(", + " name = 'x',", + " srcs = ['a.m'],", + " asset_catalogs = ['foo.xcassets/bar', 'foo.xcassets/baz'],", + ")"); + ObjcProvider provider = providerForTarget("//x:x"); + assertThat(provider.get(ASSET_CATALOG)) + .containsExactly( + getSourceArtifact("x/foo.xcassets/bar"), + getSourceArtifact("x/foo.xcassets/baz")); + } + + @Test + public void testLinksFrameworksOfSelfAndTransitiveDependencies() throws Exception { + checkLinksFrameworksOfSelfAndTransitiveDependencies(RULE_TYPE); + } + + @Test + public void testLinksWeakFrameworksOfSelfAndTransitiveDependencies() throws Exception { + checkLinksWeakFrameworksOfSelfAndTransitiveDependencies(RULE_TYPE); + } + + @Test + public void testLinksDylibsTransitively() throws Exception { + checkLinksDylibsTransitively(RULE_TYPE); + } + + @Test + public void testPopulatesCompilationArtifacts() throws Exception { + checkPopulatesCompilationArtifacts(RULE_TYPE); + } + + @Test + public void testArchivesPrecompiledObjectFiles() throws Exception { + checkArchivesPrecompiledObjectFiles(RULE_TYPE); + } + + @Test + public void testErrorsWrongFileTypeForSrcsWhenCompiling() throws Exception { + checkErrorsWrongFileTypeForSrcsWhenCompiling(RULE_TYPE); + } + + @Test + public void testObjcCopts() throws Exception { + checkObjcCopts(RULE_TYPE); + } + + @Test + public void testObjcCopts_argumentOrdering() throws Exception { + checkObjcCopts_argumentOrdering(RULE_TYPE); + } + + @Test + public void testAllowVariousNonBlacklistedTypesInHeaders() throws Exception { + checkAllowVariousNonBlacklistedTypesInHeaders(RULE_TYPE); + } + + @Test + public void testWarningForBlacklistedTypesInHeaders() throws Exception { + checkWarningForBlacklistedTypesInHeaders(RULE_TYPE); + } + + @Test + public void testCppSourceCompilesWithCppFlags() throws Exception { + checkCppSourceCompilesWithCppFlags(RULE_TYPE); + } + + @Test + public void testLinkOpts() throws Exception { + checkLinkopts(RULE_TYPE); + } + + @Test + public void testProtoBundlingAndLinking() throws Exception { + checkProtoBundlingAndLinking(RULE_TYPE); + } + + @Test + public void testProtoBundlingWithTargetsWithNoDeps() throws Exception { + checkProtoBundlingWithTargetsWithNoDeps(RULE_TYPE); + } + + @Test + public void testLinkingRuleCanUseCrosstool() throws Exception { + checkLinkingRuleCanUseCrosstool(RULE_TYPE); + } + + @Test + public void testBinaryStrippings() throws Exception { + checkBinaryStripAction(RULE_TYPE); + } + + @Test + public void testCompilationActionsForDebug() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.DBG, CodeCoverageMode.NONE); + } + + @Test + public void testCompilationActionsForOptimized() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.OPT, CodeCoverageMode.NONE); + } + + @Test + public void testClangCoptsForDebugModeWithoutGlib() throws Exception { + checkClangCoptsForDebugModeWithoutGlib(RULE_TYPE); + } + + @Test + public void testLinkActionCorrect() throws Exception { + checkLinkActionCorrect(RULE_TYPE, EXTRA_LINK_ARGS); + } + + @Test + public void testFrameworkDepLinkFlags() throws Exception { + checkFrameworkDepLinkFlags(RULE_TYPE, EXTRA_LINK_ARGS); + } + + @Test + public void testCompilationActionsForDebugInGcovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.DBG, + CodeCoverageMode.GCOV); + } + + @Test + public void testCompilationActionsForDebugInLlvmCovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.DBG, + CodeCoverageMode.LLVMCOV); + } + + @Test + public void testCompilationActionsForOptimizedInGcovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.OPT, + CodeCoverageMode.GCOV); + } + + @Test + public void testCompilationActionsForOptimizedInLlvmCovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.OPT, + CodeCoverageMode.LLVMCOV); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/IosExtensionTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/IosExtensionTest.java new file mode 100644 index 0000000000..af7f0f1f89 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/IosExtensionTest.java @@ -0,0 +1,638 @@ +// 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 static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.APP_ICON_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.LAUNCH_IMAGE_ATTR; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multiset; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.rules.apple.AppleConfiguration.ConfigurationDistinguisher; +import com.google.devtools.build.lib.rules.apple.DottedVersion; +import com.google.devtools.build.lib.testutil.Scratch; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.BundleFile; +import java.io.IOException; +import java.util.List; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for ios_extension. */ +@RunWith(JUnit4.class) +public class IosExtensionTest extends ObjcRuleTestCase { + protected static final RuleType RULE_TYPE = + new RuleType("ios_extension") { + @Override + Iterable<String> requiredAttributes( + Scratch scratch, String packageDir, Set<String> alreadyAdded) throws IOException { + ImmutableList.Builder<String> attributes = new ImmutableList.Builder<>(); + if (!alreadyAdded.contains("binary")) { + scratch.file(packageDir + "/extension_binary/a.m"); + scratch.file( + packageDir + "/extension_binary/BUILD", + "ios_extension_binary(", + " name = 'extension_binary',", + " srcs = ['a.m'],", + ")"); + attributes.add(String.format("binary = '//%s/extension_binary'", packageDir)); + } + return attributes.build(); + } + }; + + protected static final BinaryRuleTypePair RULE_TYPE_PAIR = + new BinaryRuleTypePair( + IosExtensionBinaryTest.RULE_TYPE, + RULE_TYPE, + ReleaseBundlingSupport.EXTENSION_BUNDLE_DIR_FORMAT); + + private ConfiguredTarget addMockExtensionAndLibs(String... extraExtAttributes) + throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + scratch.file("x/a.m"); + scratch.file("x/BUILD", + "ios_extension_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " deps = ['//lib1:lib1', '//lib2:lib2'],", + ")", + "", + "ios_extension(", + " name = 'x',", + " binary = ':bin',", + Joiner.on(',').join(extraExtAttributes), + ")"); + return getConfiguredTarget("//x:x"); + } + + @Test + public void testSigningAction() throws Exception { + checkDeviceSigningAction(RULE_TYPE); + } + + @Test + public void testSigningWithCertName() throws Exception { + checkSigningWithCertName(RULE_TYPE); + } + + @Test + public void testPostProcessingAction() throws Exception { + checkPostProcessingAction(RULE_TYPE); + } + + @Test + public void testSigningAndPostProcessing() throws Exception { + checkSigningAndPostProcessing(RULE_TYPE); + } + + @Test + public void testSigning_simulatorBuild() throws Exception { + checkSigningSimulatorBuild(RULE_TYPE_PAIR, false); + } + + @Test + public void testSigning_simulatorBuild_multiCpu() throws Exception { + checkSigningSimulatorBuild(RULE_TYPE_PAIR, true); + } + + @Test + public void testProvisioningProfile_deviceBuild() throws Exception { + checkProvisioningProfileDeviceBuild(RULE_TYPE_PAIR, false); + } + + @Test + public void testProvisioningProfile_deviceBuild_multiCpu() throws Exception { + checkProvisioningProfileDeviceBuild(RULE_TYPE_PAIR, true); + } + + @Test + public void testUserSpecifiedProvisioningProfile_deviceBuild() throws Exception { + checkProvisioningProfileUserSpecified(RULE_TYPE_PAIR, false); + } + + @Test + public void testUserSpecifiedProvisioningProfile_deviceBuild_multiCpu() throws Exception { + checkProvisioningProfileUserSpecified(RULE_TYPE_PAIR, true); + } + + @Test + public void testMergeControlAction() throws Exception { + addMockExtensionAndLibs("infoplist = 'Info.plist'"); + Action mergeAction = bundleMergeAction("//x:x"); + Action action = bundleMergeControlAction("//x:x"); + assertThat(action.getInputs()).isEmpty(); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())).containsExactly( + "x/x.ipa-control"); + assertThat(bundleMergeControl("//x:x")) + .isEqualTo( + BundleMergeProtos.Control.newBuilder() + .addBundleFile( + BundleFile.newBuilder() + .setSourceFile(execPathEndingWith(mergeAction.getInputs(), "x_lipobin")) + .setBundlePath("x") + .setExternalFileAttribute(BundleableFile.EXECUTABLE_EXTERNAL_FILE_ATTRIBUTE) + .build()) + .setBundleRoot("PlugIns/x.appex") + .setBundleInfoPlistFile( + getMergedInfoPlist(getConfiguredTarget("//x:x")).getExecPathString()) + .setOutFile(execPathEndingWith(mergeAction.getOutputs(), "x.unprocessed.ipa")) + .setMinimumOsVersion(DEFAULT_IOS_SDK_VERSION.toString()) + .setSdkVersion(DEFAULT_IOS_SDK_VERSION.toString()) + .setPlatform("IOS_SIMULATOR") + .setFallbackBundleIdentifier("example.x") + .build()); + } + + @Test + public void testMergeBundleAction() throws Exception { + checkMergeBundleAction(RULE_TYPE_PAIR); + } + + protected List<BuildConfiguration> getExtensionConfigurations() throws InterruptedException { + return getSplitConfigurations(getTargetConfiguration(), + IosExtension.MINIMUM_OS_AND_SPLIT_ARCH_TRANSITION); + } + + @Test + public void testErrorForLaunchImageGivenWithNoAssetCatalog() throws Exception { + checkAssetCatalogAttributeError(RULE_TYPE, LAUNCH_IMAGE_ATTR); + } + + @Test + public void testErrorForAppIconGivenWithNoAssetCatalog() throws Exception { + checkAssetCatalogAttributeError(RULE_TYPE, APP_ICON_ATTR); + } + + @Test + public void testCollectsAssetCatalogsTransitively() throws Exception { + checkCollectsAssetCatalogsTransitively(RULE_TYPE_PAIR); + } + + @Test + public void testSpecifyAppIconAndLaunchImageUsingXcassetsOfDependency() throws Exception { + checkSpecifyAppIconAndLaunchImageUsingXcassetsOfDependency( + RULE_TYPE_PAIR, DEFAULT_IOS_SDK_VERSION); + } + + private void addTargetWithAssetCatalogs() throws IOException { + scratch.file("x/foo.xcassets/foo"); + scratch.file("x/foo.xcassets/bar"); + scratch.file("x/a.m"); + scratch.file("x/BUILD", + "ios_extension_binary(", + " name = 'bin',", + " asset_catalogs = ['foo.xcassets/foo', 'bar.xcassets/bar'],", + " srcs = ['a.m'],", + ")", + "", + "ios_extension(", + " name = 'x',", + " binary = ':bin',", + ")"); + } + + @Test + public void testActoolActionCorrectness() throws Exception { + addTargetWithAssetCatalogs(); + checkActoolActionCorrectness(DEFAULT_IOS_SDK_VERSION); + } + + @Test + public void testPassesFamiliesToActool() throws Exception { + checkPassesFamiliesToActool(RULE_TYPE_PAIR); + } + + @Test + public void testPassesFamiliesToIbtool() throws Exception { + checkPassesFamiliesToIbtool(RULE_TYPE_PAIR); + } + + @Test + public void testReportsErrorsForInvalidFamiliesAttribute() throws Exception { + checkReportsErrorsForInvalidFamiliesAttribute(RULE_TYPE); + } + + @Test + public void testMergeActionsWithAssetCatalog() throws Exception { + addTargetWithAssetCatalogs(); + checkMergeActionsWithAssetCatalog(RULE_TYPE_PAIR); + } + + private void addBinAndLibWithRawResources() throws Exception { + addBinAndLibWithResources( + "resources", "resource1.txt", "ja.lproj/resource2.txt", "ios_extension_binary"); + scratch.file("x/BUILD", + "ios_extension(", + " name = 'x',", + " binary = '//bin:bin',", + ")"); + } + + private void addBinAndLibWithStrings() throws Exception { + addBinAndLibWithResources( + "strings", "foo.strings", "ja.lproj/bar.strings", "ios_extension_binary"); + scratch.file("x/BUILD", + "ios_extension(", + " name = 'x',", + " binary = '//bin:bin',", + ")"); + } + + @Test + public void testCollectsRawResourceFilesTransitively() throws Exception { + addBinAndLibWithRawResources(); + checkCollectsResourceFilesTransitively( + "//x:x", + ImmutableList.of("lib/resource1.txt", "bin/ja.lproj/resource2.txt"), + ImmutableList.of("lib/resource1.txt"), + ImmutableSetMultimap.<String, Multiset<String>>of( + "bin_bin", ImmutableMultiset.of("bin/ja.lproj/resource2.txt", "lib/resource1.txt"), + "x_x", ImmutableMultiset.of("bin/ja.lproj/resource2.txt", "lib/resource1.txt"), + "lib_lib", ImmutableMultiset.of("lib/resource1.txt"))); + } + + @Test + public void testCollectsStringsFilesTransitively() throws Exception { + addBinAndLibWithStrings(); + checkCollectsResourceFilesTransitively( + "//x:x", + ImmutableList.of("x/lib/foo.strings.binary", "x/bin/ja.lproj/bar.strings.binary"), + ImmutableList.of("lib/foo.strings.binary"), + ImmutableSetMultimap.<String, Multiset<String>>of( + "bin_bin", ImmutableMultiset.of("bin/ja.lproj/bar.strings", "lib/foo.strings"), + "x_x", ImmutableMultiset.of("bin/ja.lproj/bar.strings", "lib/foo.strings"), + "lib_lib", ImmutableMultiset.of("lib/foo.strings"))); + } + + @Test + public void testResourceFilesMergedInBundle() throws Exception { + addBinAndLibWithRawResources(); + checkBundleablesAreMerged("//x:x", + ImmutableListMultimap.of( + "resource1.txt", "resource1.txt", + "ja.lproj/resource2.txt", "ja.lproj/resource2.txt")); + } + + @Test + public void testStringsFilesMergedInBundle() throws Exception { + addBinAndLibWithStrings(); + checkBundleablesAreMerged("//x:x", + ImmutableListMultimap.of( + "foo.strings.binary", "foo.strings", + "ja.lproj/bar.strings.binary", "ja.lproj/bar.strings")); + } + + @Test + public void testMergesXcdatamodelZips() throws Exception { + checkMergesXcdatamodelZips(RULE_TYPE_PAIR); + } + + @Test + public void testPlistRequiresDotInName() throws Exception { + checkError("x", "x", + "'//x:Infoplist' does not produce any ios_extension infoplist files (expected .plist)", + "ios_extension_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + ")", + "", + "ios_extension(", + " name = 'x',", + " infoplist = 'Infoplist',", + " binary = ':bin',", + ")"); + } + + @Test + public void testMergesPartialInfoplists() throws Exception { + checkMergesPartialInfoplists(RULE_TYPE_PAIR); + } + + @Test + public void testNibZipsMergedIntoBundle() throws Exception { + checkNibZipsMergedIntoBundle(RULE_TYPE_PAIR); + } + + @Test + public void testNoEntitlementsDefined() throws Exception { + checkNoEntitlementsDefined(RULE_TYPE); + } + + @Test + public void testEntitlementsDefined() throws Exception { + checkEntitlementsDefined(RULE_TYPE); + } + + @Test + public void testExtraEntitlements() throws Exception { + checkExtraEntitlements(RULE_TYPE); + } + + @Test + public void testDebugEntitlements() throws Exception { + checkDebugEntitlements(RULE_TYPE); + } + + @Test + public void testFastbuildDebugEntitlements() throws Exception { + checkFastbuildDebugEntitlements(RULE_TYPE); + } + + @Test + public void testOptNoDebugEntitlements() throws Exception { + checkOptNoDebugEntitlements(RULE_TYPE); + } + + @Test + public void testExplicitNoDebugEntitlements() throws Exception { + checkExplicitNoDebugEntitlements(RULE_TYPE); + } + + @Test + public void testPassesFallbackBundleIdToBundleMerging() throws Exception { + checkBundleIdPassedAsFallbackId(RULE_TYPE); + } + + @Test + public void testPassesPrimaryBundleIdToBundleMerging() throws Exception { + checkBundleIdPassedAsPrimaryId(RULE_TYPE); + } + + @Test + public void testMultiPlatformBuild_fails() throws Exception { + checkBinaryActionMultiPlatform_fails(RULE_TYPE_PAIR); + } + + @Test + public void testMultiArchitectureResources() throws Exception { + checkMultiCpuResourceInheritance(RULE_TYPE_PAIR); + } + + @Test + public void testMultiCpuCompiledResources() throws Exception { + checkMultiCpuCompiledResources(RULE_TYPE_PAIR); + } + + @Test + public void testMomczipActions() throws Exception { + checkMomczipActions(RULE_TYPE_PAIR, DEFAULT_IOS_SDK_VERSION); + } + + @Test + public void testConvertStringsActions() throws Exception { + checkConvertStringsAction(RULE_TYPE_PAIR); + } + + @Test + public void testCompileXibActions() throws Exception { + checkCompileXibActions(RULE_TYPE_PAIR, DEFAULT_IOS_SDK_VERSION, "iphone"); + } + + @Test + public void testRegistersStoryboardCompileActions() throws Exception { + checkRegistersStoryboardCompileActions( + RULE_TYPE_PAIR, DEFAULT_IOS_SDK_VERSION, "iphone"); + } + + @Test + public void testMultiCpuCompiledResourcesFromGenrule() throws Exception { + checkMultiCpuCompiledResourcesFromGenrule(RULE_TYPE_PAIR); + } + + @Test + public void testMultiCpuGeneratedResourcesFromGenrule() throws Exception { + checkMultiCpuGeneratedResourcesFromGenrule(RULE_TYPE_PAIR); + } + + @Test + public void testTwoStringsOneBundlePath() throws Exception { + checkTwoStringsOneBundlePath(RULE_TYPE_PAIR, "x"); + } + + @Test + public void testTwoResourcesOneBundlePath() throws Exception { + checkTwoResourcesOneBundlePath(RULE_TYPE_PAIR, "x"); + } + + @Test + public void testSameStringsTwice() throws Exception { + checkSameStringsTwice(RULE_TYPE_PAIR, "x"); + } + + @Test + public void testExtensionReplacesMinimumOsInBundleMerge() throws Exception { + useConfiguration("--ios_minimum_os=7.1"); + addMockExtensionAndLibs("infoplist = 'Info.plist'"); + + assertThat(bundleMergeControl("//x:x").getMinimumOsVersion()) + .isEqualTo(IosExtension.EXTENSION_MINIMUM_OS_VERSION.toString()); + } + + @Test + public void testExtensionReplacesMinimumOsVersionInBundleMergeAtMost80() throws Exception { + useConfiguration("--ios_minimum_os=8.1"); + addMockExtensionAndLibs("infoplist = 'Info.plist'"); + + assertThat(bundleMergeControl("//x:x").getMinimumOsVersion()) + .isEqualTo("8.1"); + } + + @Test + public void testCheckPrimaryBundleIdInMergedPlist() throws Exception { + checkPrimaryBundleIdInMergedPlist(RULE_TYPE_PAIR); + } + + @Test + public void testCheckFallbackBundleIdInMergedPlist() throws Exception { + checkFallbackBundleIdInMergedPlist(RULE_TYPE_PAIR); + } + + protected void checkExtensionReplacesMinimumOsInCompilation() throws Exception { + addMockExtensionAndLibs("infoplist = 'Info.plist'"); + + Action lipoAction = lipoBinAction("//x:x"); + + for (Artifact bin : lipoAction.getInputs()) { + CommandAction action = (CommandAction) getGeneratingAction(bin); + if (action == null) { + continue; + } + assertThat(generatingArgumentsToString(action)) + .contains("-mios-simulator-version-min=" + IosExtension.EXTENSION_MINIMUM_OS_VERSION); + assertThat(generatingArgumentsToString(action)) + .doesNotContain("-mios-simulator-version-min=7.1"); + } + } + + private String generatingArgumentsToString(CommandAction generatingAction) { + return Joiner.on(' ').join(generatingAction.getArguments()); + } + + protected void checkExtensionDoesNotReplaceMinimumOsInCompilation() throws Exception { + addMockExtensionAndLibs("infoplist = 'Info.plist'"); + + Action lipoAction = lipoBinAction("//x:x"); + + for (Artifact bin : lipoAction.getInputs()) { + CommandAction action = (CommandAction) getGeneratingAction(bin); + if (action == null) { + continue; + } + assertThat(generatingArgumentsToString(action)).contains("-mios-simulator-version-min=8.1"); + assertThat(generatingArgumentsToString(action)) + .doesNotContain("-mios-simulator-version-min=" + DEFAULT_IOS_SDK_VERSION); + } + } + + @Test + public void testExtensionReplacesMinimumOsVersionInMomcZipAtMost80() throws Exception { + useConfiguration("--ios_minimum_os=8.1"); + checkMomczipActions(RULE_TYPE_PAIR, DottedVersion.fromString("8.1")); + } + + @Test + public void testGenruleWithoutJavaCcDeps() throws Exception { + checkGenruleWithoutJavaCcDependency(RULE_TYPE_PAIR); + } + + @Test + public void testCcDependencyWithProtoDependencyMultiArch() throws Exception { + checkCcDependencyWithProtoDependencyMultiArch( + RULE_TYPE_PAIR, ConfigurationDistinguisher.IOS_EXTENSION); + } + + @Test + public void testLaunchStoryboardIncluded() throws Exception { + checkLaunchStoryboardIncluded(RULE_TYPE_PAIR); + } + + @Test + public void testLaunchStoryboardXibIncluded() throws Exception { + checkLaunchStoryboardXib(RULE_TYPE_PAIR); + } + + @Test + public void testLaunchStoryboardLproj() throws Exception { + checkLaunchStoryboardLproj(RULE_TYPE_PAIR); + } + + @Test + public void testAutomaticPlistEntries() throws Exception { + checkAutomaticPlistEntries(RULE_TYPE); + } + + @Test + public void testBundleMergeInputContainsPlMergeOutput() throws Exception { + checkBundleMergeInputContainsPlMergeOutput(RULE_TYPE); + } + + @Test + public void testMergeBundleActionsWithNestedBundle() throws Exception { + BuildConfiguration extensionConfiguration = + Iterables.getOnlyElement(getExtensionConfigurations()); + checkMergeBundleActionsWithNestedBundle(RULE_TYPE_PAIR, extensionConfiguration); + } + + @Test + public void testIncludesStoryboardOutputZipsAsMergeZips() throws Exception { + BuildConfiguration extensionConfiguration = + Iterables.getOnlyElement(getExtensionConfigurations()); + checkIncludesStoryboardOutputZipsAsMergeZips(RULE_TYPE_PAIR, extensionConfiguration); + } + + @Test + public void testCcDependency() throws Exception { + checkCcDependency(RULE_TYPE_PAIR, ConfigurationDistinguisher.IOS_EXTENSION); + } + + @Test + public void testCcDependencyMultiArch() throws Exception { + checkCcDependencyMultiArch(RULE_TYPE_PAIR, ConfigurationDistinguisher.IOS_EXTENSION); + } + + @Test + public void testCcDependencyWithProtoDependency() throws Exception { + checkCcDependencyWithProtoDependency(RULE_TYPE_PAIR, ConfigurationDistinguisher.IOS_EXTENSION); + } + + @Test + public void testCcDependencyAndJ2objcDependency() throws Exception { + checkCcDependencyAndJ2objcDependency(RULE_TYPE_PAIR, ConfigurationDistinguisher.IOS_EXTENSION); + } + + @Test + public void testMultiArchitectureFanOut() throws Exception { + checkBinaryLipoActionMultiCpu(RULE_TYPE_PAIR, ConfigurationDistinguisher.IOS_EXTENSION); + } + + @Test + public void testTargetHasCpuSpecificDsymFiles() throws Exception { + checkTargetHasCpuSpecificDsymFiles(RULE_TYPE); + } + + @Test + public void testTargetHasDsymPlist() throws Exception { + checkTargetHasDsymPlist(RULE_TYPE); + } + + @Test + public void testGenruleDependencyMultiArch() throws Exception { + checkGenruleDependencyMultiArch(RULE_TYPE_PAIR, ConfigurationDistinguisher.IOS_EXTENSION); + } + + @Test + public void testExtensionReplacesMinimumOsInCompilation() throws Exception { + useConfiguration("--ios_minimum_os=7.1"); + checkExtensionReplacesMinimumOsInCompilation(); + } + + @Test + public void testExtensionReplacesMinimumOsInCompilationAtMost80() throws Exception { + useConfiguration("--ios_minimum_os=8.1"); + checkExtensionDoesNotReplaceMinimumOsInCompilation(); + } + + @Test + public void testExtensionReplacesMinimumOsInCompilationMultiArch() throws Exception { + useConfiguration("--ios_minimum_os=7.1", "--ios_multi_cpus=i386,x86_64"); + checkExtensionReplacesMinimumOsInCompilation(); + } + + @Test + public void testExtensionReplacesMinimumOsInCompilationAtMost80MultiArch() throws Exception { + useConfiguration("--ios_minimum_os=8.1", "--ios_multi_cpus=i386,x86_64"); + checkExtensionDoesNotReplaceMinimumOsInCompilation(); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/IosTestTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/IosTestTest.java new file mode 100644 index 0000000000..1c93df5bc1 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/IosTestTest.java @@ -0,0 +1,1231 @@ +// 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.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.FileProvider; +import com.google.devtools.build.lib.analysis.RunfilesProvider; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution; +import com.google.devtools.build.lib.analysis.config.CompilationMode; +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.AppleConfiguration.ConfigurationDistinguisher; +import com.google.devtools.build.lib.rules.apple.XcodeVersionProperties; +import com.google.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider; +import com.google.devtools.build.lib.rules.test.TestProvider; +import com.google.devtools.build.lib.rules.test.TestRunnerAction; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.xcode.plmerge.proto.PlMergeProtos; +import java.util.List; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for ios_test. */ +@RunWith(JUnit4.class) +public class IosTestTest extends ObjcRuleTestCase { + protected static final RuleType RULE_TYPE = new BinaryRuleType("ios_test"); + + @Before + public final void setUpToolsConfigMock() throws Exception { + MockObjcSupport.setupIosTest(mockToolsConfig); + MockObjcSupport.setupIosSimDevice(mockToolsConfig); + MockProtoSupport.setup(mockToolsConfig); + MockObjcSupport.setupObjcProto(mockToolsConfig); + + invalidatePackages(); + } + + @Test + public void testRunfiles() throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + ConfiguredTarget target = addSimpleIosTest( + "bin", "bin", ImmutableList.of("src.m"), ImmutableList.of("//lib1:lib1", "//lib2:lib2")); + + ImmutableList<String> expectedRunfiles = + ImmutableList.of( + "bin/bin.ipa", + "tools/objc/xctest_app.ipa", + "bin/bin_test_script", + "tools/objc/StdRedirect.dylib", + "tools/objc/testrunner"); + + RunfilesProvider runfiles = target.getProvider(RunfilesProvider.class); + assertThat(Artifact.toRootRelativePaths(runfiles.getDefaultRunfiles().getArtifacts())) + .containsExactlyElementsIn(expectedRunfiles); + assertThat(Artifact.toRootRelativePaths(runfiles.getDataRunfiles().getArtifacts())) + .containsExactlyElementsIn(expectedRunfiles); + } + + @Test + public void testRunfilesInCoverage() throws Exception { + useConfiguration("--collect_code_coverage", "--instrument_test_targets"); + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + ConfiguredTarget target = + addSimpleIosTest( + "bin", + "bin", + ImmutableList.of("src.m"), + ImmutableList.of("//lib1:lib1", "//lib2:lib2")); + + ImmutableList<String> expectedRunfiles = + ImmutableList.of( + "tools/objc/mcov", + "bin/src.m", + "lib1/a.m", + "lib1/b.m", + "lib2/a.m", + "lib2/b.m", + "tools/objc/objc_dummy.mm", + "lib1/private.h", + "lib1/hdr.h", + "lib2/private.h", + "lib2/hdr.h"); + + RunfilesProvider runfiles = target.getProvider(RunfilesProvider.class); + assertThat(Artifact.toRootRelativePaths(runfiles.getDefaultRunfiles().getArtifacts())) + .containsAllIn(expectedRunfiles); + assertThat(Artifact.toRootRelativePaths(runfiles.getDataRunfiles().getArtifacts())) + .containsAllIn(expectedRunfiles); + } + + @Test + public void testInstrumentedFilesInCoverage() throws Exception { + useConfiguration("--collect_code_coverage", "--instrument_test_targets"); + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + + ConfiguredTarget target = + addSimpleIosTest( + "bin", + "bin", + ImmutableList.of("src.m"), + ImmutableList.of("//lib1:lib1", "//lib2:lib2")); + + InstrumentedFilesProvider instrumentedFilesProvider = + target.getProvider(InstrumentedFilesProvider.class); + assertThat(Artifact.toRootRelativePaths(instrumentedFilesProvider.getInstrumentedFiles())) + .containsExactly( + "bin/src.m", + "lib1/a.m", + "lib1/b.m", + "lib2/a.m", + "lib2/b.m", + "tools/objc/objc_dummy.mm", + "lib1/private.h", + "lib1/hdr.h", + "lib2/private.h", + "lib2/hdr.h"); + assertThat( + Artifact.toRootRelativePaths( + instrumentedFilesProvider.getInstrumentationMetadataFiles())) + .containsExactly( + "bin/_objs/bin/bin/src.gcno", + "lib1/_objs/lib1/lib1/a.gcno", + "lib1/_objs/lib1/lib1/b.gcno", + "lib2/_objs/lib2/lib2/a.gcno", + "lib2/_objs/lib2/lib2/b.gcno", + "tools/objc/_objs/xctest_appbin/tools/objc/objc_dummy.gcno"); + } + + @Test + public void testBuildIpa() throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + + ConfiguredTarget target = + addSimpleIosTest( + "bin", + "bin", + ImmutableList.of("src.m"), + ImmutableList.of("//lib1:lib1", "//lib2:lib2")); + + Iterable<Artifact> filesToBuild = target.getProvider(FileProvider.class).getFilesToBuild(); + ImmutableList<String> expectedFilesToBuild = + ImmutableList.of( + "bin/bin.ipa", + "tools/objc/xctest_app.ipa"); + assertThat(Artifact.toRootRelativePaths(filesToBuild)) + .containsExactlyElementsIn(expectedFilesToBuild); + } + + @Test + public void testXcTestAppIpaIsInFilesToBuild() throws Exception { + scratch.file("x/BUILD", + "ios_application(", + " name = 'xctest_app',", + " binary = ':xctest_app_bin',", + " infoplist = 'Info.plist',", + ")", + "", + "objc_binary(", + " name = 'xctest_app_bin',", + " srcs = ['a.m'],", + ")", + "", + "ios_test(", + " name = 'x',", + " xctest = 1,", + " xctest_app = ':xctest_app',", + " srcs = ['test.m'],", + ")"); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + Iterable<Artifact> filesToBuild = target.getProvider(FileProvider.class).getFilesToBuild(); + assertThat(filesToBuild).contains(getBinArtifact("xctest_app.ipa", target)); + } + + @Test + public void testIpaIsImplicitOutput() throws Exception { + addSimpleIosTest("bin", "bin", ImmutableList.of("src.m"), ImmutableList.<String>of()); + assertThat(getConfiguredTarget("//bin:bin.ipa")).isNotNull(); + } + + @Test + public void testXcTest() throws Exception { + setUpXCTestClient(); + ConfiguredTarget target = getConfiguredTarget("//test:XcTest"); + + ImmutableList<String> expectedRunfiles = + ImmutableList.of( + "test/XcTest.ipa", + "test/testApp.ipa", + "test/XcTest_test_script", + "tools/objc/StdRedirect.dylib", + "tools/objc/testrunner"); + RunfilesProvider runfiles = target.getProvider(RunfilesProvider.class); + assertThat(Artifact.toRootRelativePaths(runfiles.getDefaultRunfiles().getArtifacts())) + .containsExactlyElementsIn(expectedRunfiles); + assertThat(Artifact.toRootRelativePaths(runfiles.getDataRunfiles().getArtifacts())) + .containsExactlyElementsIn(expectedRunfiles); + } + + @Test + public void testXcTestInCoverage() throws Exception { + useConfiguration("--collect_code_coverage", "--instrument_test_targets"); + setUpXCTestClient(); + ConfiguredTarget target = getConfiguredTarget("//test:XcTest"); + + InstrumentedFilesProvider instrumentedFilesProvider = + target.getProvider(InstrumentedFilesProvider.class); + assertThat(Artifact.toRootRelativePaths(instrumentedFilesProvider.getInstrumentedFiles())) + .containsExactly("test/src.m", "test/test-src.m"); + assertThat( + Artifact.toRootRelativePaths( + instrumentedFilesProvider.getInstrumentationMetadataFiles())) + .containsExactly( + "test/_objs/XcTest/test/test-src.gcno", "test/_objs/testAppBin/test/src.gcno"); + } + + @Test + public void testXcTestInCoverageFilter() throws Exception { + useConfiguration("--collect_code_coverage", "--instrumentation_filter=-XcTest$"); + setUpXCTestClient(); + ConfiguredTarget target = getConfiguredTarget("//test:XcTest"); + + + InstrumentedFilesProvider instrumentedFilesProvider = + target.getProvider(InstrumentedFilesProvider.class); + + // Missing "test/test-src.m" since the target including it has been excluded. + assertThat(Artifact.toRootRelativePaths(instrumentedFilesProvider.getInstrumentedFiles())) + .containsExactly("test/src.m"); + } + + @Test + public void testXcTest_linkAction() throws Exception { + setUpXCTestClient(); + CommandAction action = linkAction("//test:XcTest"); + + String commandLine = Joiner.on(" ").join(action.getArguments()); + assertThat(commandLine).contains("-bundle"); + assertThat(commandLine).contains("-Xlinker -rpath -Xlinker @loader_path/Frameworks"); + } + + @Test + public void testXcTest_linkAction_Crosstool() throws Exception { + useConfiguration(ObjcCrosstoolMode.ALL); + testXcTest_linkAction(); + } + + @Test + public void testVariableSubstitution() throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + + addSimpleIosTest( + "bin", "bin", ImmutableList.of("src.m"), ImmutableList.of("//lib1:lib1", "//lib2:lib2")); + + PlMergeProtos.Control control = plMergeControl("//bin:bin"); + Map<String, String> substitutions = control.getVariableSubstitutionMapMap(); + assertThat(substitutions) + .containsExactlyEntriesIn( + ImmutableMap.<String, String>of( + "EXECUTABLE_NAME", "bin", + "BUNDLE_NAME", "bin.xctest", + "PRODUCT_NAME", "bin")); + } + + protected void setUpXCTestClient() throws Exception { + scratch.file("/test/XcTest-Info.plist"); + scratch.file("/test/App-Info.plist"); + scratch.file("/test/src.m"); + scratch.file("/test/test-src.m"); + + scratch.file("test/BUILD", + "objc_binary(", + " name = 'testAppBin',", + " srcs = ['src.m'],", + ")", + "ios_application(", + " name = 'testApp',", + " binary = ':testAppBin',", + ")", + "ios_test(", + " name = 'XcTest',", + " srcs = ['test-src.m'],", + " xctest = True,", + " xctest_app = ':testApp',", + ")"); + } + + @Test + public void testCreate_recognizesDylibsAttribute() throws Exception { + createBinaryTargetWriter("//bin:bin").setAndCreateFiles("srcs", "a.m").write(); + scratch.file("test/BUILD", + "ios_application(", + " name = 'testApp',", + " binary = '//bin:bin',", + ")", + "ios_test(", + " name = 'test',", + " srcs = ['test-src.m'],", + " xctest = 1,", + " xctest_app = ':testApp',", + " sdk_dylibs = ['libdy'],", + ")"); + CommandAction action = linkAction("//test:test"); + assertThat(Joiner.on(" ").join(action.getArguments())).contains("-ldy"); + } + + @Test + public void testPopulatesCompilationArtifacts() throws Exception { + checkPopulatesCompilationArtifacts(RULE_TYPE); + } + + @Test + public void testArchivesPrecompiledObjectFiles() throws Exception { + checkArchivesPrecompiledObjectFiles(RULE_TYPE); + } + + @Test + public void testPopulatesBundling() throws Exception { + checkPopulatesBundling(RULE_TYPE); + } + + @Test + public void testRegistersStoryboardCompilationActions() throws Exception { + checkRegistersStoryboardCompileActions(RULE_TYPE, "iphone"); + } + + @Test + public void testRegistersSwiftStdlibActions() throws Exception { + checkRegisterSwiftStdlibActions(RULE_TYPE, "iphonesimulator"); + } + + @Test + public void testRegistersSwiftSupportActions() throws Exception { + checkRegisterSwiftSupportActions(RULE_TYPE, "iphonesimulator"); + } + + @Test + public void testRegistersSwiftStdlibActionsWithToolchain() throws Exception { + useConfiguration("--xcode_toolchain=test_toolchain"); + checkRegisterSwiftStdlibActions(RULE_TYPE, "iphonesimulator", "test_toolchain"); + } + + @Test + public void testRegistersSwiftSupportActionsWithToolchain() throws Exception { + useConfiguration("--xcode_toolchain=test_toolchain"); + checkRegisterSwiftSupportActions(RULE_TYPE, "iphonesimulator", "test_toolchain"); + } + + @Test + public void testAddsStoryboardZipsToFilesToBuild() throws Exception { + ConfiguredTarget target = createTargetWithStoryboards(RULE_TYPE); + + assertThat(getFilesToBuild(getConfiguredTarget("//x:x"))) + .containsAllOf( + getBinArtifact("x/1.storyboard.zip", target), + getBinArtifact("x/2.storyboard.zip", target)); + } + + @Test + public void testAddsXcdatamodelZipsToFilesToBuild() throws Exception { + RULE_TYPE.scratchTarget(scratch, + "datamodels", "['modela.xcdatamodel/a', 'modelb.xcdatamodeld/modelb1/a']"); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + assertThat(getFilesToBuild(target)) + .containsAllOf( + getBinArtifact("x/modela.zip", target), + getBinArtifact("x/modelb.zip", target)); + } + + @Test + public void testHasDefaultInfoplistForXcTest() throws Exception { + createBinaryTargetWriter("//bin:bin").setAndCreateFiles("srcs", "a.m").write(); + scratch.file("x/BUILD", + "ios_application(", + " name = 'testApp',", + " binary = '//bin:bin',", + ")", + "ios_test(", + " name = 'x',", + " srcs = ['x-src.m'],", + " xctest = 1,", + " xctest_app = ':testApp',", + ")"); + PlMergeProtos.Control control = plMergeControl("//x:x"); + assertThat(control.getSourceFileList()) + .contains(getSourceArtifact("tools/objc/xctest.plist").getExecPathString()); + } + + @Test + public void testCheckPrimaryBundleIdInMergedPlist() throws Exception { + checkPrimaryBundleIdInMergedPlist(RULE_TYPE); + } + + @Test + public void testCheckFallbackBundleIdInMergedPlist() throws Exception { + checkFallbackBundleIdInMergedPlist(RULE_TYPE); + } + + @Test + public void testErrorsWrongFileTypeForSrcsWhenCompiling() throws Exception { + checkErrorsWrongFileTypeForSrcsWhenCompiling(RULE_TYPE); + } + + @Test + public void testErrorForNoSources() throws Exception { + createBinaryTargetWriter("//bin:bin").setAndCreateFiles("srcs", "a.m").write(); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + checkError("x", "x", + IosTest.REQUIRES_SOURCE_ERROR, + "ios_application(", + " name = 'testApp',", + " binary = '//bin:bin',", + ")", + "ios_test(", + " name = 'x',", + " xctest = 1,", + " xctest_app = ':testApp',", + " deps = ['//lib:lib'],", + ")"); + } + + private void checkTestScript(Map<String, String> templateArguments, String... extraAttrs) + throws Exception { + createBinaryTargetWriter("//bin:bin").setAndCreateFiles("srcs", "a.m").write(); + scratch.file("x/BUILD", + "ios_application(", + " name = 'testApp',", + " binary = '//bin:bin',", + ")", + "ios_test(", + " name = 'x',", + " srcs = ['a.m'],", + " xctest = 1,", + " xctest_app = ':testApp',", + Joiner.on(",").join(extraAttrs), + ")"); + TemplateExpansionAction action = + (TemplateExpansionAction) + getGeneratingAction(getBinArtifact("x_test_script", getConfiguredTarget("//x:x"))); + + for (Map.Entry<String, String> templateArgument : templateArguments.entrySet()) { + assertThat(action.getSubstitutions()).contains( + Substitution.of("%(" + templateArgument.getKey() + ")s", templateArgument.getValue())); + } + } + + @Test + public void testTargetDeviceUsesIosVersionAttributeIfGiven() throws Exception { + scratch.file("devices/BUILD", + "ios_device(", + " name = 'dev',", + " type = 'test_type',", + " ios_version = '42.9993',", + ")"); + checkTestScript( + ImmutableMap.of("simulator_sdk", "42.9993", "device_type", "test_type"), + "target_device = '//devices:dev'"); + } + + @Test + public void testObjcCopts() throws Exception { + useConfiguration("--objccopt=-foo"); + + scratch.file("x/a.m"); + addSimpleIosTest("bin", "bin", ImmutableList.of("a.m"), ImmutableList.<String>of()); + List<String> args = compileAction("//bin:bin", "a.o").getArguments(); + assertThat(args).contains("-foo"); + } + + @Test + public void testObjcCopts_argumentOrdering() throws Exception { + useConfiguration("--objccopt=-foo"); + + scratch.file("x/a.m"); + addSimpleIosTest( + "bin", "bin", ImmutableList.of("a.m"), ImmutableList.<String>of(), "copts=['-bar']"); + List<String> args = compileAction("//bin:bin", "a.o").getArguments(); + assertThat(args).containsAllOf("-fobjc-arc", "-foo", "-bar").inOrder(); + } + + @Test + public void testGetsDefinesFromTestRig() throws Exception { + scratch.file("x/BUILD", + "objc_library(", + " name = 'lib',", + " srcs = ['lib.m'],", + " defines = ['LIB_DEFINE=1'],", + ")", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + " defines = ['BIN_DEFINE=1'],", + " deps = [':lib'],", + ")", + "ios_application(", + " name = 'testApp',", + " binary = ':bin',", + ")", + "ios_test(", + " name = 'test',", + " srcs = ['test.m'],", + " defines = ['TEST_DEFINE=1'],", + " xctest = 1,", + " xctest_app = ':testApp',", + ")"); + assertContainsSublist(compileAction("//x:test", "test.o").getArguments(), + ImmutableList.of("-DLIB_DEFINE=1", "-DBIN_DEFINE=1", "-DTEST_DEFINE=1")); + } + + @Test + public void testGetsSdkDylibsFromTestRig() throws Exception { + scratch.file("x/BUILD", + "objc_library(", + " name = 'lib',", + " srcs = ['lib.m'],", + " sdk_dylibs = ['lib_dylib'],", + ")", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + " sdk_dylibs = ['bin_dylib'],", + " deps = [':lib'],", + ")", + "ios_application(", + " name = 'testApp',", + " binary = ':bin',", + ")", + "ios_test(", + " name = 'test',", + " srcs = ['test.m'],", + " sdk_dylibs = ['test_dylib'],", + " xctest = 1,", + " xctest_app = ':testApp',", + ")"); + + String linkArgs = Joiner.on(' ').join(linkAction("//x:test").getArguments()); + assertThat(linkArgs).contains("-l_dylib"); + assertThat(linkArgs).contains("-lbin_dylib"); + assertThat(linkArgs).contains("-ltest_dylib"); + } + + @Test + public void testGetsSdkFrameworksFromTestRig() throws Exception { + scratch.file("x/BUILD", + "objc_library(", + " name = 'lib',", + " srcs = ['lib.m'],", + " sdk_frameworks = ['lib_fx'],", + " weak_sdk_frameworks = ['lib_wfx'],", + ")", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + " sdk_frameworks = ['bin_fx'],", + " weak_sdk_frameworks = ['bin_wfx'],", + " deps = [':lib'],", + ")", + "ios_application(", + " name = 'testApp',", + " binary = ':bin',", + ")", + "ios_test(", + " name = 'test',", + " srcs = ['test.m'],", + " sdk_frameworks = ['test_fx'],", + " weak_sdk_frameworks = ['test_wfx'],", + " xctest = 1,", + " xctest_app = ':testApp',", + ")"); + + String linkArgs = Joiner.on(' ').join(linkAction("//x:test").getArguments()); + assertThat(linkArgs).contains("-framework lib_fx"); + assertThat(linkArgs).contains("-weak_framework lib_wfx"); + assertThat(linkArgs).contains("-framework bin_fx"); + assertThat(linkArgs).contains("-weak_framework bin_wfx"); + assertThat(linkArgs).contains("-framework test_fx"); + assertThat(linkArgs).contains("-weak_framework test_wfx"); + } + + @Test + public void testLinkIncludeOrder_staticLibsFirst() throws Exception { + checkLinkIncludeOrderStaticLibsFirst(RULE_TYPE); + } + + @Test + public void testLinkIncludeOrder_frameworksAndSystemLibsFirst() throws Exception { + checkLinkIncludeOrderFrameworksAndSystemLibsFirst(RULE_TYPE); + } + + @Test + public void testMergesActoolPartialInfoplist() throws Exception { + checkMergesPartialInfoplists(RULE_TYPE); + } + + @Test + public void testCompileXibActions() throws Exception { + checkCompileXibActions(RULE_TYPE); + } + + @Test + public void testNibZipsMergedIntoBundle() throws Exception { + checkNibZipsMergedIntoBundle(RULE_TYPE); + } + + @Test + public void testActoolActionCorrectness() throws Exception { + addTargetWithAssetCatalogs(RULE_TYPE); + checkActoolActionCorrectness(DEFAULT_IOS_SDK_VERSION); + } + + @Test + public void testPassesFamiliesToActool() throws Exception { + checkPassesFamiliesToActool(RULE_TYPE); + } + + @Test + public void testPassesFamiliesToIbtool() throws Exception { + checkPassesFamiliesToIbtool(RULE_TYPE); + } + + @Test + public void testReportsErrorsForInvalidFamiliesAttribute() throws Exception { + checkReportsErrorsForInvalidFamiliesAttribute(RULE_TYPE); + } + + @Test + public void testCppSourceCompilesWithCppFlags() throws Exception { + checkCppSourceCompilesWithCppFlags(RULE_TYPE); + } + + @Test + public void testNoMultiCpu() throws Exception { + useConfiguration("--ios_multi_cpus=i386", "--ios_cpu=armv7"); + + checkError("x", "x", + IosTest.NO_MULTI_CPUS_ERROR, + "ios_test(", + " name = 'x',", + " srcs = ['a.m'],", + ")"); + } + + @Test + public void testXcTestAppFromSkylarkRule() throws Exception { + scratch.file("examples/rule/BUILD", + "exports_files(['test.ipa'])"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _skylark_xctest_app_impl(ctx):", + " artifact = list(ctx.attr.test_ipa.files)[0]", + " objc_provider = apple_common.new_objc_provider(define=depset(['TEST_DEFINE']))", + " xctest_app_provider = apple_common.new_xctest_app_provider(", + " bundle_loader=artifact, ipa=artifact, objc_provider=objc_provider)", + " return struct(", + " xctest_app=xctest_app_provider,", + " )", + "skylark_xctest_app = rule(implementation = _skylark_xctest_app_impl,", + " attrs = {", + " 'test_ipa': attr.label(", + " allow_single_file=True,", + " default=Label('//examples/rule:test.ipa')),", + " })"); + + scratch.file( + "examples/ios_test/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'skylark_xctest_app')", + "skylark_xctest_app(", + " name = 'my_xctest_app',", + ")", + "ios_test(", + " name = 'my_tests',", + " srcs = ['tests.m'],", + " xctest_app = ':my_xctest_app',", + ")"); + + ConfiguredTarget testTarget = getConfiguredTarget("//examples/ios_test:my_tests"); + + assertThat( + Artifact.toRootRelativePaths( + testTarget.getProvider(FileProvider.class).getFilesToBuild())) + .contains("examples/rule/test.ipa"); + } + + @Test + public void testApplePlatformAndXcode() throws Exception { + scratch.file("test/BUILD", + "xcode_version(", + " name = 'test_xcode',", + " version = '2.3',", + ")", + "ios_device(", + " name = 'test_device',", + " ios_version = '3.4',", + " xcode = ':test_xcode',", + " type = 'iChimpanzee',", + ")", + "ios_test(", + " name = 'some_test',", + " srcs = ['SomeTest.m'],", + " xctest = 0,", + " target_device = ':test_device',", + ")"); + + scratch.file("test/SomeTest.m"); + + ConfiguredTarget testTarget = getConfiguredTarget("//test:some_test"); + + TestRunnerAction testAction = (TestRunnerAction) getGeneratingAction( + Iterables.getOnlyElement(TestProvider.getTestStatusArtifacts(testTarget))); + TemplateExpansionAction templateExpansionAction = + getTestScriptGenerationAction(getConfiguredTarget("//test:some_test")); + + assertThat(templateExpansionAction.getSubstitutions()) + .contains(Substitution.of("%(simulator_sdk)s", "3.4")); + assertThat(testAction.getExtraTestEnv()).containsEntry("XCODE_VERSION_OVERRIDE", "2.3"); + } + + @Test + public void testAppleEnvironmentVariables_configurationXcodeVersion() throws Exception { + useConfiguration("--xcode_version=5.8"); + + scratch.file("test/BUILD", + "ios_device(", + " name = 'test_device',", + " type = 'iChimpanzee',", + ")", + "ios_test(", + " name = 'some_test',", + " srcs = ['SomeTest.m'],", + " xctest = 0,", + " target_device = ':test_device',", + ")"); + + scratch.file("test/SomeTest.m"); + + ConfiguredTarget testTarget = getConfiguredTarget("//test:some_test"); + + TestRunnerAction testAction = (TestRunnerAction) getGeneratingAction( + Iterables.getOnlyElement(TestProvider.getTestStatusArtifacts(testTarget))); + TemplateExpansionAction templateExpansionAction = + getTestScriptGenerationAction(getConfiguredTarget("//test:some_test")); + + assertThat(templateExpansionAction.getSubstitutions()).contains( + Substitution.of("%(simulator_sdk)s", XcodeVersionProperties.DEFAULT_IOS_SDK_VERSION)); + assertThat(testAction.getExtraTestEnv()) + .containsEntry("XCODE_VERSION_OVERRIDE", "5.8"); + } + + @Test + public void testRunnerSubstitution() throws Exception { + addSimpleIosTest("test", "some_test", ImmutableList.of("a.m"), ImmutableList.<String>of()); + + TemplateExpansionAction action = + getTestScriptGenerationAction(getConfiguredTarget("//test:some_test")); + assertThat(action.getSubstitutions()).containsExactly( + Substitution.of("%(memleaks)s", "false"), + + Substitution.of("%(test_app_ipa)s", "test/some_test.ipa"), + Substitution.of("%(test_app_name)s", "some_test"), + Substitution.of("%(test_bundle_path)s", "test/some_test.ipa"), + + Substitution.of("%(xctest_app_ipa)s", "tools/objc/xctest_app.ipa"), + Substitution.of("%(xctest_app_name)s", "xctest_app"), + Substitution.of("%(test_host_path)s", "tools/objc/xctest_app.ipa"), + + Substitution.of("%(plugin_jars)s", ""), + Substitution.of("%(device_type)s", "iChimpanzee"), + Substitution.of("%(locale)s", "en"), + Substitution.of("%(simulator_sdk)s", "9.8"), + Substitution.of("%(testrunner_binary)s", "tools/objc/testrunner"), + Substitution.of("%(std_redirect_dylib_path)s", "tools/objc/StdRedirect.dylib"), + Substitution.of("%(test_env)s", ""), + Substitution.of("%(test_type)s", "XCTEST") + ); + } + + @Test + public void testNonXcTestSubstitution() throws Exception { + scratch.file("test/BUILD", + "ios_test(", + " name = 'some_test',", + " srcs = ['SomeTest.m'],", + " xctest = 0,", + ")"); + + scratch.file("test/SomeTest.m"); + + ConfiguredTarget target = getConfiguredTarget("//test:some_test"); + + TemplateExpansionAction action = + getTestScriptGenerationAction(target); + assertThat(action.getSubstitutions()).containsExactly( + Substitution.of("%(memleaks)s", "false"), + + Substitution.of("%(test_app_ipa)s", "test/some_test.ipa"), + Substitution.of("%(test_bundle_path)s", "test/some_test.ipa"), + Substitution.of("%(test_app_name)s", "some_test"), + + Substitution.of("%(xctest_app_ipa)s", ""), + Substitution.of("%(xctest_app_name)s", ""), + Substitution.of("%(test_host_path)s", ""), + + Substitution.of("%(plugin_jars)s", ""), + Substitution.of("%(device_type)s", "iChimpanzee"), + Substitution.of("%(locale)s", "en"), + Substitution.of("%(simulator_sdk)s", "9.8"), + Substitution.of("%(testrunner_binary)s", "tools/objc/testrunner"), + Substitution.of("%(std_redirect_dylib_path)s", "tools/objc/StdRedirect.dylib"), + Substitution.of("%(test_env)s", ""), + Substitution.of("%(test_type)s", "KIF") + ); + + assertRunfilesContainsRootRelativePaths(target, + "test/some_test.ipa", + "test/some_test_test_script", + "tools/objc/testrunner"); + } + + @Test + public void testRunnerWithDevice() throws Exception { + scratch.file("test/BUILD", + "ios_test(", + " name = 'some_test_with_device',", + " srcs = ['SomeOtherTest.m'],", + " xctest = 1,", + " xctest_app = ':testApp',", + " target_device = ':device',", + ")", + "ios_device(", + " name = 'device',", + " ios_version = '1.2',", + " type = 'iMarmoset',", + " locale = 'en-gb'", + ")", + "ios_application(", + " name = 'testApp',", + " binary = ':bin',", + ")", + "objc_binary(name = 'bin',", + " srcs = ['app.m'],", + ")"); + + scratch.file("test/SomeOtherTest.m"); + scratch.file("test/app.m"); + + TemplateExpansionAction action = + getTestScriptGenerationAction(getConfiguredTarget("//test:some_test_with_device")); + assertThat(action.getSubstitutions()).containsExactly( + Substitution.of("%(memleaks)s", "false"), + + Substitution.of("%(test_app_ipa)s", "test/some_test_with_device.ipa"), + Substitution.of("%(test_app_name)s", "some_test_with_device"), + Substitution.of("%(test_bundle_path)s", "test/some_test_with_device.ipa"), + + Substitution.of("%(xctest_app_ipa)s", "test/testApp.ipa"), + Substitution.of("%(xctest_app_name)s", "testApp"), + Substitution.of("%(test_host_path)s", "test/testApp.ipa"), + + Substitution.of("%(plugin_jars)s", ""), + Substitution.of("%(device_type)s", "iMarmoset"), + Substitution.of("%(locale)s", "en-gb"), + Substitution.of("%(simulator_sdk)s", "1.2"), + Substitution.of("%(testrunner_binary)s", "tools/objc/testrunner"), + Substitution.of("%(std_redirect_dylib_path)s", "tools/objc/StdRedirect.dylib"), + Substitution.of("%(test_env)s", ""), + Substitution.of("%(test_type)s", "XCTEST") + ); + } + + @Test + public void testPlugins() throws Exception { + scratch.file("test/BUILD", + "ios_test(", + " name = 'one_plugin',", + " srcs = ['SomeTest.m'],", + " xctest = 1,", + " xctest_app = ':testApp',", + " plugins = [':a_plugin_deploy.jar'],", + ")", + "ios_test(", + " name = 'two_plugins',", + " srcs = ['SomeOtherTest.m'],", + " xctest = 1,", + " xctest_app = ':testApp',", + " plugins = [':a_plugin_deploy.jar', ':b_plugin_deploy.jar'],", + ")", + "java_binary(", + " name = 'a_plugin',", + " srcs = ['A.java'],", + " main_class = 'A',", + ")", + "java_binary(", + " name = 'b_plugin',", + " srcs = ['B.java'],", + " main_class = 'B',", + ")", + "ios_application(", + " name = 'testApp',", + " binary = ':bin',", + ")", + "objc_binary(name = 'bin',", + " srcs = ['app.m'],", + ")"); + + scratch.file("test/SomeTest.m"); + scratch.file("test/SomeOtherTest.m"); + scratch.file("test/app.m"); + scratch.file("test/A.java"); + scratch.file("test/B.java"); + + ConfiguredTarget onePluginTarget = getConfiguredTarget("//test:one_plugin"); + TemplateExpansionAction onePluginAction = + getTestScriptGenerationAction(onePluginTarget); + assertThat(onePluginAction.getSubstitutions()).contains( + Substitution.of("%(plugin_jars)s", "test/a_plugin_deploy.jar")); + assertRunfilesContainsRootRelativePaths(onePluginTarget, "test/a_plugin_deploy.jar"); + + ConfiguredTarget twoPluginsTarget = getConfiguredTarget("//test:two_plugins"); + TemplateExpansionAction twoPluginsAction = + getTestScriptGenerationAction(twoPluginsTarget); + assertThat(twoPluginsAction.getSubstitutions()).contains( + Substitution.of("%(plugin_jars)s", "test/a_plugin_deploy.jar:test/b_plugin_deploy.jar") + ); + assertRunfilesContainsRootRelativePaths(twoPluginsTarget, + "test/a_plugin_deploy.jar", "test/b_plugin_deploy.jar"); + } + + private TemplateExpansionAction getTestScriptGenerationAction(ConfiguredTarget target) + throws Exception { + Artifact testScript = getBinArtifact(target.getLabel().getName() + "_test_script", target); + return (TemplateExpansionAction) getGeneratingAction(testScript); + } + + private void assertRunfilesContainsRootRelativePaths( + ConfiguredTarget target, String... expectedRunfiles) { + RunfilesProvider runfiles = target.getProvider(RunfilesProvider.class); + ImmutableList<String> listToAvoidDumbUnsafeVarargsWarning = + ImmutableList.copyOf(expectedRunfiles); + assertThat(Artifact.toRootRelativePaths(runfiles.getDefaultRunfiles().getArtifacts())) + .containsAllIn(listToAvoidDumbUnsafeVarargsWarning); + assertThat(Artifact.toRootRelativePaths(runfiles.getDataRunfiles().getArtifacts())) + .containsAllIn(listToAvoidDumbUnsafeVarargsWarning); + } + + @Test + public void testAutomaticPlistEntries() throws Exception { + checkAutomaticPlistEntries(RULE_TYPE); + } + + @Test + public void testBundleMergeInputContainsPlMergeOutput() throws Exception { + checkBundleMergeInputContainsPlMergeOutput(RULE_TYPE); + } + + @Test + public void testLinkOpts() throws Exception { + checkLinkopts(RULE_TYPE); + } + + @Test + public void testProtoBundlingAndLinking() throws Exception { + checkProtoBundlingAndLinking(RULE_TYPE); + } + + @Test + public void testProtoBundlingWithTargetsWithNoDeps() throws Exception { + checkProtoBundlingWithTargetsWithNoDeps(RULE_TYPE); + } + + @Test + public void testLinkingRuleCanUseCrosstool() throws Exception { + checkLinkingRuleCanUseCrosstool(RULE_TYPE); + } + + @Test + public void testBinaryStrippings() throws Exception { + checkBinaryStripAction(RULE_TYPE, "-S"); + } + + @Test + public void testMergeBundleActionsWithNestedBundle() throws Exception { + checkMergeBundleActionsWithNestedBundle(RULE_TYPE); + } + + @Test + public void testIncludesStoryboardOutputZipsAsMergeZips() throws Exception { + checkIncludesStoryboardOutputZipsAsMergeZips(RULE_TYPE); + } + + @Test + public void testCompilationActionsForDebug() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.DBG, CodeCoverageMode.NONE); + } + + @Test + public void testClangCoptsForDebugModeWithoutGlib() throws Exception { + checkClangCoptsForDebugModeWithoutGlib(RULE_TYPE); + } + + @Test + public void testCompilationActionsForOptimized() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.OPT, CodeCoverageMode.NONE); + } + + @Test + public void testProtobufPropagatedHeaderSearchPaths() throws Exception { + scratch.file( + "test/BUILD", + "ios_test(", + " name = 'protos_test',", + " srcs = ['SomeTest.m'],", + " xctest = 1,", + " xctest_app = ':protos_app',", + ")", + "ios_application(", + " name = 'protos_app',", + " binary = ':protos_bin',", + ")", + "objc_binary(", + " name = 'protos_bin',", + " srcs = ['app.m'],", + " deps = [':protos_objc'],", + ")", + "objc_proto_library(", + " name = 'protos_objc',", + " deps = [':protos_lib'],", + " portable_proto_filters = ['filter.pbascii'],", + ")", + "proto_library(", + " name = 'protos_lib',", + " srcs = ['a.proto'],", + ")"); + + ObjcProvider appProvider = + getConfiguredTarget("//test:protos_app") + .getProvider(XcTestAppProvider.class) + .getObjcProvider(); + ConfiguredTarget binTarget = getConfiguredTarget("//test:protos_bin"); + Artifact protoHeader = + getBinArtifact("_generated_protos/protos_bin/test/A.pbobjc.h", binTarget); + + assertThat(PathFragment.safePathStrings(appProvider.get(ObjcProvider.INCLUDE))) + .containsAllOf( + "objcproto/include", + protoHeader.getExecPath().getParentDirectory().getParentDirectory().toString()); + } + + @Test + public void testCcDependency() throws Exception { + checkCcDependency(RULE_TYPE, "xctest", "0"); + } + + @Test + public void testPassesTestRigAppAsBundleLoaderFlagToLinker() throws Exception { + useConfiguration("--cpu=ios_x86_64", + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go"); + scratch.file("x/BUILD", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + ")", + "ios_application(", + " name = 'testApp',", + " binary = ':bin',", + ")", + "ios_test(", + " name = 'test',", + " srcs = ['test.m'],", + " xctest = 1,", + " xctest_app = ':testApp',", + ")"); + CommandAction testLinkAction = linkAction("//x:test"); + Action appLipoAction = lipoBinAction("//x:testApp"); + Artifact rigBinary = Iterables.getOnlyElement(appLipoAction.getOutputs()); + + String linkArgs = Joiner.on(' ').join(testLinkAction.getArguments()); + assertThat(linkArgs).contains("-bundle_loader " + rigBinary.getExecPath()); + assertThat(testLinkAction.getInputs()).contains(rigBinary); + } + + @Test + public void testCompilationActionsForDebugInGcovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.DBG, + CodeCoverageMode.GCOV); + } + + @Test + public void testCompilationActionsForDebugInLlvmCovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.DBG, + CodeCoverageMode.LLVMCOV); + } + + @Test + public void testCompilationActionsForOptimizedInGcovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.OPT, + CodeCoverageMode.GCOV); + } + + @Test + public void testCompilationActionsForOptimizedInLlvmCovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.OPT, + CodeCoverageMode.LLVMCOV); + } + + @Test + public void testMultiArchUserHeaderSearchPathsUsed() throws Exception { + // Usually, an ios_test would depend on apple_binary through a skylark_ios_application in its + // 'binary' attribute. Since we don't have skylark_ios_application here, we use the 'deps' + // attribute instead. + scratch.file("x/BUILD", + "genrule(", + " name = 'gen_hdrs',", + " outs = ['generated.h'],", + " cmd = 'echo hello > \\$@',", + ")", + "apple_binary(", + " name = 'apple_bin',", + " srcs = ['apple_bin.m'],", + " platform_type = 'ios',", + " hdrs = ['generated.h'],", + ")", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + ")", + "ios_application(", + " name = 'testApp',", + " binary = ':bin',", + ")", + "ios_test(", + " name = 'test',", + " srcs = ['test.m'],", + " xctest = 1,", + " xctest_app = ':testApp',", + " deps = [':apple_bin']", + ")"); + CommandAction compileAction = compileAction("//x:test", "test.o"); + // The genfiles root for child configurations must be present in the compile action so that + // generated headers can be resolved. + assertThat(Joiner.on(" ").join(compileAction.getArguments())).contains("-iquote " + + configurationGenfiles("x86_64", ConfigurationDistinguisher.APPLEBIN_IOS, + defaultMinimumOs(ConfigurationDistinguisher.APPLEBIN_IOS))); + } + + @Test + public void testXcTest_linkAction_inCoverageMode() throws Exception { + useConfiguration("--collect_code_coverage"); + setUpXCTestClient(); + CommandAction action = linkAction("//test:XcTest"); + assertThat(Joiner.on(" ").join(action.getArguments())).contains("-bundle"); + for (String linkerCoverageFlag : CompilationSupport.LINKER_COVERAGE_FLAGS) { + assertThat(Joiner.on(" ").join(action.getArguments())).contains(linkerCoverageFlag); + } + } + + @Test + public void testXcTest_linkAction_inLLVMCoverageMode() throws Exception { + useConfiguration("--collect_code_coverage", "--experimental_use_llvm_covmap"); + setUpXCTestClient(); + CommandAction action = linkAction("//test:XcTest"); + assertThat(Joiner.on(" ").join(action.getArguments())).contains("-bundle"); + for (String linkerCoverageFlag : CompilationSupport.LINKER_LLVM_COVERAGE_FLAGS) { + assertThat(Joiner.on(" ").join(action.getArguments())).contains(linkerCoverageFlag); + } + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleBinaryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleBinaryTest.java new file mode 100644 index 0000000000..7f919ceee3 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleBinaryTest.java @@ -0,0 +1,31 @@ +// 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.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. + */ +@RunWith(JUnit4.class) +public class LegacyAppleBinaryTest extends AppleBinaryTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } + +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleDynamicLibraryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleDynamicLibraryTest.java new file mode 100644 index 0000000000..ca1844f6d3 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleDynamicLibraryTest.java @@ -0,0 +1,31 @@ +// 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.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. + */ +@RunWith(JUnit4.class) +public class LegacyAppleDynamicLibraryTest extends AppleDynamicLibraryTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } + +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleStaticLibraryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleStaticLibraryTest.java new file mode 100644 index 0000000000..d25de7b30e --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleStaticLibraryTest.java @@ -0,0 +1,96 @@ +// 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.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. + */ +@RunWith(JUnit4.class) +public class LegacyAppleStaticLibraryTest extends AppleStaticLibraryTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } + + @Test + public void testAvoidDepsObjects() throws Exception { + scratch.file("package/BUILD", + "apple_static_library(", + " name = 'test',", + " deps = [':objcLib'],", + " avoid_deps = [':avoidLib'],", + " platform_type = 'ios',", + ")", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ], deps = [':avoidLib', ':baseLib'])", + "objc_library(name = 'baseLib', srcs = [ 'base.m' ])", + "objc_library(name = 'avoidLib', srcs = [ 'c.m' ])"); + + CommandAction action = linkLibAction("//package:test"); + assertThat(Artifact.toRootRelativePaths(action.getInputs())).containsExactly( + "package/libobjcLib.a", "package/libbaseLib.a", MOCK_LIBTOOL_PATH); + } + + @Test + // Tests that if there is a cc_library in avoid_deps, all of its dependencies are + // transitively avoided, even if it is not present in deps. + public void testAvoidDepsObjects_avoidViaCcLibrary() throws Exception { + scratch.file("package/BUILD", + "apple_static_library(", + " name = 'test',", + " deps = [':objcLib'],", + " avoid_deps = [':avoidCclib'],", + " platform_type = 'ios',", + ")", + "cc_library(name = 'avoidCclib', srcs = ['cclib.c'], deps = [':avoidLib'])", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ], deps = [':avoidLib'])", + "objc_library(name = 'avoidLib', srcs = [ 'c.m' ])"); + + useConfiguration("--experimental_disable_go", "--experimental_disable_jvm"); + CommandAction action = linkLibAction("//package:test"); + assertThat(Artifact.toRootRelativePaths(action.getInputs())).containsExactly( + "package/libobjcLib.a", MOCK_LIBTOOL_PATH); + } + + @Test + // Tests that if there is a cc_library in avoid_deps, and it is present in deps, it will + // be avoided, as well as its transitive dependencies. + public void testAvoidDepsObjects_avoidCcLibrary() throws Exception { + scratch.file("package/BUILD", + "apple_static_library(", + " name = 'test',", + " deps = [':objcLib', ':avoidCclib'],", + " avoid_deps = [':avoidCclib'],", + " platform_type = 'ios',", + ")", + "cc_library(name = 'avoidCclib', srcs = ['cclib.c'], deps = [':avoidLib'])", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ])", + "objc_library(name = 'avoidLib', srcs = [ 'c.m' ])"); + + useConfiguration("--experimental_disable_go", "--experimental_disable_jvm"); + CommandAction action = linkLibAction("//package:test"); + assertThat(Artifact.toRootRelativePaths(action.getInputs())).containsExactly( + "package/libobjcLib.a", MOCK_LIBTOOL_PATH); + } + +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleWatch1ExtensionTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleWatch1ExtensionTest.java new file mode 100644 index 0000000000..dc386625e8 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleWatch1ExtensionTest.java @@ -0,0 +1,36 @@ +// 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.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. + */ +@RunWith(JUnit4.class) +public class LegacyAppleWatch1ExtensionTest extends AppleWatch1ExtensionTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } + + @Test + public void testGenruleDependency() throws Exception { + checkGenruleDependency(RULE_TYPE_PAIR); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleWatch2ExtensionTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleWatch2ExtensionTest.java new file mode 100644 index 0000000000..89ed19475d --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleWatch2ExtensionTest.java @@ -0,0 +1,59 @@ +// 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.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. + */ +@RunWith(JUnit4.class) +public class LegacyAppleWatch2ExtensionTest extends AppleWatch2ExtensionTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } + + @Test + public void testRegistersSwiftSupportActions() throws Exception { + createSwiftBinaryTarget( + "apple_watch2_extension(", + " name = 'ext',", + " app_name = 'y',", + " app_asset_catalogs = ['foo.xcassets/foo', 'bar.xcassets/bar'],", + " binary = ':x',", + ")"); + + checkRegisterSwiftSupportActions( + getConfiguredTarget("//x:ext", getAppleCrosstoolConfiguration()), "watchsimulator"); + } + + @Test + public void testRegistersSwiftStdlibActions() throws Exception { + createSwiftBinaryTarget( + "apple_watch2_extension(", + " name = 'ext',", + " app_name = 'y',", + " app_asset_catalogs = ['foo.xcassets/foo', 'bar.xcassets/bar'],", + " binary = ':x',", + ")"); + + checkRegisterSwiftStdlibActions( + getConfiguredTarget("//x:ext", getAppleCrosstoolConfiguration()), "watchsimulator"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleWatchExtensionBinaryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleWatchExtensionBinaryTest.java new file mode 100644 index 0000000000..84bd7cc4ad --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyAppleWatchExtensionBinaryTest.java @@ -0,0 +1,76 @@ +// 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.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. + */ +@RunWith(JUnit4.class) +public class LegacyAppleWatchExtensionBinaryTest extends AppleWatchExtensionBinaryTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } + + @Test + public void testLinkActionWithTransitiveCppDependency() throws Exception { + checkLinkActionWithTransitiveCppDependency(RULE_TYPE, EXTRA_LINK_ARGS); + } + + @Test + public void testCompilesSources() throws Exception { + checkCompilesSources(RULE_TYPE); + } + + @Test + public void testCompilesSourcesWithModuleMapsEnabled() throws Exception { + checkCompilesSourcesWithModuleMapsEnabled(RULE_TYPE); + } + + @Test + public void testCompileWithTextualHeaders() throws Exception { + checkCompileWithTextualHeaders(RULE_TYPE); + } + + @Test + public void testLinkWithFrameworkImportsIncludesFlagsAndInputArtifacts() throws Exception { + checkLinkWithFrameworkImportsIncludesFlagsAndInputArtifacts(RULE_TYPE); + } + + @Test + public void testCompilesWithHdrs() throws Exception { + checkCompilesWithHdrs(RULE_TYPE); + } + + @Test + public void testForceLoadsAlwayslinkTargets() throws Exception { + checkForceLoadsAlwayslinkTargets(RULE_TYPE, EXTRA_LINK_ARGS); + } + + @Test + public void testReceivesTransitivelyPropagatedDefines() throws Exception { + checkReceivesTransitivelyPropagatedDefines(RULE_TYPE); + } + + @Test + public void testSdkIncludesUsedInCompileAction() throws Exception { + checkSdkIncludesUsedInCompileAction(RULE_TYPE); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyIosApplicationTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyIosApplicationTest.java new file mode 100644 index 0000000000..e319b9e37f --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyIosApplicationTest.java @@ -0,0 +1,36 @@ +// 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.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. + */ +@RunWith(JUnit4.class) +public class LegacyIosApplicationTest extends IosApplicationTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } + + @Test + public void testGenruleDependency() throws Exception { + checkGenruleDependency(RULE_TYPE_PAIR); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyIosExtensionBinaryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyIosExtensionBinaryTest.java new file mode 100644 index 0000000000..2cdfe47413 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyIosExtensionBinaryTest.java @@ -0,0 +1,76 @@ +// 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.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. + */ +@RunWith(JUnit4.class) +public class LegacyIosExtensionBinaryTest extends IosExtensionBinaryTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } + + @Test + public void testLinkActionWithTransitiveCppDependency() throws Exception { + checkLinkActionWithTransitiveCppDependency(RULE_TYPE, EXTRA_LINK_ARGS); + } + + @Test + public void testCompilesSources() throws Exception { + checkCompilesSources(RULE_TYPE); + } + + @Test + public void testCompilesSourcesWithModuleMapsEnabled() throws Exception { + checkCompilesSourcesWithModuleMapsEnabled(RULE_TYPE); + } + + @Test + public void testCompileWithTextualHeaders() throws Exception { + checkCompileWithTextualHeaders(RULE_TYPE); + } + + @Test + public void testLinkWithFrameworkImportsIncludesFlagsAndInputArtifacts() throws Exception { + checkLinkWithFrameworkImportsIncludesFlagsAndInputArtifacts(RULE_TYPE); + } + + @Test + public void testCompilesWithHdrs() throws Exception { + checkCompilesWithHdrs(RULE_TYPE); + } + + @Test + public void testForceLoadsAlwayslinkTargets() throws Exception { + checkForceLoadsAlwayslinkTargets(RULE_TYPE, EXTRA_LINK_ARGS); + } + + @Test + public void testReceivesTransitivelyPropagatedDefines() throws Exception { + checkReceivesTransitivelyPropagatedDefines(RULE_TYPE); + } + + @Test + public void testSdkIncludesUsedInCompileAction() throws Exception { + checkSdkIncludesUsedInCompileAction(RULE_TYPE); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyIosExtensionTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyIosExtensionTest.java new file mode 100644 index 0000000000..fc2ca29b9a --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyIosExtensionTest.java @@ -0,0 +1,30 @@ +// 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.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. + */ +@RunWith(JUnit4.class) +public class LegacyIosExtensionTest extends IosExtensionTest { + @Override + public ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyIosTestTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyIosTestTest.java new file mode 100644 index 0000000000..28c103416b --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyIosTestTest.java @@ -0,0 +1,160 @@ +// 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.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.rules.apple.AppleToolchain; +import com.google.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. + */ +@RunWith(JUnit4.class) +public class LegacyIosTestTest extends IosTestTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } + + @Test + public void testCompilesWithHdrs() throws Exception { + checkCompilesWithHdrs(RULE_TYPE); + } + + @Test + public void testGetsHeadersFromTestRig() throws Exception { + scratch.file("x/BUILD", + "objc_library(", + " name = 'lib',", + " srcs = ['lib.m'],", + " hdrs = ['lib.h'],", + ")", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + " hdrs = ['bin.h'],", + " deps = [':lib'],", + ")", + "ios_application(", + " name = 'testApp',", + " binary = ':bin',", + ")", + "ios_test(", + " name = 'test',", + " srcs = ['test.m'],", + " hdrs = ['test.h'],", + " xctest = 1,", + " xctest_app = ':testApp',", + ")"); + Iterable<Artifact> compileInputs = compileAction("//x:test", "test.o").getInputs(); + assertThat(Artifact.toExecPaths(compileInputs)) + .containsAllOf("x/lib.h", "x/bin.h", "x/test.h"); + } + + @Test + public void testGetsIncludesFromTestRig() throws Exception { + scratch.file("x/BUILD", + "objc_library(", + " name = 'lib',", + " srcs = ['lib.m'],", + " includes = ['libinc'],", + " sdk_includes = ['libinc_sdk'],", + ")", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + " includes = ['bininc'],", + " sdk_includes = ['bininc_sdk'],", + " deps = [':lib'],", + ")", + "ios_application(", + " name = 'testApp',", + " binary = ':bin',", + ")", + "ios_test(", + " name = 'test',", + " srcs = ['test.m'],", + " includes = ['testinc'],", + " sdk_includes = ['testinc_sdk'],", + " xctest = 1,", + " xctest_app = ':testApp',", + ")"); + List<String> compileArgs = compileAction("//x:test", "test.o").getArguments(); + assertContainsSublist(compileArgs, ImmutableList.of("-I", "x/libinc")); + assertContainsSublist(compileArgs, ImmutableList.of("-I", "x/bininc")); + assertContainsSublist(compileArgs, ImmutableList.of("-I", "x/testinc")); + + String sdkIncludeDir = AppleToolchain.sdkDir() + "/usr/include/"; + assertContainsSublist(compileArgs, ImmutableList.of("-I", sdkIncludeDir + "libinc_sdk")); + assertContainsSublist(compileArgs, ImmutableList.of("-I", sdkIncludeDir + "bininc_sdk")); + assertContainsSublist(compileArgs, ImmutableList.of("-I", sdkIncludeDir + "testinc_sdk")); + } + + @Test + public void testGetsFrameworksFromTestRig() throws Exception { + scratch.file("x/BUILD", + "objc_framework(", + " name = 'fx',", + " framework_imports = ['fx.framework/1'],", + ")", + "objc_library(", + " name = 'lib',", + " srcs = ['lib.m'],", + " deps = [':fx'],", + ")", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + " deps = [':lib'],", + ")", + "ios_application(", + " name = 'testApp',", + " binary = ':bin',", + ")", + "ios_test(", + " name = 'test',", + " srcs = ['test.m'],", + " xctest = 1,", + " xctest_app = ':testApp',", + ")"); + CommandAction compileAction = compileAction("//x:test", "test.o"); + + assertThat(Artifact.toExecPaths(compileAction.getInputs())) + .contains("x/fx.framework/1"); + assertContainsSublist(compileAction.getArguments(), ImmutableList.of("-F", "x")); + + CommandAction linkAction = linkAction("//x:test"); + assertThat(Joiner.on(" ").join(linkAction.getArguments())).doesNotContain("-framework fx"); + } + + @Test + public void testReceivesTransitivelyPropagatedDefines() throws Exception { + checkReceivesTransitivelyPropagatedDefines(RULE_TYPE); + } + + @Test + public void testSdkIncludesUsedInCompileAction() throws Exception { + checkSdkIncludesUsedInCompileAction(RULE_TYPE); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcBinaryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcBinaryTest.java new file mode 100644 index 0000000000..af13d04ad1 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcBinaryTest.java @@ -0,0 +1,137 @@ +// 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.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.rules.objc.CompilationSupport.ExtraLinkArgs; +import com.google.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. + */ +@RunWith(JUnit4.class) +public class LegacyObjcBinaryTest extends ObjcBinaryTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } + + @Test + public void testLinkActionWithTransitiveCppDependency() throws Exception { + checkLinkActionWithTransitiveCppDependency(RULE_TYPE, new ExtraLinkArgs()); + } + + @Test + public void testCompilesSources() throws Exception { + checkCompilesSources(RULE_TYPE); + } + + @Test + public void testCompilesSourcesWithModuleMapsEnabled() throws Exception { + checkCompilesSourcesWithModuleMapsEnabled(RULE_TYPE); + } + + @Test + public void testCompileWithTextualHeaders() throws Exception { + checkCompileWithTextualHeaders(RULE_TYPE); + } + + @Test + public void testLinkWithFrameworkImportsIncludesFlagsAndInputArtifacts() throws Exception { + checkLinkWithFrameworkImportsIncludesFlagsAndInputArtifacts(RULE_TYPE); + } + + @Test + public void testCompilesWithHdrs() throws Exception { + checkCompilesWithHdrs(RULE_TYPE); + } + + @Test + public void testForceLoadsAlwayslinkTargets() throws Exception { + checkForceLoadsAlwayslinkTargets(RULE_TYPE, new ExtraLinkArgs()); + } + + @Test + public void testReceivesTransitivelyPropagatedDefines() throws Exception { + checkReceivesTransitivelyPropagatedDefines(RULE_TYPE); + } + + @Test + public void testSdkIncludesUsedInCompileAction() throws Exception { + checkSdkIncludesUsedInCompileAction(RULE_TYPE); + } + + @Override + @Test + public void testCreate_debugSymbolActionWithAppleFlag() throws Exception { + useConfiguration("--apple_generate_dsym"); + RULE_TYPE.scratchTarget(scratch, "srcs", "['a.m']"); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + + Artifact artifact = getBinArtifact("x.app.dSYM.temp.zip", target); + String execPath = artifact.getExecPath().getParentDirectory().toString(); + CommandAction linkAction = (CommandAction) getGeneratingAction(artifact); + assertThat(Joiner.on(" ").join(linkAction.getArguments())) + .contains( + Joiner.on(" ") + .join( + "&&", + MOCK_XCRUNWRAPPER_PATH, + ObjcRuleClasses.DSYMUTIL, + execPath + "/x_bin", + "-o", + execPath + "/x.app.dSYM.temp", + "&&", + "zipped_bundle=${PWD}/" + artifact.getExecPathString(), + "&&", + "cd " + artifact.getExecPathString().replace(".zip", ""), + "&&", + "/usr/bin/zip -q -r \"${zipped_bundle}\" .")); + + Artifact plistArtifact = getBinArtifact("x.app.dSYM/Contents/Info.plist", target); + Artifact debugSymbolArtifact = + getBinArtifact("x.app.dSYM/Contents/Resources/DWARF/x_bin", target); + SpawnAction plistAction = (SpawnAction) getGeneratingAction(plistArtifact); + SpawnAction debugSymbolAction = (SpawnAction) getGeneratingAction(debugSymbolArtifact); + assertThat(debugSymbolAction).isEqualTo(plistAction); + + String dsymUnzipActionArg = + "unzip -p " + + execPath + + "/x.app.dSYM.temp.zip" + + " Contents/Info.plist > " + + plistArtifact.getExecPathString() + + " && unzip -p " + + execPath + + "/x.app.dSYM.temp.zip" + + " Contents/Resources/DWARF/x_bin > " + + debugSymbolArtifact.getExecPathString(); + assertThat(plistAction.getArguments()).contains(dsymUnzipActionArg); + } + + @Test + public void testGenruleDependency() throws Exception { + checkGenruleDependency(RULE_TYPE); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcBundleLibraryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcBundleLibraryTest.java new file mode 100644 index 0000000000..f999b21938 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcBundleLibraryTest.java @@ -0,0 +1,30 @@ +// 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.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. + */ +@RunWith(JUnit4.class) +public class LegacyObjcBundleLibraryTest extends ObjcBundleLibraryTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcBundleTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcBundleTest.java new file mode 100644 index 0000000000..696e650341 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcBundleTest.java @@ -0,0 +1,31 @@ +// 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.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. The class is empty + * because each test is also run in the superclass, which tests --experimental_objc_crosstool=all. + */ +@RunWith(JUnit4.class) +public class LegacyObjcBundleTest extends ObjcBundleTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcFrameworkTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcFrameworkTest.java new file mode 100644 index 0000000000..c209b2cbe7 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcFrameworkTest.java @@ -0,0 +1,31 @@ +// 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.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. The class is empty + * because each test is also run in the superclass, which tests --experimental_objc_crosstool=all. + */ +@RunWith(JUnit4.class) +public class LegacyObjcFrameworkTest extends ObjcFrameworkTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcImportTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcImportTest.java new file mode 100644 index 0000000000..b8cf7325af --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcImportTest.java @@ -0,0 +1,36 @@ +// 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.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. + */ +@RunWith(JUnit4.class) +public class LegacyObjcImportTest extends ObjcImportTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } + + @Test + public void testSdkIncludesUsedInCompileActionsOfDependers() throws Exception { + checkSdkIncludesUsedInCompileActionsOfDependers(RULE_TYPE); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcLibraryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcLibraryTest.java new file mode 100644 index 0000000000..3d931b961b --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcLibraryTest.java @@ -0,0 +1,900 @@ +// 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 static com.google.devtools.build.lib.actions.util.ActionsTestUtil.baseArtifactNames; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.CC_LIBRARY; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.SDK_FRAMEWORK; +import static org.junit.Assert.fail; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +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.Action; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.util.ScratchAttributeWriter; +import com.google.devtools.build.lib.cmdline.RepositoryName; +import com.google.devtools.build.lib.packages.NoSuchTargetException; +import com.google.devtools.build.lib.packages.util.MockObjcSupport; +import com.google.devtools.build.lib.rules.apple.AppleToolchain; +import com.google.devtools.build.lib.rules.apple.Platform; +import com.google.devtools.build.lib.rules.cpp.HeaderDiscovery; +import com.google.devtools.build.lib.rules.cpp.LinkerInput; +import com.google.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import com.google.devtools.build.lib.vfs.PathFragment; +import java.util.List; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Legacy test case (that is, without the OSX crosstool) for objc_library. */ +@RunWith(JUnit4.class) +public class LegacyObjcLibraryTest extends ObjcLibraryTest { + private static final RuleType RULE_TYPE = new OnlyNeedsSourcesRuleType("objc_library"); + private static final String XCRUNWRAPPER = "xcrunwrapper"; + private static final String LIBTOOL = "libtool"; + + @Override + protected void useConfiguration(String... args) throws Exception { + // Crosstool case is tested in {@link ObjcLibraryTest} + useConfiguration(ObjcCrosstoolMode.OFF, args); + } + + @Test + public void testLibFileIsCorrectForSlashInTargetName() throws Exception { + ConfiguredTarget target = + createLibraryTargetWriter("//objc:dir/Target") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .write(); + Iterable<Artifact> outputArtifacts = getFilesToBuild(target); + assertThat(Artifact.toRootRelativePaths(outputArtifacts)).containsExactly("objc/libTarget.a"); + } + + @Test + public void testNonPropagatedDepsDiamond() throws Exception { + // Non-propagated. + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "a.h") + .write(); + // Conflicts with non-propagated. + createLibraryTargetWriter("//objc2:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "a.h") + .write(); + + createLibraryTargetWriter("//objc3:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "b.h") + .setList("non_propagated_deps", "//objc:lib") + .write(); + + createLibraryTargetWriter("//objc4:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .setList("deps", "//objc2:lib", "//objc3:lib") + .write(); + + Action action = compileAction("//objc4:lib", "a.o"); + assertThat(Artifact.toRootRelativePaths(action.getInputs())) + .containsAllOf("objc2/a.h", "objc3/b.h", "objc4/c.h", "objc4/a.m", "objc4/private.h"); + } + + static Iterable<String> iquoteArgs(ObjcProvider provider, BuildConfiguration configuration) { + return Interspersing.beforeEach( + "-iquote", + PathFragment.safePathStrings(ObjcCommon.userHeaderSearchPaths(provider, configuration))); + } + + // Override required for distinct compiler path + @Override + @Test + public void testCompilationActions_simulator() throws Exception { + useConfiguration("--cpu=ios_i386", "--ios_minimum_os=1.0"); + Platform platform = Platform.IOS_SIMULATOR; + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .setAndCreateFiles("non_arc_srcs", "not_arc.m") + .write(); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + CommandAction compileActionNotArc = compileAction("//objc:lib", "not_arc.o"); + + List<String> commonCompileFlags = + new ImmutableList.Builder<String>() + .add(MOCK_XCRUNWRAPPER_PATH) + .add(ObjcRuleClasses.CLANG) + .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=1.0") + .add("-arch", "i386") + .add("-isysroot", AppleToolchain.sdkDir()) + .add("-F", AppleToolchain.sdkDir() + "/Developer/Library/Frameworks") + .add("-F", frameworkDir(platform)) + .addAll(FASTBUILD_COPTS) + .addAll( + iquoteArgs( + getConfiguredTarget("//objc:lib").getProvider(ObjcProvider.class), + getTargetConfiguration())) + .build(); + + assertThat(compileActionA.getArguments()) + .isEqualTo( + new ImmutableList.Builder<String>() + .addAll(commonCompileFlags) + .add("-fobjc-arc") + .add("-c", "objc/a.m") + .addAll(outputArgs(compileActionA.getOutputs())) + .build()); + assertThat(compileActionNotArc.getArguments()) + .isEqualTo( + new ImmutableList.Builder<String>() + .addAll(commonCompileFlags) + .add("-fno-objc-arc") + .add("-c", "objc/not_arc.m") + .addAll(outputArgs(compileActionNotArc.getOutputs())) + .build()); + + assertRequiresDarwin(compileActionA); + try { + reporter.removeHandler(failFastHandler); + getTarget("//objc:c.o"); + fail("should have thrown"); + } catch (NoSuchTargetException expected) {} + assertThat(baseArtifactNames(compileActionA.getOutputs())).containsExactly("a.o", "a.d"); + assertThat(baseArtifactNames(compileActionA.getInputs())) + .containsExactly("a.m", "c.h", "private.h", XCRUNWRAPPER); + } + + // Override required for distinct compiler path + @Override + @Test + public void testCompilationActions_device() throws Exception { + useConfiguration("--cpu=ios_armv7", "--ios_minimum_os=1.0"); + Platform platform = Platform.IOS_DEVICE; + + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .setAndCreateFiles("non_arc_srcs", "not_arc.m") + .write(); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + CommandAction compileActionNotArc = compileAction("//objc:lib", "not_arc.o"); + + List<String> commonCompileFlags = + new ImmutableList.Builder<String>() + .add(MOCK_XCRUNWRAPPER_PATH) + .add(ObjcRuleClasses.CLANG) + .addAll(AppleToolchain.DEFAULT_WARNINGS.values()) + .addAll(CompilationSupport.DEFAULT_COMPILER_FLAGS) + .add("-miphoneos-version-min=1.0") + .add("-arch", "armv7") + .add("-isysroot", AppleToolchain.sdkDir()) + .add("-F", AppleToolchain.sdkDir() + "/Developer/Library/Frameworks") + .add("-F", frameworkDir(platform)) + .addAll(FASTBUILD_COPTS) + .addAll( + iquoteArgs( + getConfiguredTarget("//objc:lib").getProvider(ObjcProvider.class), + getTargetConfiguration())) + .build(); + + assertThat(compileActionA.getArguments()) + .isEqualTo( + new ImmutableList.Builder<String>() + .addAll(commonCompileFlags) + .add("-fobjc-arc") + .add("-c", "objc/a.m") + .addAll(outputArgs(compileActionA.getOutputs())) + .build()); + assertThat(compileActionNotArc.getArguments()) + .isEqualTo( + new ImmutableList.Builder<String>() + .addAll(commonCompileFlags) + .add("-fno-objc-arc") + .add("-c", "objc/not_arc.m") + .addAll(outputArgs(compileActionNotArc.getOutputs())) + .build()); + + assertRequiresDarwin(compileActionA); + try { + reporter.removeHandler(failFastHandler); + getTarget("//objc:c.o"); + fail("should have thrown"); + } catch (NoSuchTargetException expected) {} + assertThat(baseArtifactNames(compileActionA.getOutputs())).containsExactly("a.o", "a.d"); + assertThat(baseArtifactNames(compileActionA.getInputs())) + .containsExactly("a.m", "c.h", "private.h", XCRUNWRAPPER); + } + + // Test with ios device SDK version 9.0. Framework path differs from previous versions. + @Test + public void testCompilationActions_deviceSdk9() throws Exception { + useConfiguration("--cpu=ios_armv7", "--ios_minimum_os=1.0", "--ios_sdk_version=9.0"); + + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + + CommandAction compileAction = compileAction("//objc:lib", "a.o"); + + assertThat(compileAction.getArguments()).containsAllOf( + "-F", AppleToolchain.sdkDir() + AppleToolchain.SYSTEM_FRAMEWORK_PATH).inOrder(); + } + + @Test + public void testCompilationActionsWithPch() throws Exception { + Platform platform = Platform.IOS_SIMULATOR; + scratch.file("objc/foo.pch"); + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .set("pch", "'some.pch'") + .write(); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + + assertThat(compileActionA.getArguments()) + .containsExactlyElementsIn( + new ImmutableList.Builder<String>() + .add(MOCK_XCRUNWRAPPER_PATH) + .add(ObjcRuleClasses.CLANG) + .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", "x86_64") + .add("-isysroot", AppleToolchain.sdkDir()) + .add("-F", AppleToolchain.sdkDir() + "/Developer/Library/Frameworks") + .add("-F", frameworkDir(platform)) + .addAll(FASTBUILD_COPTS) + .addAll( + iquoteArgs( + getConfiguredTarget("//objc:lib").getProvider(ObjcProvider.class), + getAppleCrosstoolConfiguration())) + .add("-include", "objc/some.pch") + .add("-fobjc-arc") + .add("-c", "objc/a.m") + .addAll(outputArgs(compileActionA.getOutputs())) + .build()) + .inOrder(); + + assertThat(compileActionA.getInputs()).contains( + getFileConfiguredTarget("//objc:some.pch").getArtifact()); + } + + // Override required for distinct compiler path + @Override + @Test + public void testCompilationActionsWithCopts() throws Exception { + useConfiguration("--cpu=ios_i386"); + Platform platform = Platform.IOS_SIMULATOR; + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .setList("copts", "-Ifoo", "--monkeys=$(TARGET_CPU)") + .write(); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + + assertThat(compileActionA.getArguments()) + .containsExactlyElementsIn( + new ImmutableList.Builder<String>() + .add(MOCK_XCRUNWRAPPER_PATH) + .add(ObjcRuleClasses.CLANG) + .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() + "/Developer/Library/Frameworks") + .add("-F", frameworkDir(platform)) + .addAll(FASTBUILD_COPTS) + .addAll( + iquoteArgs( + getConfiguredTarget("//objc:lib").getProvider(ObjcProvider.class), + getTargetConfiguration())) + .add("-fobjc-arc") + .add("-Ifoo") + .add("--monkeys=ios_i386") + .add("-c", "objc/a.m") + .addAll(outputArgs(compileActionA.getOutputs())) + .build()) + .inOrder(); + } + + // Override required since module map is not included in action inputs for the crosstool case + @Override + @Test + public void testCompilationActionsWithModuleMapsEnabled() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_enable_module_maps"); + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + assertThat(compileActionA.getArguments()) + .containsAllIn(moduleMapArtifactArguments("//objc", "lib")); + assertThat(compileActionA.getArguments()).contains("-fmodule-maps"); + assertThat(Artifact.toRootRelativePaths(compileActionA.getInputs())) + .contains("objc/lib.modulemaps/module.modulemap"); + } + + @Test + public void testCompilationActionsWithCoptFmodules() throws Exception { + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .setList("copts", "-fmodules") + .write(); + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + assertThat(compileActionA.getArguments()).containsAllOf("-fmodules", + "-fmodules-cache-path=" + getModulesCachePath()); + } + + @Test + public void testCompilationActionsWithCoptFmodulesCachePath() throws Exception { + checkWarning("objc", "lib", CompilationSupport.MODULES_CACHE_PATH_WARNING, + "objc_library(", + " name = 'lib',", + " srcs = ['a.m'],", + " copts = ['-fmodules', '-fmodules-cache-path=foobar']", + ")"); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + assertThat(compileActionA.getArguments()).containsAllOf("-fmodules", + "-fmodules-cache-path=" + getModulesCachePath()); + } + + // Override required for distinct libtool path + @Override + @Test + public void testArchiveAction_simulator() throws Exception { + useConfiguration("--cpu=ios_i386"); + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + CommandAction archiveAction = archiveAction("//objc:lib"); + assertThat(archiveAction.getArguments()) + .isEqualTo( + ImmutableList.of( + MOCK_LIBTOOL_PATH, + "-static", + "-filelist", + getBinArtifact("lib-archive.objlist", "//objc:lib").getExecPathString(), + "-arch_only", + "i386", + "-syslibroot", + AppleToolchain.sdkDir(), + "-o", + Iterables.getOnlyElement(archiveAction.getOutputs()).getExecPathString())); + assertThat(baseArtifactNames(archiveAction.getInputs())) + .containsExactly("a.o", "b.o", "lib-archive.objlist", LIBTOOL); + assertThat(baseArtifactNames(archiveAction.getOutputs())).containsExactly("liblib.a"); + assertRequiresDarwin(archiveAction); + } + + // Override required for distinct libtool path + @Override + @Test + public void testArchiveAction_device() throws Exception { + useConfiguration("--cpu=ios_armv7"); + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + CommandAction archiveAction = archiveAction("//objc:lib"); + assertThat(archiveAction.getArguments()) + .isEqualTo( + ImmutableList.of( + MOCK_LIBTOOL_PATH, + "-static", + "-filelist", + getBinArtifact("lib-archive.objlist", "//objc:lib").getExecPathString(), + "-arch_only", + "armv7", + "-syslibroot", + AppleToolchain.sdkDir(), + "-o", + Iterables.getOnlyElement(archiveAction.getOutputs()).getExecPathString())); + assertThat(baseArtifactNames(archiveAction.getInputs())) + .containsExactly("a.o", "b.o", "lib-archive.objlist", LIBTOOL); + assertThat(baseArtifactNames(archiveAction.getOutputs())).containsExactly("liblib.a"); + assertRequiresDarwin(archiveAction); + } + + // Override required for distinct libtool path + @Override + @Test + public void testFullyLinkArchiveAction_simulator() throws Exception { + useConfiguration("--cpu=ios_i386"); + createLibraryTargetWriter("//objc:lib_dep") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "a.h", "b.h") + .write(); + createLibraryTargetWriter("//objc2:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h", "d.h") + .setList("deps", "//objc:lib_dep") + .write(); + SpawnAction archiveAction = (SpawnAction) getGeneratingActionForLabel( + "//objc2:lib_fully_linked.a"); + assertRequiresDarwin(archiveAction); + assertThat(archiveAction.getArguments()) + .isEqualTo( + ImmutableList.of( + MOCK_LIBTOOL_PATH, + "-static", + "-arch_only", + "i386", + "-syslibroot", + AppleToolchain.sdkDir(), + "-o", + Iterables.getOnlyElement(archiveAction.getOutputs()).getExecPathString(), + getBinArtifact("liblib.a", "//objc2:lib").getExecPathString(), + getBinArtifact("liblib_dep.a", "//objc:lib_dep").getExecPathString())); + assertThat(baseArtifactNames(archiveAction.getInputs())) + .containsExactly("liblib_dep.a", "liblib.a", LIBTOOL); + } + + // Override required for distinct libtool path + @Override + @Test + public void testFullyLinkArchiveAction_device() throws Exception { + useConfiguration("--cpu=ios_armv7"); + createLibraryTargetWriter("//objc:lib_dep") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "a.h", "b.h") + .write(); + createLibraryTargetWriter("//objc2:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h", "d.h") + .setList("deps", "//objc:lib_dep") + .write(); + SpawnAction archiveAction = (SpawnAction) getGeneratingActionForLabel( + "//objc2:lib_fully_linked.a"); + assertRequiresDarwin(archiveAction); + assertThat(archiveAction.getArguments()) + .isEqualTo( + ImmutableList.of( + MOCK_LIBTOOL_PATH, + "-static", + "-arch_only", + "armv7", + "-syslibroot", + AppleToolchain.sdkDir(), + "-o", + Iterables.getOnlyElement(archiveAction.getOutputs()).getExecPathString(), + getBinArtifact("liblib.a", "//objc2:lib").getExecPathString(), + getBinArtifact("liblib_dep.a", "//objc:lib_dep").getExecPathString())); + assertThat(baseArtifactNames(archiveAction.getInputs())) + .containsExactly("liblib_dep.a", "liblib.a", LIBTOOL); + } + + @Test + public void checkStoresCcLibsAsCc() throws Exception { + ScratchAttributeWriter.fromLabelString(this, "cc_library", "//cc:lib") + .setAndCreateFiles("srcs", "a.cc") + .write(); + scratch.file( + "third_party/cc_lib/BUILD", + "licenses(['unencumbered'])", + "cc_library(", + " name = 'cc_lib_impl',", + " srcs = [", + " 'v1/a.c',", + " 'v1/a.h',", + " ],", + ")", + "", + "cc_inc_library(", + " name = 'cc_lib',", + " hdrs = ['v1/a.h'],", + " prefix = 'v1',", + " deps = [':cc_lib_impl'],", + ")"); + createLibraryTargetWriter("//objc2:lib") + .setAndCreateFiles("srcs", "a.m", "b.m") + .setAndCreateFiles("hdrs", "c.h", "d.h") + .setList("deps", "//cc:lib", "//third_party/cc_lib:cc_lib_impl") + .write(); + ObjcProvider objcProvider = providerForTarget("//objc2:lib"); + + Iterable<Artifact> linkerInputArtifacts = + Iterables.transform(objcProvider.get(CC_LIBRARY), new Function<LinkerInput, Artifact>() { + @Override + public Artifact apply(LinkerInput library) { + return library.getArtifact(); + } + }); + + assertThat(linkerInputArtifacts) + .containsAllOf( + getBinArtifact( + "liblib.a", getConfiguredTarget("//cc:lib", getAppleCrosstoolConfiguration())), + getBinArtifact( + "libcc_lib_impl.a", + getConfiguredTarget( + "//third_party/cc_lib:cc_lib_impl", getAppleCrosstoolConfiguration()))); + } + + @Test + public void testCollectsSdkFrameworksTransitively() throws Exception { + createLibraryTargetWriter("//base_lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("sdk_frameworks", "foo") + .write(); + createLibraryTargetWriter("//depender_lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("sdk_frameworks", "bar") + .setList("deps", "//base_lib:lib") + .write(); + + ObjcProvider baseProvider = providerForTarget("//base_lib:lib"); + ObjcProvider dependerProvider = providerForTarget("//depender_lib:lib"); + + Set<SdkFramework> baseFrameworks = ImmutableSet.of(new SdkFramework("foo")); + Set<SdkFramework> dependerFrameworks = + ImmutableSet.of(new SdkFramework("foo"), new SdkFramework("bar")); + assertThat(baseProvider.get(SDK_FRAMEWORK)).containsExactlyElementsIn(baseFrameworks); + assertThat(dependerProvider.get(SDK_FRAMEWORK)).containsExactlyElementsIn(dependerFrameworks); + + // Make sure that the archive action does not actually include the frameworks. This is needed + // for creating binaries but is ignored for libraries. + CommandAction archiveAction = archiveAction("//depender_lib:lib"); + assertThat(archiveAction.getArguments()) + .isEqualTo( + new ImmutableList.Builder<String>() + .add(MOCK_LIBTOOL_PATH) + .add("-static") + .add("-filelist") + .add( + getBinArtifact("lib-archive.objlist", "//depender_lib:lib").getExecPathString()) + .add("-arch_only", "x86_64") + .add("-syslibroot") + .add(AppleToolchain.sdkDir()) + .add("-o") + .addAll(Artifact.toExecPaths(archiveAction.getOutputs())) + .build()); + } + + @Test + public void testMultipleRulesCompilingOneSourceGenerateUniqueObjFiles() throws Exception { + scratch.file("lib/a.m"); + scratch.file("lib/BUILD", + "objc_library(name = 'lib1', srcs = ['a.m'], copts = ['-Ilib1flag'])", + "objc_library(name = 'lib2', srcs = ['a.m'], copts = ['-Ilib2flag'])"); + Artifact obj1 = Iterables.getOnlyElement( + inputsEndingWith(archiveAction("//lib:lib1"), ".o")); + Artifact obj2 = Iterables.getOnlyElement( + inputsEndingWith(archiveAction("//lib:lib2"), ".o")); + + // The exec paths of each obj file should be based on the objc_library target. + assertThat(obj1.getExecPathString()).contains("lib1"); + assertThat(obj1.getExecPathString()).doesNotContain("lib2"); + assertThat(obj2.getExecPathString()).doesNotContain("lib1"); + assertThat(obj2.getExecPathString()).contains("lib2"); + + SpawnAction compile1 = (SpawnAction) getGeneratingAction(obj1); + SpawnAction compile2 = (SpawnAction) getGeneratingAction(obj2); + assertThat(compile1.getArguments()).contains("-Ilib1flag"); + assertThat(compile2.getArguments()).contains("-Ilib2flag"); + } + + @Test + public void testIncludesDirsOfTransitiveDepsGetPassedToCompileAction() throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("includes", "third_party/foo", "opensource/bar") + .write(); + + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("includes", "more_includes") + .setList("deps", "//lib1:lib1") + .write(); + CommandAction compileAction = compileAction("//lib2:lib2", "a.o"); + assertContainsSublist( + compileAction.getArguments(), + ImmutableList.copyOf( + Interspersing.beforeEach( + "-I", + rootedIncludePaths( + getAppleCrosstoolConfiguration(), + "lib2/more_includes", + "lib1/third_party/foo", + "lib1/opensource/bar")))); + } + + @Test + public void testIncludesDirsOfTransitiveCcDepsGetPassedToCompileAction() throws Exception { + scratch.file("package/BUILD", + "cc_library(", + " name = 'cc_lib',", + " srcs = ['a.cc'],", + " includes = ['foo/bar'],", + ")", + "", + "objc_library(", + " name = 'objc_lib',", + " srcs = ['b.m'],", + " deps = [':cc_lib'],", + ")"); + + CommandAction compileAction = compileAction("//package:objc_lib", "b.o"); + assertContainsSublist( + compileAction.getArguments(), + ImmutableList.copyOf( + Interspersing.beforeEach( + "-isystem", + rootedIncludePaths(getAppleCrosstoolConfiguration(), "package/foo/bar")))); + } + + @Test + public void testIncludesDirsOfTransitiveCcIncDepsGetPassedToCompileAction() throws Exception { + scratch.file( + "third_party/cc_lib/BUILD", + "licenses(['unencumbered'])", + "cc_library(", + " name = 'cc_lib_impl',", + " srcs = [", + " 'v1/a.c',", + " 'v1/a.h',", + " ],", + ")", + "", + "cc_inc_library(", + " name = 'cc_lib',", + " hdrs = ['v1/a.h'],", + " prefix = 'v1',", + " deps = [':cc_lib_impl'],", + ")"); + + scratch.file( + "package/BUILD", + "objc_library(", + " name = 'objc_lib',", + " srcs = ['b.m'],", + " deps = ['//third_party/cc_lib:cc_lib'],", + ")"); + + CommandAction compileAction = compileAction("//package:objc_lib", "b.o"); + String includeDir = + getAppleCrosstoolConfiguration() + .getIncludeDirectory(RepositoryName.MAIN) + .getExecPathString() + + "/third_party/cc_lib/_/cc_lib"; + assertContainsSublist(compileAction.getArguments(), ImmutableList.of("-I", includeDir)); + } + + @Test + public void testIncludesIquoteFlagForGenFilesRoot() throws Exception { + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .write(); + CommandAction compileAction = compileAction("//lib:lib", "a.o"); + BuildConfiguration config = getAppleCrosstoolConfiguration(); + assertContainsSublist(compileAction.getArguments(), ImmutableList.of( + "-iquote", config.getGenfilesFragment().getSafePathString())); + assertThat(compileAction.getArguments()).doesNotContain( + config.getBinFragment().getSafePathString()); + } + + @Test + public void testProvidesHdrsAndIncludes() throws Exception { + checkProvidesHdrsAndIncludes(RULE_TYPE); + } + + @Test + public void testCompilesWithHdrs() throws Exception { + checkCompilesWithHdrs(RULE_TYPE); + } + + @Test + public void testReceivesTransitivelyPropagatedDefines() throws Exception { + checkReceivesTransitivelyPropagatedDefines(RULE_TYPE); + } + + @Test + public void testSdkIncludesUsedInCompileAction() throws Exception { + checkSdkIncludesUsedInCompileAction(RULE_TYPE); + } + + @Test + public void testSdkIncludesUsedInCompileActionsOfDependers() throws Exception { + checkSdkIncludesUsedInCompileActionsOfDependers(RULE_TYPE); + } + + @Test + public void testCompilesAssemblyS() throws Exception { + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.s") + .setAndCreateFiles("hdrs", "c.h") + .write(); + + CommandAction compileAction = compileAction("//objc:lib", "b.o"); + + assertThat(compileAction.getArguments()).doesNotContain("-x"); + assertThat(compileAction.getArguments()).doesNotContain("assembler-with-cpp"); + assertThat(baseArtifactNames(compileAction.getOutputs())).containsExactly("b.o", "b.d"); + assertThat(baseArtifactNames(compileAction.getInputs())) + .containsExactly("c.h", "b.s", XCRUNWRAPPER); + } + + @Test + public void testCompilesAssemblyAsm() throws Exception { + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.asm") + .setAndCreateFiles("hdrs", "c.h") + .write(); + + CommandAction compileAction = compileAction("//objc:lib", "b.o"); + + assertThat(compileAction.getArguments()).doesNotContain("-x"); + assertThat(compileAction.getArguments()).doesNotContain("assembler-with-cpp"); + assertThat(baseArtifactNames(compileAction.getOutputs())).containsExactly("b.o", "b.d"); + assertThat(baseArtifactNames(compileAction.getInputs())) + .containsExactly("c.h", "b.asm", XCRUNWRAPPER); + } + + @Test + public void testCompilesAssemblyWithPreprocessing() throws Exception { + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.S") + .setAndCreateFiles("hdrs", "c.h") + .write(); + + CommandAction compileAction = compileAction("//objc:lib", "b.o"); + + // Clang automatically preprocesses .S files, so the assembler-with-cpp flag is unnecessary. + // Regression test for b/22636858. + assertThat(compileAction.getArguments()).doesNotContain("-x"); + assertThat(compileAction.getArguments()).doesNotContain("assembler-with-cpp"); + assertThat(baseArtifactNames(compileAction.getOutputs())).containsExactly("b.o", "b.d"); + assertThat(baseArtifactNames(compileAction.getInputs())) + .containsExactly("c.h", "b.S", XCRUNWRAPPER); + } + + // Converts output artifacts into expected command-line arguments. + private List<String> outputArgs(Set<Artifact> outputs) { + ImmutableList.Builder<String> result = new ImmutableList.Builder<>(); + for (String output : Artifact.toExecPaths(outputs)) { + if (output.endsWith(".o")) { + result.add("-o", output); + } else if (output.endsWith(".d")) { + result.add("-MD", "-MF", output); + } else { + throw new IllegalArgumentException( + "output " + output + " has unknown ending (not in (.d, .o)"); + } + } + return result.build(); + } + + // Dotd pruning must be tested seperately for the legacy case, since it involves the + // ObjcCompileAction. + @Override + @Test + public void testUsesDotdPruning() throws Exception { + createLibraryTargetWriter("//lib:lib").setList("srcs", "a.m").write(); + CommandAction compileAction = compileAction("//lib:lib", "a.o"); + assertThat(compileAction).isInstanceOf(ObjcCompileAction.class); + assertThat(((ObjcCompileAction) compileAction).getDotdPruningPlan()) + .isEqualTo(HeaderDiscovery.DotdPruningMode.USE); + } + + @Override + @Test + public void testDoesNotUseDotdPruning() throws Exception { + useConfiguration("--objc_use_dotd_pruning=false"); + createLibraryTargetWriter("//lib:lib").setList("srcs", "a.m").write(); + CommandAction compileAction = compileAction("//lib:lib", "a.o"); + assertThat(compileAction).isInstanceOf(ObjcCompileAction.class); + assertThat(((ObjcCompileAction) compileAction).getDotdPruningPlan()) + .isEqualTo(HeaderDiscovery.DotdPruningMode.DO_NOT_USE); + } + + // Override required because CppCompileAction#getPossibleInputsForTesting is not available to + // test for the presence of inputs that will be pruned. + @Override + @Test + public void testPrecompiledHeaders() throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + scratch.file("objc/a.m"); + scratch.file("objc/c.pch"); + scratch.file( + "objc/BUILD", + RULE_TYPE.target( + scratch, "objc", "x", "srcs", "['a.m']", "non_arc_srcs", "['b.m']", "pch", "'c.pch'")); + CommandAction compileAction = compileAction("//objc:x", "a.o"); + assertThat(Joiner.on(" ").join(compileAction.getArguments())).contains("-include objc/c.pch"); + } + + // Override required because CppCompileAction#getPossibleInputsForTesting is not available to + // test for the presence of inputs that will be pruned. + @Override + @Test + public void testCompilesSources() throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + createLibraryTargetWriter("//objc/lib1") + .setAndCreateFiles("srcs", "a.m") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + + createLibraryTargetWriter("//objc/lib2") + .setAndCreateFiles("srcs", "a.m") + .setAndCreateFiles("hdrs", "hdr.h") + .setList("deps", "//objc/lib1") + .write(); + + createLibraryTargetWriter("//objc:x") + .setAndCreateFiles("srcs", "a.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .setList("deps", "//objc/lib2:lib2") + .write(); + + CommandAction compileA = compileAction("//objc:x", "a.o"); + + assertThat(Artifact.toRootRelativePaths(compileA.getInputs())) + .contains("objc/a.m"); + assertThat(Artifact.toRootRelativePaths(compileA.getOutputs())) + .containsExactly("objc/_objs/x/objc/a.o", "objc/_objs/x/objc/a.d"); + } + + + @Override + @Test + public void testCompilationModeDbg() throws Exception { + // Feature requires crosstool + } + + @Override + @Test + public void testCompilationModeFastbuild() throws Exception { + // Feature requires crosstool + } + + @Override + @Test + public void testCompilationModeOpt() throws Exception { + // Feature requires crosstool + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcProtoLibraryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcProtoLibraryTest.java new file mode 100644 index 0000000000..ff99cefb5e --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/LegacyObjcProtoLibraryTest.java @@ -0,0 +1,125 @@ +// 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.collect.ImmutableList; +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.rules.apple.AppleToolchain; +import com.google.devtools.build.lib.rules.apple.Platform; +import com.google.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Legacy test: These tests test --experimental_objc_crosstool=off. See README. + */ +@RunWith(JUnit4.class) +public class LegacyObjcProtoLibraryTest extends ObjcProtoLibraryTest { + @Override + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.OFF; + } + + @Test + public void testCompilationAction() throws Exception { + useConfiguration("--cpu=ios_i386"); + Platform platform = Platform.IOS_SIMULATOR; + + ConfiguredTarget target = getConfiguredTarget("//package:opl"); + CommandAction linkAction = + (CommandAction) getGeneratingAction(getBinArtifact("libopl.a", target)); + + CommandAction compileAction = + (CommandAction) + getGeneratingAction( + ActionsTestUtil.getFirstArtifactEndingWith(linkAction.getInputs(), "/FileA.pb.o")); + + Artifact sourceFile = + ActionsTestUtil.getFirstArtifactEndingWith( + getFilesToBuild(getConfiguredTarget("//package:opl")), "/FileA.pb.m"); + + Artifact objectFile = + ActionsTestUtil.getFirstArtifactEndingWith(compileAction.getOutputs(), ".o"); + Artifact dotdFile = + ActionsTestUtil.getFirstArtifactEndingWith(compileAction.getOutputs(), ".d"); + assertThat(compileAction.getArguments()) + .containsExactlyElementsIn( + new ImmutableList.Builder<String>() + .add(MOCK_XCRUNWRAPPER_PATH) + .add(ObjcRuleClasses.CLANG) + .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( + LegacyObjcLibraryTest.iquoteArgs( + target.getProvider(ObjcProvider.class), getTargetConfiguration())) + .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()) + .inOrder(); + assertRequiresDarwin(compileAction); + assertThat(Artifact.toRootRelativePaths(compileAction.getInputs())).containsAllOf( + "package/_generated_protos/opl/package/FileA.pb.m", + "package/_generated_protos/opl/package/FileA.pb.h", + "package/_generated_protos/opl/package/dir/FileB.pb.h", + "package/_generated_protos/opl/dep/File.pb.h" + ); + } + + @Test + public void testLibraryLinkAction() throws Exception { + useConfiguration("--cpu=ios_armv7"); + Artifact libFile = + ActionsTestUtil.getFirstArtifactEndingWith( + getFilesToBuild(getConfiguredTarget("//package:opl")), "/libopl.a"); + CommandAction action = (CommandAction) getGeneratingAction(libFile); + assertThat(action.getArguments()) + .isEqualTo( + ImmutableList.of( + MOCK_LIBTOOL_PATH, + "-static", + "-filelist", + getBinArtifact("opl-archive.objlist", "//package:opl").getExecPathString(), + "-arch_only", + "armv7", + "-syslibroot", + AppleToolchain.sdkDir(), + "-o", + libFile.getExecPathString())); + assertRequiresDarwin(action); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcBinaryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcBinaryTest.java new file mode 100644 index 0000000000..c167d312b5 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcBinaryTest.java @@ -0,0 +1,918 @@ +// 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 static com.google.devtools.build.lib.rules.objc.BinaryLinkingTargetFactory.REQUIRES_AT_LEAST_ONE_LIBRARY_OR_SOURCE_FILE; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.APP_ICON_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.LAUNCH_IMAGE_ATTR; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Multiset; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.RunfilesProvider; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.analysis.config.CompilationMode; +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.objc.CompilationSupport.ExtraLinkArgs; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.BundleFile; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.MergeZip; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for objc_binary. */ +@RunWith(JUnit4.class) +public class ObjcBinaryTest extends ObjcRuleTestCase { + static final RuleType RULE_TYPE = new BinaryRuleType("objc_binary"); + + protected ConfiguredTarget addMockBinAndLibs(List<String> srcs) throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + + return createBinaryTargetWriter("//bin:bin") + .setAndCreateFiles("srcs", srcs) + .setList("deps", "//lib1:lib1", "//lib2:lib2") + .write(); + } + + @Before + public final void initializeToolsConfigMock() throws Exception { + MockProtoSupport.setup(mockToolsConfig); + MockObjcSupport.setupObjcProto(mockToolsConfig); + } + + @Test + public void testCreate_runfiles() throws Exception { + ConfiguredTarget binary = addMockBinAndLibs(ImmutableList.of("a.m")); + RunfilesProvider runfiles = binary.getProvider(RunfilesProvider.class); + assertThat(runfiles.getDefaultRunfiles().getArtifacts()).isEmpty(); + assertThat(Artifact.toRootRelativePaths(runfiles.getDataRunfiles().getArtifacts())) + .containsExactly( + "bin/bin.ipa", + "bin/bin_bin"); + } + + @Test + public void testFilesToRun() throws Exception { + checkFilesToRun(RULE_TYPE); + } + + @Test + public void testNoRunfilesSupportForDevice() throws Exception { + checkNoRunfilesSupportForDevice(RULE_TYPE); + } + + @Test + public void testGenerateRunnerScriptAction() throws Exception { + checkGenerateRunnerScriptAction(RULE_TYPE); + } + + @Test + public void testGenerateRunnerScriptAction_escaped() throws Exception { + checkGenerateRunnerScriptAction_escaped(RULE_TYPE); + } + + @Test + public void testLinkActionDuplicateInputs() throws Exception { + checkLinkActionDuplicateInputs(RULE_TYPE, new ExtraLinkArgs()); + } + + @Test + /** + * Tests that bitcode is disabled for simulator builds even if enabled by flag. + */ + public void testLinkActionsWithBitcode_simulator() throws Exception { + useConfiguration("--xcode_version=7.1", "--apple_bitcode=embedded", + "--ios_multi_cpus=x86_64"); + createBinaryTargetWriter("//objc:bin").setAndCreateFiles("srcs", "a.m").write(); + + CommandAction linkAction = linkAction("//objc:bin"); + + String commandLine = Joiner.on(" ").join(linkAction.getArguments()); + assertThat(commandLine).doesNotContain("-fembed-bitcode"); + assertThat(commandLine).doesNotContain("-fembed-bitcode-marker"); + } + + @Test + public void testLinkActionsWithNoBitcode() throws Exception { + useConfiguration("--xcode_version=7.1", "--apple_bitcode=none", + "--ios_multi_cpus=arm64"); + createBinaryTargetWriter("//objc:bin").setAndCreateFiles("srcs", "a.m").write(); + + CommandAction linkAction = linkAction("//objc:bin"); + + String commandLine = Joiner.on(" ").join(linkAction.getArguments()); + assertThat(commandLine).doesNotContain("-fembed-bitcode"); + assertThat(commandLine).doesNotContain("-fembed-bitcode-marker"); + } + + @Test + public void testSigningAction() throws Exception { + checkDeviceSigningAction(RULE_TYPE); + } + + @Test + public void testSigningWithCertName() throws Exception { + checkSigningWithCertName(RULE_TYPE); + } + + @Test + public void testProvisioningProfile_simulatorBuild() throws Exception { + useConfiguration("--cpu=ios_i386"); + addMockBinAndLibs(ImmutableList.of("a.m")); + + Artifact provisioningProfile = + getFileConfiguredTarget("//tools/objc:foo.mobileprovision").getArtifact(); + SpawnAction spawnAction = bundleMergeAction("//bin:bin"); + assertThat(spawnAction.getInputs()).doesNotContain(provisioningProfile); + + BundleMergeProtos.Control control = bundleMergeControl("//bin:bin"); + assertThat(mobileProvisionProfiles(control)).isEmpty(); + } + + @Test + public void testProvisioningProfile_deviceBuild() throws Exception { + useConfiguration("--cpu=ios_armv7"); + + addMockBinAndLibs(ImmutableList.of("a.m")); + + Artifact provisioningProfile = + getFileConfiguredTarget("//tools/objc:foo.mobileprovision").getArtifact(); + SpawnAction spawnAction = bundleMergeAction("//bin:bin"); + assertThat(spawnAction.getInputs()).contains(provisioningProfile); + + BundleMergeProtos.Control control = bundleMergeControl("//bin:bin"); + Map<String, String> profiles = mobileProvisionProfiles(control); + ImmutableMap<String, String> expectedProfiles = ImmutableMap.of( + provisioningProfile.getExecPathString(), + ReleaseBundlingSupport.PROVISIONING_PROFILE_BUNDLE_FILE); + assertThat(profiles).isEqualTo(expectedProfiles); + } + + @Test + public void testUserSpecifiedProvisioningProfile_deviceBuild() throws Exception { + useConfiguration("--cpu=ios_armv7"); + scratch.file("custom/BUILD", "exports_files(['pp.mobileprovision'])"); + scratch.file("custom/pp.mobileprovision"); + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createBinaryTargetWriter("//bin:bin") + .setAndCreateFiles("srcs", "a.m") + .setList("deps", "//lib1:lib1", "//lib2:lib2") + .set("provisioning_profile", "'//custom:pp.mobileprovision'") + .write(); + + Artifact defaultProvisioningProfile = + getFileConfiguredTarget("//tools/objc:foo.mobileprovision").getArtifact(); + Artifact customProvisioningProfile = + getFileConfiguredTarget("//custom:pp.mobileprovision").getArtifact(); + SpawnAction spawnAction = bundleMergeAction("//bin:bin"); + assertThat(spawnAction.getInputs()).contains(customProvisioningProfile); + assertThat(spawnAction.getInputs()).doesNotContain(defaultProvisioningProfile); + + BundleMergeProtos.Control control = bundleMergeControl("//bin:bin"); + Map<String, String> profiles = mobileProvisionProfiles(control); + Map<String, String> expectedProfiles = ImmutableMap.of( + customProvisioningProfile.getExecPathString(), + ReleaseBundlingSupport.PROVISIONING_PROFILE_BUNDLE_FILE); + assertThat(profiles).isEqualTo(expectedProfiles); + } + + @Test + public void testCreate_mergeControlAction() throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createBinaryTargetWriter("//bin:bin") + .setAndCreateFiles("srcs", "a.m") + .setList("deps", "//lib1:lib1", "//lib2:lib2") + .set("infoplist", "'bin-Info.plist'") + .write(); + + Action mergeAction = bundleMergeAction("//bin:bin"); + Action action = bundleMergeControlAction("//bin:bin"); + assertThat(action.getInputs()).isEmpty(); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())).containsExactly( + "bin/bin.ipa-control"); + assertThat(bundleMergeControl("//bin:bin")) + .isEqualTo( + BundleMergeProtos.Control.newBuilder() + .addBundleFile( + BundleFile.newBuilder() + .setSourceFile(execPathEndingWith(mergeAction.getInputs(), "bin")) + .setBundlePath("bin") + .setExternalFileAttribute(BundleableFile.EXECUTABLE_EXTERNAL_FILE_ATTRIBUTE) + .build()) + .setBundleRoot("Payload/bin.app") + .setBundleInfoPlistFile( + execPathEndingWith(mergeAction.getInputs(), "bin-MergedInfo.plist")) + .setOutFile(execPathEndingWith(mergeAction.getOutputs(), "bin.unprocessed.ipa")) + .setMinimumOsVersion(DEFAULT_IOS_SDK_VERSION.toString()) + .setSdkVersion(DEFAULT_IOS_SDK_VERSION.toString()) + .setPlatform("IOS_SIMULATOR") + .setFallbackBundleIdentifier("example.bin") + .build()); + } + + @Test + public void testCreate_mergeBundleAction() throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createBinaryTargetWriter("//bin:bin") + .setAndCreateFiles("srcs", "a.m") + .setList("deps", "//lib1:lib1", "//lib2:lib2") + .set("infoplist", "'bin-Info.plist'") + .write(); + + SpawnAction action = bundleMergeAction("//bin:bin"); + assertThat(Artifact.toRootRelativePaths(action.getInputs())) + .containsExactly( + MOCK_BUNDLEMERGE_PATH, + "bin/bin_lipobin", + "bin/bin.ipa-control", + "bin/bin-MergedInfo.plist"); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("bin/bin.unprocessed.ipa"); + assertNotRequiresDarwin(action); + assertThat(action.getEnvironment()).isEmpty(); + assertThat(action.getArguments()) + .containsExactly( + MOCK_BUNDLEMERGE_PATH, execPathEndingWith(action.getInputs(), "bin.ipa-control")) + .inOrder(); + } + + @Test + public void testCheckPrimaryBundleIdInMergedPlist() throws Exception { + checkPrimaryBundleIdInMergedPlist(RULE_TYPE); + } + + @Test + public void testCheckFallbackBundleIdInMergedPlist() throws Exception { + checkFallbackBundleIdInMergedPlist(RULE_TYPE); + } + + @Test + public void testCreate_errorForNoSourceOrDep() throws Exception { + scratch.file("x/Foo.plist"); + checkError("x", "x", REQUIRES_AT_LEAST_ONE_LIBRARY_OR_SOURCE_FILE, + "objc_binary(name='x')"); + } + + @Test + public void testCompileWithDotMFileInHeaders() throws Exception { + checkCompileWithDotMFileInHeaders(RULE_TYPE); + } + + @Test + public void testCreate_NoDebugSymbolActionWithoutAppleFlag() throws Exception { + checkNoDebugSymbolFileWithoutAppleFlag(RULE_TYPE); + } + + @Test + public void testErrorForLaunchImageGivenWithNoAssetCatalog() throws Exception { + checkAssetCatalogAttributeError(RULE_TYPE, LAUNCH_IMAGE_ATTR); + } + + @Test + public void testErrorForAppIconGivenWithNoAssetCatalog() throws Exception { + checkAssetCatalogAttributeError(RULE_TYPE, APP_ICON_ATTR); + } + + @Test + public void testCollectsAssetCatalogsTransitively() throws Exception { + scratch.file("lib/ac.xcassets/foo"); + scratch.file("lib/ac.xcassets/bar"); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .set("asset_catalogs", "glob(['ac.xcassets/**'])") + .write(); + scratch.file("bin/ac.xcassets/baz"); + scratch.file("bin/ac.xcassets/42"); + createBinaryTargetWriter("//bin:bin") + .setAndCreateFiles("srcs", "src.m") + .setList("deps", "//lib:lib") + .set("asset_catalogs", "glob(['ac.xcassets/**'])") + .write(); + + // Test that the actoolzip Action has arguments and inputs obtained from dependencies. + SpawnAction actoolZipAction = actoolZipActionForIpa("//bin:bin"); + assertThat(Artifact.toExecPaths(actoolZipAction.getInputs())).containsExactly( + "lib/ac.xcassets/foo", "lib/ac.xcassets/bar", "bin/ac.xcassets/baz", "bin/ac.xcassets/42", + MOCK_ACTOOLWRAPPER_PATH); + assertContainsSublist(actoolZipAction.getArguments(), + ImmutableList.of("lib/ac.xcassets", "bin/ac.xcassets")); + } + + @Test + public void testCcDependencyLinkoptsArePropagatedToLinkAction() throws Exception { + useConfiguration("--experimental_disable_go", "--experimental_disable_jvm", "--cpu=ios_i386", + "--crosstool_top=//tools/osx/crosstool:crosstool"); + + scratch.file("bin/BUILD", + "cc_library(", + " name = 'cclib1',", + " srcs = ['dep1.c'],", + " linkopts = ['-framework F1', '-framework F2', '-Wl,--other-opt'],", + ")", + "cc_library(", + " name = 'cclib2',", + " srcs = ['dep2.c'],", + " linkopts = ['-another-opt', '-framework F2'],", + " deps = ['cclib1'],", + ")", + "cc_library(", + " name = 'cclib3',", + " srcs = ['dep2.c'],", + " linkopts = ['-one-more-opt', '-framework UIKit'],", + " deps = ['cclib1'],", + ")", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + " deps = [':cclib2', ':cclib3'],", + ")"); + + // Frameworks from the CROSSTOOL "apply_implicit_frameworks" feature should be present. + assertThat(Joiner.on(" ").join(linkAction("//bin").getArguments())) + .contains("-framework Foundation -framework UIKit"); + // Frameworks included in linkopts by the user should get placed together with no duplicates. + // (They may duplicate the ones inserted by the CROSSTOOL feature, but we don't test that here.) + assertThat(Joiner.on(" ").join(linkAction("//bin").getArguments())) + .contains("-framework F2 -framework F1"); + // Linkopts should also be grouped together. + assertThat(Joiner.on(" ").join(linkAction("//bin").getArguments())) + .contains("-another-opt -Wl,--other-opt -one-more-opt"); + } + + @Test + public void testAlwaysLinkCcDependenciesAreForceLoaded() throws Exception { + useConfiguration("--experimental_disable_go", "--experimental_disable_jvm", "--cpu=ios_i386", + "--crosstool_top=//tools/osx/crosstool:crosstool"); + + scratch.file("bin/BUILD", + "cc_library(", + " name = 'cclib1',", + " srcs = ['dep1.c'],", + " alwayslink = 1,", + ")", + "cc_library(", + " name = 'cclib2',", + " srcs = ['dep2.c'],", + " deps = [':cclib1'],", + ")", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + " deps = [':cclib2'],", + ")"); + + // cclib1 is force loaded. + assertThat(Joiner.on(" ").join(linkAction("//bin").getArguments())) + .containsMatch(Pattern.compile(" -force_load [^\\s]+/libcclib1.lo\\b")); + } + + @Test + public void testSpecifyAppIconAndLaunchImageUsingXcassetsOfDependency() throws Exception { + checkSpecifyAppIconAndLaunchImageUsingXcassetsOfDependency(RULE_TYPE); + } + + @Test + public void testCreate_actoolAction() throws Exception { + addTargetWithAssetCatalogs(RULE_TYPE); + checkActoolActionCorrectness(DEFAULT_IOS_SDK_VERSION); + } + + @Test + public void testPassesFamiliesToActool() throws Exception { + checkPassesFamiliesToActool(RULE_TYPE); + } + + @Test + public void testPassesFamiliesToIbtool() throws Exception { + checkPassesFamiliesToIbtool(RULE_TYPE); + } + + @Test + public void testReportsErrorsForInvalidFamiliesAttribute() throws Exception { + checkReportsErrorsForInvalidFamiliesAttribute(RULE_TYPE); + } + + @SuppressWarnings("deprecation") // getMergeWithoutNamePrefixZipList is deprecated + @Test + public void testCreate_mergeActionsWithAssetCatalog() throws Exception { + // TODO(matvore): add this test to IosTestTest.java. + addTargetWithAssetCatalogs(RULE_TYPE); + + Artifact actoolZipOut = getBinArtifact("x.actool.zip", "//x:x"); + assertThat(bundleMergeAction("//x:x").getInputs()).contains(actoolZipOut); + + BundleMergeProtos.Control mergeControl = bundleMergeControl("//x:x"); + assertThat(mergeControl.getMergeZipList()).containsExactly(MergeZip.newBuilder() + .setEntryNamePrefix("Payload/x.app/") + .setSourcePath(actoolZipOut.getExecPathString()) + .build()); + } + + private void addBinAndLibWithRawResources() throws Exception { + addBinAndLibWithResources("resources", "resource1.txt", "ja.lproj/resource2.txt", + "objc_binary"); + } + + private void addBinAndLibWithStrings() throws Exception { + addBinAndLibWithResources("strings", "foo.strings", "ja.lproj/bar.strings", + "objc_binary"); + } + + @Test + public void testCollectsRawResourceFilesTransitively() throws Exception { + addBinAndLibWithRawResources(); + checkCollectsResourceFilesTransitively( + "//bin:bin", + ImmutableList.of("lib/resource1.txt", "bin/ja.lproj/resource2.txt"), + ImmutableList.of("lib/resource1.txt"), + ImmutableSetMultimap.<String, Multiset<String>>of( + "bin_bin", + ImmutableMultiset.of("bin/ja.lproj/resource2.txt", "lib/resource1.txt"), + "bin_static_lib_bin", + ImmutableMultiset.of("bin/ja.lproj/resource2.txt", "lib/resource1.txt"), + "lib_lib", + ImmutableMultiset.of("lib/resource1.txt"))); + } + + @Test + public void testCollectsStringsFilesTransitively() throws Exception { + addBinAndLibWithStrings(); + checkCollectsResourceFilesTransitively( + "//bin:bin", + ImmutableList.of("bin/lib/foo.strings.binary", "bin/bin/ja.lproj/bar.strings.binary"), + ImmutableList.of("lib/foo.strings.binary"), + ImmutableSetMultimap.<String, Multiset<String>>of( + "bin_bin", + ImmutableMultiset.of("bin/ja.lproj/bar.strings", "lib/foo.strings"), + "bin_static_lib_bin", + ImmutableMultiset.of("bin/ja.lproj/bar.strings", "lib/foo.strings"), + "lib_lib", + ImmutableMultiset.of("lib/foo.strings"))); + } + + @Test + public void testResourceFilesMergedInBundle() throws Exception { + addBinAndLibWithRawResources(); + checkBundleablesAreMerged("//bin:bin", + ImmutableListMultimap.of( + "resource1.txt", "resource1.txt", + "ja.lproj/resource2.txt", "ja.lproj/resource2.txt")); + } + + @Test + public void testStringsFilesMergedInBundle() throws Exception { + addBinAndLibWithStrings(); + checkBundleablesAreMerged("//bin:bin", + ImmutableListMultimap.of( + "foo.strings.binary", "foo.strings", + "ja.lproj/bar.strings.binary", "ja.lproj/bar.strings")); + } + + @Test + public void testLinksFrameworksOfSelfAndTransitiveDependencies() throws Exception { + checkLinksFrameworksOfSelfAndTransitiveDependencies(RULE_TYPE); + } + + @Test + public void testLinksWeakFrameworksOfSelfAndTransitiveDependencies() throws Exception { + checkLinksWeakFrameworksOfSelfAndTransitiveDependencies(RULE_TYPE); + } + + @Test + public void testMergesXcdatamodelZips() throws Exception { + checkMergesXcdatamodelZips(RULE_TYPE); + } + + @Test + public void testPlistRequiresDotInName() throws Exception { + checkError("x", "x", + "'//x:Infoplist' does not produce any objc_binary infoplist files (expected .plist)", + "objc_binary(", + " name = 'x',", + " srcs = ['a.m'],", + " infoplist = 'Infoplist'", + ")"); + } + + @Test + public void testLinkIncludeOrder_staticLibsFirst() throws Exception { + checkLinkIncludeOrderStaticLibsFirst(RULE_TYPE); + } + + @Test + public void testLinkIncludeOrder_frameworksAndSystemLibsFirst() throws Exception { + checkLinkIncludeOrderFrameworksAndSystemLibsFirst(RULE_TYPE); + } + + @Test + public void testLinksDylibsTransitively() throws Exception { + checkLinksDylibsTransitively(RULE_TYPE); + } + + @Test + public void testPopulatesCompilationArtifacts() throws Exception { + checkPopulatesCompilationArtifacts(RULE_TYPE); + } + + @Test + public void testArchivesPrecompiledObjectFiles() throws Exception { + checkArchivesPrecompiledObjectFiles(RULE_TYPE); + } + + @Test + public void testPopulatesBundling() throws Exception { + checkPopulatesBundling(RULE_TYPE); + } + + @Test + public void testRegistersStoryboardCompilationActions() throws Exception { + checkRegistersStoryboardCompileActions(RULE_TYPE, "iphone"); + } + + @Test + public void testSwiftStdlibActions() throws Exception { + checkRegisterSwiftStdlibActions(RULE_TYPE, "iphonesimulator"); + } + + @Test + public void testSwiftStdlibActionsWithToolchain() throws Exception { + useConfiguration("--xcode_toolchain=test_toolchain"); + checkRegisterSwiftStdlibActions(RULE_TYPE, "iphonesimulator", "test_toolchain"); + } + + @Test + public void testRegistersSwiftSupportActions() throws Exception { + checkRegisterSwiftSupportActions(RULE_TYPE, "iphonesimulator"); + } + + @Test + public void testRegistersSwiftSupportActionsWithToolchain() throws Exception { + useConfiguration("--xcode_toolchain=test_toolchain"); + checkRegisterSwiftSupportActions(RULE_TYPE, "iphonesimulator", "test_toolchain"); + } + + @Test + public void testErrorsWrongFileTypeForSrcsWhenCompiling() throws Exception { + checkErrorsWrongFileTypeForSrcsWhenCompiling(RULE_TYPE); + } + + @Test + public void testObjcCopts() throws Exception { + checkObjcCopts(RULE_TYPE); + } + + @Test + public void testObjcCopts_argumentOrdering() throws Exception { + checkObjcCopts_argumentOrdering(RULE_TYPE); + } + + @Test + public void testMergesActoolPartialInfoplist() throws Exception { + checkMergesPartialInfoplists(RULE_TYPE); + } + + @Test + public void checkDefinesFromCcLibraryDep() throws Exception { + checkDefinesFromCcLibraryDep(RULE_TYPE); + } + + @Test + public void testCompileXibActions() throws Exception { + checkCompileXibActions(RULE_TYPE); + } + + @Test + public void testNibZipsMergedIntoBundle() throws Exception { + checkNibZipsMergedIntoBundle(RULE_TYPE); + } + + @Test + public void testAllowVariousNonBlacklistedTypesInHeaders() throws Exception { + checkAllowVariousNonBlacklistedTypesInHeaders(RULE_TYPE); + } + + @Test + public void testWarningForBlacklistedTypesInHeaders() throws Exception { + checkWarningForBlacklistedTypesInHeaders(RULE_TYPE); + } + + @Test + public void testCppSourceCompilesWithCppFlags() throws Exception { + checkCppSourceCompilesWithCppFlags(RULE_TYPE); + } + + @Test + public void testPassesFallbackBundleIdToBundleMerging() throws Exception { + checkBundleIdPassedAsFallbackId(RULE_TYPE); + } + + @Test + public void testPassesPrimaryBundleIdToBundleMerging() throws Exception { + checkBundleIdPassedAsPrimaryId(RULE_TYPE); + } + + @Test + public void testNestedBundleIdIsNotAffectedByParent() throws Exception { + scratch.file("bndl/BUILD", + "objc_bundle_library(", + " name = 'bndl',", + ")"); + + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("hdrs", "lib.h") + .setList("bundles", "//bndl:bndl") + .write(); + + createBinaryTargetWriter("//bin:bin") + .setAndCreateFiles("srcs", "a.m") + .setList("deps", "//lib:lib") + .set("bundle_id", "'com.main.bundle'") + .write(); + + BundleMergeProtos.Control control = bundleMergeControl("//bin:bin"); + + assertThat(control.getPrimaryBundleIdentifier()).isEqualTo("com.main.bundle"); + // The nested bndl should not get its parent's bundle_id + assertThat(control.getNestedBundleList().get(0).getPrimaryBundleIdentifier()) + .isNotEqualTo("com.main.bundle"); + } + + @Test + public void testAutomaticPlistEntries() throws Exception { + checkAutomaticPlistEntries(RULE_TYPE); + } + + @Test + public void testBundleMergeInputContainsPlMergeOutput() throws Exception { + checkBundleMergeInputContainsPlMergeOutput(RULE_TYPE); + } + + @Test + public void testMultipleInfoPlists() throws Exception { + checkMultipleInfoPlists(RULE_TYPE); + } + + @Test + public void testInfoplistAndInfoplistsTogether() throws Exception { + checkInfoplistAndInfoplistsTogether(RULE_TYPE); + } + + @Test + public void testLinkOpts() throws Exception { + checkLinkopts(RULE_TYPE); + } + + @Test + public void testProtoBundlingAndLinking() throws Exception { + checkProtoBundlingAndLinking(RULE_TYPE); + } + + @Test + public void testProtoBundlingWithTargetsWithNoDeps() throws Exception { + checkProtoBundlingWithTargetsWithNoDeps(RULE_TYPE); + } + + @Test + public void testCanUseCrosstool() throws Exception { + checkLinkingRuleCanUseCrosstool(RULE_TYPE); + } + + @Test + public void testBinaryStrippings() throws Exception { + checkBinaryStripAction(RULE_TYPE); + } + + @Test + public void testAppleSdkVersionEnv() throws Exception { + addMockBinAndLibs(ImmutableList.of("a.m")); + CommandAction action = linkAction("//bin:bin"); + + assertAppleSdkVersionEnv(action); + } + + @Test + public void testNonDefaultAppleSdkVersionEnv() throws Exception { + useConfiguration("--ios_sdk_version=8.1"); + + addMockBinAndLibs(ImmutableList.of("a.m")); + CommandAction action = linkAction("//bin:bin"); + + assertAppleSdkVersionEnv(action, "8.1"); + } + + @Test + public void testAppleSdkDefaultPlatformEnv() throws Exception { + addMockBinAndLibs(ImmutableList.of("a.m")); + CommandAction action = linkAction("//bin:bin"); + + assertAppleSdkPlatformEnv(action, "iPhoneSimulator"); + } + + @Test + public void testAppleSdkDevicePlatformEnv() throws Exception { + useConfiguration("--cpu=ios_arm64"); + + addMockBinAndLibs(ImmutableList.of("a.m")); + CommandAction action = linkAction("//bin:bin"); + + assertAppleSdkPlatformEnv(action, "iPhoneOS"); + } + + @Test + public void testMergeBundleActionsWithNestedBundle() throws Exception { + checkMergeBundleActionsWithNestedBundle(RULE_TYPE); + } + + @Test + public void testIncludesStoryboardOutputZipsAsMergeZips() throws Exception { + checkIncludesStoryboardOutputZipsAsMergeZips(RULE_TYPE); + } + + @Test + public void testCompilationActionsForDebug() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.DBG, CodeCoverageMode.NONE); + } + + @Test + public void testClangCoptsForDebugModeWithoutGlib() throws Exception { + checkClangCoptsForDebugModeWithoutGlib(RULE_TYPE); + } + + @Test + public void testCompilationActionsForOptimized() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.OPT, CodeCoverageMode.NONE); + } + + @Test + public void testCcDependency() throws Exception { + checkCcDependency(RULE_TYPE); + } + + @Test + public void testLinkActionCorrect() throws Exception { + checkLinkActionCorrect(RULE_TYPE, new ExtraLinkArgs()); + } + + @Test + public void testFrameworkDepLinkFlags() throws Exception { + checkFrameworkDepLinkFlags(RULE_TYPE, new ExtraLinkArgs()); + } + + @Test + public void testLinkActionsWithEmbeddedBitcode() throws Exception { + useConfiguration("--xcode_version=7.1", "--apple_bitcode=embedded", "--ios_multi_cpus=arm64"); + createBinaryTargetWriter("//objc:bin").setAndCreateFiles("srcs", "a.m").write(); + + CommandAction linkAction = linkAction("//objc:bin"); + String commandLine = Joiner.on(" ").join(linkAction.getArguments()); + + assertThat(commandLine).contains("-fembed-bitcode"); + assertThat(commandLine).contains("-Xlinker -bitcode_verify"); + assertThat(commandLine).contains("-Xlinker -bitcode_hide_symbols"); + } + + @Test + public void testLinkActionsWithEmbeddedBitcodeMarkers() throws Exception { + useConfiguration( + "--xcode_version=7.1", "--apple_bitcode=embedded_markers", "--ios_multi_cpus=arm64"); + createBinaryTargetWriter("//objc:bin").setAndCreateFiles("srcs", "a.m").write(); + + CommandAction linkAction = linkAction("//objc:bin"); + + assertThat(Joiner.on(" ").join(linkAction.getArguments())).contains("-fembed-bitcode-marker"); + } + + @Test + public void testCompilationActionsForDebugInGcovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.DBG, + CodeCoverageMode.GCOV); + } + + @Test + public void testCompilationActionsForDebugInLlvmCovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.DBG, + CodeCoverageMode.LLVMCOV); + } + + @Test + public void testCompilationActionsForOptimizedInGcovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.OPT, + CodeCoverageMode.GCOV); + } + + @Test + public void testCompilationActionsForOptimizedInLlvmCovCoverage() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.OPT, + CodeCoverageMode.LLVMCOV); + } + + @Test + public void testXcodeVersionEnv() throws Exception { + useConfiguration("--xcode_version=5.8"); + + addMockBinAndLibs(ImmutableList.of("a.m")); + CommandAction action = linkAction("//bin:bin"); + + assertXcodeVersionEnv(action, "5.8"); + } + + @Test + public void testCreate_debugSymbolActionWithAppleFlag() throws Exception { + useConfiguration("--apple_generate_dsym"); + RULE_TYPE.scratchTarget(scratch, "srcs", "['a.m']"); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + + Artifact artifact = getBinArtifact("x.app.dSYM.temp.zip", target); + String execPath = artifact.getExecPath().getParentDirectory().toString(); + CommandAction linkAction = (CommandAction) getGeneratingAction(artifact); + assertThat(linkAction.getArguments()).containsAllOf( + "DSYM_HINT_LINKED_BINARY=" + execPath + "/x_bin", + "DSYM_HINT_DSYM_PATH=" + execPath + "/x.app.dSYM.temp", + "DSYM_HINT_DSYM_BUNDLE_ZIP=" + artifact.getExecPathString()); + + Artifact plistArtifact = getBinArtifact("x.app.dSYM/Contents/Info.plist", target); + Artifact debugSymbolArtifact = + getBinArtifact("x.app.dSYM/Contents/Resources/DWARF/x_bin", target); + SpawnAction plistAction = (SpawnAction) getGeneratingAction(plistArtifact); + SpawnAction debugSymbolAction = (SpawnAction) getGeneratingAction(debugSymbolArtifact); + assertThat(debugSymbolAction).isEqualTo(plistAction); + + String dsymUnzipActionArg = + "unzip -p " + + execPath + + "/x.app.dSYM.temp.zip" + + " Contents/Info.plist > " + + plistArtifact.getExecPathString() + + " && unzip -p " + + execPath + + "/x.app.dSYM.temp.zip" + + " Contents/Resources/DWARF/x_bin > " + + debugSymbolArtifact.getExecPathString(); + assertThat(plistAction.getArguments()).contains(dsymUnzipActionArg); + } + + @Test + public void testTargetHasDebugSymbols() throws Exception { + checkTargetHasDebugSymbols(RULE_TYPE); + } + + @Test + public void testFilesToCompileOutputGroup() throws Exception { + checkFilesToCompileOutputGroup(RULE_TYPE); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcBundleLibraryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcBundleLibraryTest.java new file mode 100644 index 0000000000..8f7e2862db --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcBundleLibraryTest.java @@ -0,0 +1,197 @@ +// 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 static com.google.devtools.build.lib.rules.objc.ObjcProvider.NESTED_BUNDLE; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.testutil.Scratch; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos; +import com.google.devtools.build.xcode.plmerge.proto.PlMergeProtos; +import java.io.IOException; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for objc_bundle_library. */ +@RunWith(JUnit4.class) +public class ObjcBundleLibraryTest extends ObjcRuleTestCase { + protected static final RuleType RULE_TYPE = + new RuleType("objc_bundle_library") { + @Override + Iterable<String> requiredAttributes( + Scratch scratch, String packageDir, Set<String> alreadyAdded) { + return ImmutableList.of(); + } + }; + + private void addBundleWithResource() throws IOException { + scratch.file("bndl/foo.data"); + scratch.file("bndl/BUILD", + "objc_bundle_library(", + " name = 'bndl',", + " resources = ['foo.data'],", + ")"); + } + + @Test + public void testDoesNotGenerateLinkActionWhenThereAreNoSources() throws Exception { + addBundleWithResource(); + assertThat(linkAction("//bndl:bndl")).isNull(); + ObjcProvider objcProvider = providerForTarget("//bndl:bndl"); + Bundling providedBundle = + Iterables.getOnlyElement(objcProvider.get(NESTED_BUNDLE)); + assertThat(providedBundle.getCombinedArchitectureBinary()).isAbsent(); + } + + @Test + public void testCreate_actoolAction() throws Exception { + addTargetWithAssetCatalogs(RULE_TYPE); + checkActoolActionCorrectness(DEFAULT_IOS_SDK_VERSION); + } + + @Test + public void testProvidesBundling() throws Exception { + addBundleWithResource(); + scratch.file( + "bin/BUILD", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + " bundles = ['//bndl:bndl'],", + ")"); + BundleMergeProtos.Control mergeControl = bundleMergeControl("//bin:bin"); + BundleMergeProtos.Control nestedControl = + Iterables.getOnlyElement(mergeControl.getNestedBundleList()); + BundleMergeProtos.BundleFile bundleFile = + BundleMergeProtos.BundleFile.newBuilder() + .setBundlePath("foo.data") + .setSourceFile(getSourceArtifact("bndl/foo.data").getExecPathString()) + .setExternalFileAttribute(BundleableFile.DEFAULT_EXTERNAL_FILE_ATTRIBUTE) + .build(); + + // Should put resource in .bundle directory, not bundle root (.app dir) + assertThat(nestedControl.getBundleFileList()).containsExactly(bundleFile); + assertThat(mergeControl.getBundleFileList()).doesNotContain(bundleFile); + } + + @Test + public void testDoesNotMergeInfoplistOfNestedBundle() throws Exception { + scratch.file("bndl1/bndl1-Info.plist"); + scratch.file("bndl1/BUILD", + "objc_bundle_library(", + " name = 'bndl1',", + " infoplist = 'bndl1-Info.plist',", + ")"); + scratch.file("bndl2/bndl2-Info.plist"); + scratch.file("bndl2/BUILD", + "objc_bundle_library(", + " name = 'bndl2',", + " bundles = ['//bndl1:bndl1'],", + " infoplist = 'bndl2-Info.plist',", + ")"); + scratch.file("bin/bin-Info.plist"); + scratch.file("bin/bin.m"); + scratch.file("bin/BUILD", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + " bundles = ['//bndl2:bndl2'],", + " infoplist = 'bin-Info.plist'", + ")"); + Artifact bundle1Infoplist = getSourceArtifact("bndl1/bndl1-Info.plist"); + Artifact bundle2Infoplist = getSourceArtifact("bndl2/bndl2-Info.plist"); + Artifact binaryInfoplist = getSourceArtifact("bin/bin-Info.plist"); + Artifact binaryMergedInfoplist = getMergedInfoPlist(getConfiguredTarget("//bin:bin")); + + PlMergeProtos.Control binaryPlMergeControl = plMergeControl("//bin:bin"); + + assertThat(binaryPlMergeControl.getSourceFileList()) + .contains(binaryInfoplist.getExecPathString()); + assertThat(binaryPlMergeControl.getSourceFileList()) + .containsNoneOf(bundle1Infoplist.getExecPathString(), bundle2Infoplist.getExecPathString()); + + assertThat(bundleMergeAction("//bin:bin").getInputs()) + .containsAllOf(bundle1Infoplist, bundle2Infoplist, binaryMergedInfoplist); + + BundleMergeProtos.Control binControl = bundleMergeControl("//bin:bin"); + assertThat(binControl.getBundleInfoPlistFile()) + .isEqualTo(binaryMergedInfoplist.getExecPathString()); + + BundleMergeProtos.Control bundle2Control = + Iterables.getOnlyElement(binControl.getNestedBundleList()); + assertThat(bundle2Control.getBundleInfoPlistFile()) + .isEqualTo(bundle2Infoplist.getExecPathString()); + + BundleMergeProtos.Control bundle1Control = + Iterables.getOnlyElement(bundle2Control.getNestedBundleList()); + assertThat(bundle1Control.getBundleInfoPlistFile()) + .isEqualTo(bundle1Infoplist.getExecPathString()); + } + + @Test + public void testRegistersStoryboardCompilationActions() throws Exception { + checkRegistersStoryboardCompileActions(RULE_TYPE, "iphone"); + } + + @Test + public void testCompileXibActions() throws Exception { + checkCompileXibActions(RULE_TYPE); + } + + @Test + public void testTwoStringsOneBundlePath() throws Exception { + checkTwoStringsOneBundlePath(RULE_TYPE); + } + + @Test + public void testTwoResourcesOneBundlePath() throws Exception { + checkTwoResourcesOneBundlePath(RULE_TYPE); + } + + @Test + public void testSameStringTwice() throws Exception { + checkSameStringsTwice(RULE_TYPE); + } + + @Test + public void testPassesFamiliesToIbtool() throws Exception { + checkPassesFamiliesToIbtool(RULE_TYPE); + } + + @Test + public void testMultipleInfoPlists() throws Exception { + checkMultipleInfoPlists(RULE_TYPE); + } + + @Test + public void testInfoplistAndInfoplistsTogether() throws Exception { + checkInfoplistAndInfoplistsTogether(RULE_TYPE); + } + + @Test + // Regression test for b/34770913. + public void testDeviceAndSimulatorBuilds() throws Exception { + addBundleWithResource(); + useConfiguration("--ios_multi_cpus=i386,x86_64,arm64,armv7"); + + // Verifies this rule does not raise a rule error based on bundling platform flags. + providerForTarget("//bndl:bndl"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcBundleTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcBundleTest.java new file mode 100644 index 0000000000..cf23de0082 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcBundleTest.java @@ -0,0 +1,77 @@ +// 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 static com.google.devtools.build.lib.rules.objc.ObjcCommon.BUNDLE_CONTAINER_TYPE; +import static com.google.devtools.build.lib.rules.objc.ObjcCommon.NOT_IN_CONTAINER_ERROR_FORMAT; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.BUNDLE_FILE; + +import com.google.common.collect.ImmutableList; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for objc_bundle. */ +@RunWith(JUnit4.class) +public class ObjcBundleTest extends ObjcRuleTestCase { + @Test + public void testErrorForImportArtifactNotInDotBundleDir() throws Exception { + scratch.file("x/foo/notinbundledir"); + scratch.file("x/bar/x.bundle/isinbundledir"); + checkError("x", "x", + String.format(NOT_IN_CONTAINER_ERROR_FORMAT, + "x/foo/notinbundledir", + ImmutableList.of(BUNDLE_CONTAINER_TYPE)), + "objc_bundle(", + " name = 'x',", + " bundle_imports = ['bar/x.bundle/isinbundledir', 'foo/notinbundledir'],", + ")"); + } + + @Test + public void testBundleFilesProvided() throws Exception { + scratch.file("bundle/bar/x.bundle/1"); + scratch.file("bundle/bar/x.bundle/subdir/2"); + scratch.file("bundle/bar/y.bundle/subdir/1"); + scratch.file("bundle/bar/y.bundle/2"); + scratch.file("bundle/BUILD", + "objc_bundle(", + " name = 'bundle',", + " bundle_imports = glob(['bar/**']),", + ")"); + ObjcProvider provider = providerForTarget("//bundle:bundle"); + assertThat(provider.get(BUNDLE_FILE)).containsExactly( + new BundleableFile(getSourceArtifact("bundle/bar/x.bundle/1"), "x.bundle/1"), + new BundleableFile(getSourceArtifact("bundle/bar/x.bundle/subdir/2"), "x.bundle/subdir/2"), + new BundleableFile(getSourceArtifact("bundle/bar/y.bundle/subdir/1"), "y.bundle/subdir/1"), + new BundleableFile(getSourceArtifact("bundle/bar/y.bundle/2"), "y.bundle/2")); + } + + @Test + public void testBundleImportsUsesOuterMostDotBundleDirAsRoot() throws Exception { + scratch.file("bundle/bar/x.bundle/foo/y.bundle/baz"); + scratch.file("bundle/BUILD", + "objc_bundle(", + " name = 'bundle',", + " bundle_imports = glob(['bar/**']),", + ")"); + ObjcProvider provider = providerForTarget("//bundle:bundle"); + assertThat(provider.get(BUNDLE_FILE)) + .containsExactly(new BundleableFile( + getSourceArtifact("bundle/bar/x.bundle/foo/y.bundle/baz"), "x.bundle/foo/y.bundle/baz")) + .inOrder(); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcFrameworkTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcFrameworkTest.java new file mode 100644 index 0000000000..e4a9dded52 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcFrameworkTest.java @@ -0,0 +1,319 @@ +// 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 static com.google.devtools.build.lib.rules.objc.ObjcCommon.FRAMEWORK_CONTAINER_TYPE; +import static com.google.devtools.build.lib.rules.objc.ObjcCommon.NOT_IN_CONTAINER_ERROR_FORMAT; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.SDK_DYLIB; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.SDK_FRAMEWORK; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.STATIC_FRAMEWORK_DIR; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.WEAK_SDK_FRAMEWORK; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.BundleFile; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for objc_framework. */ +@RunWith(JUnit4.class) +public class ObjcFrameworkTest extends ObjcRuleTestCase { + @Test + public void testErrorForImportArtifactNotInDotFrameworkDir() throws Exception { + scratch.file("x/foo/notinframeworkdir"); + scratch.file("x/bar/x.framework/isinframeworkdir"); + checkError("x", "x", + String.format(NOT_IN_CONTAINER_ERROR_FORMAT, + "x/foo/notinframeworkdir", + ImmutableList.of(FRAMEWORK_CONTAINER_TYPE)), + "objc_framework(", + " name = 'x',", + " framework_imports = ['bar/x.framework/isinframeworkdir', 'foo/notinframeworkdir'],", + ")"); + } + + @Test + public void testProvidesFilesAndDirs_static() throws Exception { + addBinWithTransitiveDepOnFrameworkImport(); + ObjcProvider provider = providerForTarget("//fx:fx"); + assertThat(provider.get(STATIC_FRAMEWORK_DIR)) + .containsExactly( + PathFragment.create("fx/fx1.framework"), + PathFragment.create("fx/fx2.framework")); + assertThat(provider.get(ObjcProvider.STATIC_FRAMEWORK_FILE)) + .containsExactly( + getSourceArtifact("fx/fx1.framework/a"), + getSourceArtifact("fx/fx1.framework/b"), + getSourceArtifact("fx/fx2.framework/c"), + getSourceArtifact("fx/fx2.framework/d")); + assertThat(provider.get(ObjcProvider.DYNAMIC_FRAMEWORK_DIR)).isEmpty(); + assertThat(provider.get(ObjcProvider.DYNAMIC_FRAMEWORK_FILE)).isEmpty(); + } + + @Test + public void testProvidesFilesAndDirs_dynamic() throws Exception { + scratch.file("fx/fx1.framework/a"); + scratch.file("fx/fx1.framework/b"); + scratch.file("fx/fx2.framework/c"); + scratch.file("fx/fx2.framework/d"); + scratch.file("fx/BUILD", + "objc_framework(", + " name = 'fx',", + " framework_imports = glob(['fx1.framework/*', 'fx2.framework/*']),", + " is_dynamic = 1,", + ")"); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("deps", "//fx:fx") + .write(); + + ObjcProvider provider = providerForTarget("//fx:fx"); + assertThat(provider.get(ObjcProvider.DYNAMIC_FRAMEWORK_DIR)) + .containsExactly( + PathFragment.create("fx/fx1.framework"), + PathFragment.create("fx/fx2.framework")); + assertThat(provider.get(ObjcProvider.DYNAMIC_FRAMEWORK_FILE)) + .containsExactly( + getSourceArtifact("fx/fx1.framework/a"), + getSourceArtifact("fx/fx1.framework/b"), + getSourceArtifact("fx/fx2.framework/c"), + getSourceArtifact("fx/fx2.framework/d")); + assertThat(provider.get(ObjcProvider.STATIC_FRAMEWORK_DIR)).isEmpty(); + assertThat(provider.get(ObjcProvider.STATIC_FRAMEWORK_FILE)).isEmpty(); + } + + @Test + public void testSdkFrameworks_objcProvider() throws Exception { + ConfiguredTarget configuredTarget = addLibWithDepOnFrameworkImport(); + ObjcProvider provider = providerForTarget(configuredTarget.getLabel().toString()); + + Set<SdkFramework> sdkFrameworks = ImmutableSet.of(new SdkFramework("CoreLocation")); + + assertThat(provider.get(SDK_FRAMEWORK)).containsExactlyElementsIn(sdkFrameworks); + } + + @Test + public void testWeakSdkFrameworks_objcProvider() throws Exception { + ConfiguredTarget configuredTarget = addLibWithDepOnFrameworkImport(); + ObjcProvider provider = providerForTarget(configuredTarget.getLabel().toString()); + + assertThat(provider.get(WEAK_SDK_FRAMEWORK)) + .containsExactly(new SdkFramework("MediaAccessibility")); + } + + @Test + public void testDylibs_objcProvider() throws Exception { + ConfiguredTarget configuredTarget = addLibWithDepOnFrameworkImport(); + ObjcProvider provider = providerForTarget(configuredTarget.getLabel().toString()); + + assertThat(provider.get(SDK_DYLIB)).containsExactly("libdy1"); + } + + @Test + public void testRequiresNonEmptyFrameworkImports() + throws Exception { + scratch.file("x/dir/x.framework/isinframeworkdir"); + checkError("x", "empty_with_configuration", + getErrorMsgNonEmptyList( + "framework_imports", "objc_framework", "//x:empty_with_configuration"), + "objc_framework(", + " name = 'empty_with_configuration',", + " framework_imports = [],", + ")"); + } + + // This also serves as a regression test for non-empty attributes with configurable values. Please + // don't delete this. + @Test + public void testRequiresNonEmptyFrameworkImports_Configurable_EmptyWithConfiguration() + throws Exception { + scratch.file("x/dir/x.framework/isinframeworkdir"); + useConfiguration("--test_arg=a"); + checkError("x", "empty_with_configuration", + getErrorMsgNonEmptyList( + "framework_imports", "objc_framework", "//x:empty_with_configuration"), + "config_setting(", + " name = 'a',", + " values = {'test_arg': 'a'},", + ")", + "objc_framework(", + " name = 'empty_with_configuration',", + " framework_imports = select({", + " ':a': [],", + " '//conditions:default': ['dir/x.framework/isinframeworkdir']", + " })", + ")"); + } + + // This also serves as a regression test for non-empty attributes with configurable values. Please + // don't delete this. + @Test + public void testRequiresNonEmptyFrameworkImports_Configurable_NonEmptyWithConfiguration() + throws Exception { + scratch.file("x/dir/x.framework/isinframeworkdir"); + useConfiguration("--test_arg=a"); + scratchConfiguredTarget("x", "empty_with_configuration", + "config_setting(", + " name = 'a',", + " values = {'test_arg': 'a'},", + ")", + "objc_framework(", + " name = 'empty_with_configuration',", + " framework_imports = select({", + " ':a': ['dir/x.framework/isinframeworkdir'],", + " '//conditions:default': []", + " })", + ")"); + } + + // This also serves as a regression test for non-empty attributes with configurable values. Please + // don't delete this. + @Test + public void testRequiresNonEmptyFrameworkImports_Configurable_NonEmptyWithDefault() + throws Exception { + scratch.file("x/dir/x.framework/isinframeworkdir"); + scratchConfiguredTarget("x", "empty_with_configuration", + "config_setting(", + " name = 'a',", + " values = {'test_arg': 'a'},", + ")", + "objc_framework(", + " name = 'empty_with_configuration',", + " framework_imports = select({", + " ':a': [],", + " '//conditions:default': ['dir/x.framework/isinframeworkdir']", + " })", + ")"); + } + + // This also serves as a regression test for non-empty attributes with configurable values. Please + // don't delete this. + @Test + public void testRequiresNonEmptyFrameworkImports_Configurable_EmptyWithDefault() + throws Exception { + scratch.file("x/dir/x.framework/isinframeworkdir"); + checkError("x", "empty_with_configuration", + getErrorMsgNonEmptyList( + "framework_imports", "objc_framework", "//x:empty_with_configuration"), + "config_setting(", + " name = 'a',", + " values = {'test_arg': 'a'},", + ")", + "objc_framework(", + " name = 'empty_with_configuration',", + " framework_imports = select({", + " ':a': ['dir/x.framework/isinframeworkdir'],", + " '//conditions:default': []", + " })", + ")"); + } + + @Test + public void testDynamicFrameworkInFinalBundle() throws Exception { + scratch.file("x/Foo.framework/Foo"); + scratch.file("x/Foo.framework/Info.plist"); + scratch.file("x/Foo.framework/Headers/Foo.h"); + scratch.file("x/Foo.framework/Resources/bar.png"); + scratch.file( + "x/BUILD", + "objc_framework(", + " name = 'foo_framework',", + " framework_imports = glob(['Foo.framework/**']),", + " is_dynamic = 1,", + ")", + "", + "objc_binary(", + " name = 'bin',", + " srcs = [ 'a.m' ],", + " deps = [ ':foo_framework' ],", + ")", + "", + "ios_application(", + " name = 'x',", + " binary = ':bin',", + ")"); + + BundleMergeProtos.Control mergeControl = bundleMergeControl("//x:x"); + + assertThat(mergeControl.getBundleFileList()) + .containsAllOf( + BundleFile.newBuilder() + .setBundlePath("Frameworks/Foo.framework/Foo") + .setSourceFile(getSourceArtifact("x/Foo.framework/Foo").getExecPathString()) + .setExternalFileAttribute(BundleableFile.EXECUTABLE_EXTERNAL_FILE_ATTRIBUTE) + .build(), + BundleFile.newBuilder() + .setBundlePath("Frameworks/Foo.framework/Info.plist") + .setSourceFile(getSourceArtifact("x/Foo.framework/Info.plist").getExecPathString()) + .setExternalFileAttribute(BundleableFile.EXECUTABLE_EXTERNAL_FILE_ATTRIBUTE) + .build(), + BundleFile.newBuilder() + .setBundlePath("Frameworks/Foo.framework/Resources/bar.png") + .setSourceFile( + getSourceArtifact("x/Foo.framework/Resources/bar.png").getExecPathString()) + .setExternalFileAttribute(BundleableFile.DEFAULT_EXTERNAL_FILE_ATTRIBUTE) + .build()); + + assertThat(mergeControl.getBundleFileList()) + .doesNotContain( + BundleFile.newBuilder() + .setBundlePath("Frameworks/Foo.framework/Headers/Foo.h") + .setSourceFile( + getSourceArtifact("x/Foo.framework/Headers/Foo.h").getExecPathString()) + .setExternalFileAttribute(BundleableFile.DEFAULT_EXTERNAL_FILE_ATTRIBUTE) + .build()); + } + + @Test + public void testDynamicFrameworkSigned() throws Exception { + useConfiguration("--ios_cpu=arm64"); + + scratch.file("x/Foo.framework/Foo"); + scratch.file("x/Foo.framework/Info.plist"); + scratch.file("x/Foo.framework/Headers/Foo.h"); + scratch.file("x/Foo.framework/Resources/bar.png"); + scratch.file( + "x/BUILD", + "objc_framework(", + " name = 'foo_framework',", + " framework_imports = glob(['Foo.framework/**']),", + " is_dynamic = 1,", + ")", + "", + "objc_binary(", + " name = 'bin',", + " srcs = [ 'a.m' ],", + " deps = [ ':foo_framework' ],", + ")", + "", + "ios_application(", + " name = 'x',", + " binary = ':bin',", + ")"); + + SpawnAction signingAction = (SpawnAction) ipaGeneratingAction(); + + assertThat(normalizeBashArgs(signingAction.getArguments())) + .containsAllOf("--sign", "${t}/Payload/x.app/Frameworks/*", "--sign", "${t}/Payload/x.app") + .inOrder(); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcImportTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcImportTest.java new file mode 100644 index 0000000000..3fea5129b8 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcImportTest.java @@ -0,0 +1,131 @@ +// 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.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.testutil.Scratch; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for objc_import. */ +@RunWith(JUnit4.class) +public class ObjcImportTest extends ObjcRuleTestCase { + protected static final RuleType RULE_TYPE = + new RuleType("objc_import") { + @Override + Iterable<String> requiredAttributes( + Scratch scratch, String packageDir, Set<String> alreadyAdded) throws IOException { + List<String> attributes = new ArrayList<>(); + if (!alreadyAdded.contains("archives")) { + scratch.file(packageDir + "/precomp_library.a"); + attributes.add("archives = ['precomp_library.a']"); + } + return attributes; + } + }; + + private void addTrivialImportLibrary() throws IOException { + scratch.file("imp/precomp_lib.a"); + scratch.file("imp/BUILD", + "objc_import(", + " name = 'imp',", + " archives = ['precomp_lib.a'],", + ")"); + } + + @Test + public void testImportLibrariesProvidedTransitively() throws Exception { + scratch.file("imp/this_library.a"); + addTrivialImportLibrary(); + scratch.file("lib/BUILD", + "objc_library(", + " name = 'lib',", + " deps = ['//imp:imp'],", + ")"); + ObjcProvider provider = providerForTarget("//lib:lib"); + assertThat(Artifact.toExecPaths(provider.get(ObjcProvider.IMPORTED_LIBRARY))) + .containsExactly("imp/precomp_lib.a").inOrder(); + } + + @Test + public void testImportLibrariesLinkedToFinalBinary() throws Exception { + addTrivialImportLibrary(); + createBinaryTargetWriter("//bin:bin").setList("deps", "//imp:imp").write(); + CommandAction linkBinAction = linkAction("//bin:bin"); + verifyObjlist(linkBinAction, "bin-linker.objlist", "imp/precomp_lib.a"); + assertThat(Artifact.toExecPaths(linkBinAction.getInputs())) + .contains("imp/precomp_lib.a"); + } + + @Test + public void testNoDepsAllowed() throws Exception { + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .write(); + checkError("imp", "imp", + "//imp:imp: no such attribute 'deps' in 'objc_import' rule", + "objc_import(", + " name = 'imp',", + " archives = ['library.a'],", + " deps = ['//lib:lib'],", + ")"); + } + + @Test + public void testArchiveRequiresDotInName() throws Exception { + checkError("x", "x", "'//x:fooa' does not produce any objc_import archives files (expected .a)", + "objc_import(", + " name = 'x',", + " archives = ['fooa'],", + ")"); + } + + @Test + public void testDylibsProvided() throws Exception { + scratch.file("imp/imp.a"); + scratch.file("imp/BUILD", + "objc_import(", + " name = 'imp',", + " archives = ['imp.a'],", + " sdk_dylibs = ['libdy1', 'libdy2'],", + ")"); + ObjcProvider provider = providerForTarget("//imp:imp"); + assertThat(provider.get(ObjcProvider.SDK_DYLIB)).containsExactly("libdy1", "libdy2").inOrder(); + } + + @Test + public void testProvidesHdrsAndIncludes() throws Exception { + checkProvidesHdrsAndIncludes(RULE_TYPE); + } + + @Test + public void testProvidesStoryboardObjects() throws Exception { + checkProvidesStoryboardObjects(RULE_TYPE); + } + + @Test + public void testNestedBundleInformationPropagatedToDependers() throws Exception { + checkNestedBundleInformationPropagatedToDependers(RULE_TYPE); + } + +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcLibraryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcLibraryTest.java new file mode 100644 index 0000000000..40a2c83edc --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcLibraryTest.java @@ -0,0 +1,1542 @@ +// 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 static com.google.common.truth.Truth.assertWithMessage; +import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.baseArtifactNames; +import static com.google.devtools.build.lib.rules.objc.CompilationSupport.ABSOLUTE_INCLUDES_PATH_FORMAT; +import static com.google.devtools.build.lib.rules.objc.CompilationSupport.FILE_IN_SRCS_AND_HDRS_WARNING_FORMAT; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.ASSET_CATALOG; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.BUNDLE_FILE; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.CC_LIBRARY; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.HEADER; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.LIBRARY; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.SDK_DYLIB; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.WEAK_SDK_FRAMEWORK; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.XCASSETS_DIR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.NON_ARC_SRCS_TYPE; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.SRCS_TYPE; +import static org.junit.Assert.fail; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.ActionExecutionException; +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.RunfilesProvider; +import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction; +import com.google.devtools.build.lib.analysis.config.CompilationMode; +import com.google.devtools.build.lib.analysis.util.ScratchAttributeWriter; +import com.google.devtools.build.lib.packages.NoSuchTargetException; +import com.google.devtools.build.lib.packages.util.MockObjcSupport; +import com.google.devtools.build.lib.rules.apple.AppleToolchain; +import com.google.devtools.build.lib.rules.apple.Platform; +import com.google.devtools.build.lib.rules.cpp.CppCompileAction; +import com.google.devtools.build.lib.rules.cpp.CppModuleMapAction; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.common.options.OptionsParsingException; +import java.util.Collections; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for objc_library. */ +@RunWith(JUnit4.class) +public class ObjcLibraryTest extends ObjcRuleTestCase { + + static final RuleType RULE_TYPE = new OnlyNeedsSourcesRuleType("objc_library"); + private static final String WRAPPED_CLANG = "wrapped_clang"; + + /** + * Middleman artifact arising from //tools/osx/crosstool:link, containing tools that should be + * inputs to link actions. + */ + private static final String CROSSTOOL_LINK_MIDDLEMAN = "tools_Sosx_Scrosstool_Clink"; + + /** Creates an {@code objc_library} target writer. */ + @Override + protected ScratchAttributeWriter createLibraryTargetWriter(String labelString) { + return ScratchAttributeWriter.fromLabelString(this, "objc_library", labelString); + } + + @Test + public void testFilesToBuild() throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + ConfiguredTarget target = + createLibraryTargetWriter("//objc:One") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .write(); + + Iterable<Artifact> files = getFilesToBuild(target); + assertThat(Artifact.toRootRelativePaths(files)).containsExactly("objc/libOne.a"); + } + + @Test + public void testCompilesSources() throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + createLibraryTargetWriter("//objc/lib1") + .setAndCreateFiles("srcs", "a.m") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + + createLibraryTargetWriter("//objc/lib2") + .setAndCreateFiles("srcs", "a.m") + .setAndCreateFiles("hdrs", "hdr.h") + .setList("deps", "//objc/lib1") + .write(); + + createLibraryTargetWriter("//objc:x") + .setAndCreateFiles("srcs", "a.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .setList("deps", "//objc/lib2:lib2") + .write(); + + CppCompileAction compileA = (CppCompileAction) compileAction("//objc:x", "a.o"); + + assertThat(Artifact.toRootRelativePaths(compileA.getPossibleInputsForTesting())) + .containsAllOf("objc/a.m", "objc/hdr.h", "objc/private.h"); + assertThat(Artifact.toRootRelativePaths(compileA.getOutputs())) + .containsExactly("objc/_objs/x/objc/a.o", "objc/_objs/x/objc/a.d"); + } + + @Test + public void testObjcPlusPlusCompile() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--cpu=ios_i386", + "--ios_cpu=i386", + "--ios_minimum_os=9.10.11"); + createLibraryTargetWriter("//objc:lib") + .setList("srcs", "a.mm") + .write(); + CommandAction compileAction = compileAction("//objc:lib", "a.o"); + assertThat(compileAction.getArguments()) + .containsAllOf("-stdlib=libc++", "-std=gnu++11", "-mios-simulator-version-min=9.10.11"); + } + + @Test + public void testObjcPlusPlusCompileDarwin() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--cpu=darwin_x86_64", + "--macos_minimum_os=9.10.11", + // TODO(b/36126423): Darwin should imply macos, so the + // following line should not be necessary. + "--apple_platform_type=macos", + "--experimental_objc_crosstool=all"); + createLibraryTargetWriter("//objc:lib") + .setList("srcs", "a.mm") + .write(); + CommandAction compileAction = compileAction("//objc:lib", "a.o"); + assertThat(compileAction.getArguments()) + .containsAllOf("-stdlib=libc++", "-std=gnu++11", "-mmacosx-version-min=9.10.11"); + } + + @Test + public void testCompilationModeDbg() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--cpu=ios_i386", + "--ios_cpu=i386", + "--compilation_mode=dbg"); + scratch.file("objc/a.m"); + scratch.file( + "objc/BUILD", + RULE_TYPE.target( + scratch, + "objc", + "lib", + "srcs", + "['a.m']")); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + + assertThat(compileActionA.getArguments()).contains("--DBG_ONLY_FLAG"); + assertThat(compileActionA.getArguments()).doesNotContain("--FASTBUILD_ONLY_FLAG"); + assertThat(compileActionA.getArguments()).doesNotContain("--OPT_ONLY_FLAG"); + } + + @Test + public void testCompilationModeFastbuild() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--cpu=ios_i386", + "--ios_cpu=i386", + "--compilation_mode=fastbuild"); + scratch.file("objc/a.m"); + scratch.file( + "objc/BUILD", + RULE_TYPE.target( + scratch, + "objc", + "lib", + "srcs", + "['a.m']")); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + + assertThat(compileActionA.getArguments()).doesNotContain("--DBG_ONLY_FLAG"); + assertThat(compileActionA.getArguments()).contains("--FASTBUILD_ONLY_FLAG"); + assertThat(compileActionA.getArguments()).doesNotContain("--OPT_ONLY_FLAG"); + } + + @Test + public void testCompilationModeOpt() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--cpu=ios_i386", + "--ios_cpu=i386", + "--compilation_mode=opt"); + scratch.file("objc/a.m"); + scratch.file( + "objc/BUILD", + RULE_TYPE.target( + scratch, + "objc", + "lib", + "srcs", + "['a.m']")); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + + assertThat(compileActionA.getArguments()).doesNotContain("--DBG_ONLY_FLAG"); + assertThat(compileActionA.getArguments()).doesNotContain("--FASTBUILD_ONLY_FLAG"); + assertThat(compileActionA.getArguments()).contains("--OPT_ONLY_FLAG"); + } + + @Test + public void testCreate_runfilesWithSourcesOnly() throws Exception { + ConfiguredTarget target = + createLibraryTargetWriter("//objc:One") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .write(); + RunfilesProvider provider = target.getProvider(RunfilesProvider.class); + assertThat(baseArtifactNames(provider.getDefaultRunfiles().getArtifacts())).isEmpty(); + assertThat(Artifact.toRootRelativePaths(provider.getDataRunfiles().getArtifacts())) + .containsExactly("objc/libOne.a"); + } + + @Test + public void testCreate_noErrorForEmptySourcesButHasDependency() throws Exception { + createLibraryTargetWriter("//baselib:baselib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .write(); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("hdrs", "a.h") + .setList("deps", "//baselib:baselib") + .write(); + ObjcProvider provider = providerForTarget("//lib:lib"); + assertThat(provider.get(LIBRARY)) + .containsExactlyElementsIn(archiveAction("//baselib:baselib").getOutputs()); + } + + @Test + public void testCreate_errorForEmptyFilegroupSources() throws Exception { + checkError( + "x", + "x", + "does not produce any objc_library srcs files (expected " + SRCS_TYPE + ")", + "filegroup(name = 'fg', srcs = [])", + "objc_library(name = 'x', srcs = ['fg'])"); + } + + @Test + public void testCreate_srcsContainingHeaders() throws Exception { + scratch.file("x/a.m", "dummy source file"); + scratch.file("x/a.h", "dummy header file"); + scratch.file("x/BUILD", "objc_library(name = 'Target', srcs = ['a.m', 'a.h'])"); + assertThat(view.hasErrors(getConfiguredTarget("//x:Target"))).isFalse(); + } + + @Test + public void testCreate_warningForOverlappingSrcsAndHdrs() throws Exception { + scratch.file("/x/a.h", "dummy header file"); + checkWarning( + "x", + "x", + String.format(FILE_IN_SRCS_AND_HDRS_WARNING_FORMAT, "x/a.h"), + "objc_library(name = 'x', srcs = ['a.h'], hdrs = ['a.h'])"); + } + + @Test + public void testCreate_headerAndCompiledSourceWithSameName() throws Exception { + scratch.file("objc/BUILD", "objc_library(name = 'Target', srcs = ['a.m'], hdrs = ['a.h'])"); + assertThat(view.hasErrors(getConfiguredTarget("//objc:Target"))).isFalse(); + } + + @Test + public void testCreate_errorForCcInNonArcSources() throws Exception { + scratch.file("x/cc.cc"); + checkError( + "x", + "x", + "'//x:cc.cc' does not produce any objc_library non_arc_srcs files (expected " + + NON_ARC_SRCS_TYPE + + ")", + "objc_library(name = 'x', non_arc_srcs = ['cc.cc'])"); + } + + @Test + public void testFileInSrcsAndNonArcSources() throws Exception { + checkError( + "x", + "x", + String.format(CompilationSupport.FILE_IN_SRCS_AND_NON_ARC_SRCS_ERROR_FORMAT, "x/foo.m"), + "objc_library(name = 'x', srcs = ['foo.m'], non_arc_srcs = ['foo.m'])"); + } + + @Test + public void testCreate_headerContainingDotMAndDotCFiles() throws Exception { + scratch.file("x/a.m", "dummy source file"); + scratch.file("x/a.h", "dummy header file"); + scratch.file("x/b.m", "dummy source file"); + scratch.file("x/a.c", "dummy source file"); + scratch.file( + "x/BUILD", "objc_library(name = 'Target', srcs = ['a.m'], hdrs = ['a.h', 'b.m', 'a.c'])"); + assertThat(view.hasErrors(getConfiguredTarget("//x:Target"))).isFalse(); + } + + @Test + public void testProvidesObjcHeadersWithDotMFiles() throws Exception { + ConfiguredTarget target = + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "a.h", "b.h", "f.m") + .write(); + ConfiguredTarget depender = + createLibraryTargetWriter("//objc2:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "d.h", "e.m") + .setList("deps", "//objc:lib") + .write(); + assertThat(Artifact.toRootRelativePaths(target.getProvider(ObjcProvider.class).get(HEADER))) + .containsExactly("objc/a.h", "objc/b.h", "objc/f.m"); + assertThat(Artifact.toRootRelativePaths(depender.getProvider(ObjcProvider.class).get(HEADER))) + .containsExactly("objc/a.h", "objc/b.h", "objc/f.m", "objc2/d.h", "objc2/e.m"); + } + + @Test + public void testNonPropagatedDepsProvider() throws Exception { + ConfiguredTarget target = + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "a.h", "b.h") + .write(); + createLibraryTargetWriter("//objc2:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h", "d.h") + .setList("non_propagated_deps", "//objc:lib") + .write(); + ConfiguredTarget transitiveDepender = + createLibraryTargetWriter("//objc3:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "e.h", "f.h") + .setList("non_propagated_deps", "//objc2:lib") + .write(); + + assertThat(Artifact.toRootRelativePaths(target.getProvider(ObjcProvider.class).get(HEADER))) + .containsExactly("objc/a.h", "objc/b.h"); + assertThat( + Artifact.toRootRelativePaths( + transitiveDepender.getProvider(ObjcProvider.class).get(HEADER))) + .containsExactly("objc2/c.h", "objc2/d.h", "objc3/e.h", "objc3/f.h"); + } + + @Test + public void testMultiPlatformLibrary() throws Exception { + useConfiguration("--ios_multi_cpus=i386,x86_64,armv7,arm64", "--ios_cpu=armv7"); + + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "a.h") + .write(); + + assertThat(view.hasErrors(getConfiguredTarget("//objc:lib"))).isFalse(); + } + + @Test + public void testCompilationActions_simulator() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--cpu=ios_i386", + "--ios_cpu=i386"); + + scratch.file("objc/a.m"); + scratch.file("objc/non_arc.m"); + scratch.file("objc/private.h"); + scratch.file("objc/c.h"); + scratch.file( + "objc/BUILD", + RULE_TYPE.target( + scratch, + "objc", + "lib", + "srcs", + "['a.m', 'private.h']", + "hdrs", + "['c.h']", + "non_arc_srcs", + "['non_arc.m']")); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + CommandAction compileActionNonArc = compileAction("//objc:lib", "non_arc.o"); + + assertRequiresDarwin(compileActionA); + assertThat(compileActionA.getArguments()) + .contains("tools/osx/crosstool/iossim/" + WRAPPED_CLANG); + assertThat(compileActionA.getArguments()) + .containsAllOf("-isysroot", AppleToolchain.sdkDir()).inOrder(); + assertThat(Collections.frequency(compileActionA.getArguments(), + "-F" + AppleToolchain.sdkDir() + "/Developer/Library/Frameworks")).isEqualTo(1); + assertThat(Collections.frequency(compileActionA.getArguments(), + "-F" + frameworkDir(Platform.IOS_SIMULATOR))).isEqualTo(1); + assertThat(compileActionA.getArguments()) + .containsAllIn(AppleToolchain.DEFAULT_WARNINGS.values()); + assertThat(compileActionA.getArguments()) + .containsAllIn(CompilationSupport.DEFAULT_COMPILER_FLAGS); + assertThat(compileActionA.getArguments()) + .containsAllIn(CompilationSupport.SIMULATOR_COMPILE_FLAGS); + assertThat(compileActionA.getArguments()).contains("-fobjc-arc"); + assertThat(compileActionA.getArguments()).containsAllOf("-c", "objc/a.m"); + assertThat(compileActionNonArc.getArguments()).contains("-fno-objc-arc"); + assertThat(compileActionA.getArguments()).containsAllIn(FASTBUILD_COPTS); + assertThat(compileActionA.getArguments()) + .contains("-mios-simulator-version-min=" + DEFAULT_IOS_SDK_VERSION); + assertThat(compileActionA.getArguments()).contains("-arch i386"); + } + + @Test + public void testCompilationActions_device() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--cpu=ios_armv7", + "--ios_cpu=armv7"); + + scratch.file("objc/a.m"); + scratch.file("objc/non_arc.m"); + scratch.file("objc/private.h"); + scratch.file("objc/c.h"); + scratch.file( + "objc/BUILD", + RULE_TYPE.target( + scratch, + "objc", + "lib", + "srcs", + "['a.m', 'private.h']", + "hdrs", + "['c.h']", + "non_arc_srcs", + "['non_arc.m']")); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + CommandAction compileActionNonArc = compileAction("//objc:lib", "non_arc.o"); + + assertRequiresDarwin(compileActionA); + assertThat(compileActionA.getArguments()).contains("tools/osx/crosstool/ios/" + WRAPPED_CLANG); + assertThat(compileActionA.getArguments()) + .containsAllOf("-isysroot", AppleToolchain.sdkDir()).inOrder(); + assertThat(Collections.frequency(compileActionA.getArguments(), + "-F" + AppleToolchain.sdkDir() + "/Developer/Library/Frameworks")).isEqualTo(1); + assertThat(Collections.frequency(compileActionA.getArguments(), + "-F" + frameworkDir(Platform.IOS_DEVICE))).isEqualTo(1); + assertThat(compileActionA.getArguments()) + .containsAllIn(AppleToolchain.DEFAULT_WARNINGS.values()); + assertThat(compileActionA.getArguments()) + .containsAllIn(CompilationSupport.DEFAULT_COMPILER_FLAGS); + assertThat(compileActionA.getArguments()) + .containsNoneIn(CompilationSupport.SIMULATOR_COMPILE_FLAGS); + + assertThat(compileActionA.getArguments()).contains("-fobjc-arc"); + assertThat(compileActionA.getArguments()).containsAllOf("-c", "objc/a.m"); + + assertThat(compileActionNonArc.getArguments()).contains("-fno-objc-arc"); + assertThat(compileActionA.getArguments()).containsAllIn(FASTBUILD_COPTS); + assertThat(compileActionA.getArguments()) + .contains("-miphoneos-version-min=" + DEFAULT_IOS_SDK_VERSION); + assertThat(compileActionA.getArguments()).contains("-arch armv7"); + } + + @Test + public void testArchivesPrecompiledObjectFiles() throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + scratch.file("objc/a.m"); + scratch.file("objc/b.o"); + scratch.file("objc/BUILD", RULE_TYPE.target(scratch, "objc", "x", "srcs", "['a.m', 'b.o']")); + assertThat(Artifact.toRootRelativePaths(archiveAction("//objc:x").getInputs())) + .contains("objc/b.o"); + } + + @Test + public void testCompileWithFrameworkImportsIncludesFlagsAndInputArtifacts() throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + addBinWithTransitiveDepOnFrameworkImport(); + CommandAction compileAction = compileAction("//lib:lib", "a.o"); + + assertThat(compileAction.getArguments()).doesNotContain("-framework"); + assertThat(Joiner.on("").join(compileAction.getArguments())).contains("-Ffx"); + assertThat(compileAction.getInputs()) + .containsAllOf( + getSourceArtifact("fx/fx1.framework/a"), + getSourceArtifact("fx/fx1.framework/b"), + getSourceArtifact("fx/fx2.framework/c"), + getSourceArtifact("fx/fx2.framework/d")); + } + + @Test + public void testPrecompiledHeaders() throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + scratch.file("objc/a.m"); + scratch.file("objc/c.pch"); + scratch.file( + "objc/BUILD", + RULE_TYPE.target( + scratch, "objc", "x", "srcs", "['a.m']", "non_arc_srcs", "['b.m']", "pch", "'c.pch'")); + CppCompileAction compileAction = (CppCompileAction) compileAction("//objc:x", "a.o"); + assertThat(Joiner.on(" ").join(compileAction.getArguments())) + .contains("-include objc/c.pch"); + assertThat(Artifact.toRootRelativePaths(compileAction.getPossibleInputsForTesting())) + .contains("objc/c.pch"); + } + + @Test + public void testCompilationActionsWithCopts() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--cpu=ios_i386", + "--ios_cpu=i386", + "--experimental_disable_go"); + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .setList("copts", "-Ifoo", "--monkeys=$(TARGET_CPU)") + .write(); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + assertThat(compileActionA.getArguments()).containsAllOf("-Ifoo", "--monkeys=ios_i386"); + } + + @Test + public void testObjcCopts() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--objccopt=-foo"); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .write(); + List<String> args = compileAction("//lib:lib", "a.o").getArguments(); + assertThat(args).contains("-foo"); + } + + @Test + public void testObjcCopts_argumentOrdering() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--objccopt=-foo"); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("copts", "-bar") + .write(); + List<String> args = compileAction("//lib:lib", "a.o").getArguments(); + assertThat(args).containsAllOf("-fobjc-arc", "-foo", "-bar").inOrder(); + } + + @Test + public void testCompilationActionsWithModuleMapsEnabled() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_enable_module_maps"); + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + assertThat(compileActionA.getArguments()) + .containsAllIn(moduleMapArtifactArguments("//objc", "lib")); + assertThat(compileActionA.getArguments()).contains("-fmodule-maps"); + assertThat(Artifact.toRootRelativePaths(compileActionA.getInputs())) + .doesNotContain("objc/lib.modulemaps/module.modulemap"); + } + + @Test + public void testCompilationActionsWithEmbeddedBitcode() throws Exception { + useConfiguration( + "--xcode_version=7.1", + "--ios_multi_cpus=arm64", + "--apple_bitcode=embedded"); + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + + assertThat(compileActionA.getArguments()).contains("-fembed-bitcode"); + } + + @Test + public void testCompilationActionsWithEmbeddedBitcodeMarkers() throws Exception { + useConfiguration( + "--xcode_version=7.1", + "--ios_multi_cpus=arm64", + "--apple_bitcode=embedded_markers"); + + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + + assertThat(compileActionA.getArguments()).contains("-fembed-bitcode-marker"); + } + + @Test + public void testCompilationActionsWithNoBitcode() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--xcode_version=7.1", + "--ios_multi_cpus=arm64", + "--apple_bitcode=none"); + + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + + assertThat(compileActionA.getArguments()).doesNotContain("-fembed-bitcode"); + assertThat(compileActionA.getArguments()).doesNotContain("-fembed-bitcode-marker"); + } + + /** + * Tests that bitcode is disabled for simulator builds even if enabled by flag. + */ + @Test + public void testCompilationActionsWithBitcode_simulator() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--xcode_version=7.1", + "--ios_multi_cpus=x86_64", + "--apple_bitcode=embedded"); + + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + + assertThat(compileActionA.getArguments()).doesNotContain("-fembed-bitcode"); + assertThat(compileActionA.getArguments()).doesNotContain("-fembed-bitcode-marker"); + } + + @Test + public void testModuleMapActionFiltersHeaders() throws Exception { + RULE_TYPE.scratchTarget( + scratch, + "srcs", + "['a.m', 'b.m', 'private.h', 'private.inc']", + "hdrs", + "['a.h', 'x.inc', 'foo.m', 'bar.mm']"); + + ConfiguredTarget configuredTarget = getConfiguredTarget("//x:x"); + Artifact moduleMap = getGenfilesArtifact("x.modulemaps/module.modulemap", configuredTarget); + + CppModuleMapAction genMap = (CppModuleMapAction) getGeneratingAction(moduleMap); + + assertThat(Artifact.toRootRelativePaths(genMap.getPrivateHeaders())).isEmpty(); + assertThat(Artifact.toRootRelativePaths(genMap.getPublicHeaders())).containsExactly("x/a.h"); + } + + @Test + public void testArchiveAction_simulator() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--cpu=ios_i386", + "--ios_cpu=i386"); + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + + CommandAction archiveAction = archiveAction("//objc:lib"); + assertThat(archiveAction.getArguments()) + .isEqualTo( + ImmutableList.of( + "tools/osx/crosstool/iossim/libtool", + "-static", + "-filelist", + getBinArtifact("lib-archive.objlist", "//objc:lib").getExecPathString(), + "-arch_only", + "i386", + "-syslibroot", + AppleToolchain.sdkDir(), + "-o", + Iterables.getOnlyElement(archiveAction.getOutputs()).getExecPathString())); + assertThat(baseArtifactNames(archiveAction.getInputs())) + .containsAllOf("a.o", "b.o", "lib-archive.objlist", CROSSTOOL_LINK_MIDDLEMAN); + assertThat(baseArtifactNames(archiveAction.getOutputs())).containsExactly("liblib.a"); + assertRequiresDarwin(archiveAction); + } + + @Test + public void testArchiveAction_device() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--cpu=ios_armv7", + "--ios_cpu=armv7"); + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + CommandAction archiveAction = archiveAction("//objc:lib"); + + assertThat(archiveAction.getArguments()) + .isEqualTo( + ImmutableList.of( + "tools/osx/crosstool/ios/libtool", + "-static", + "-filelist", + getBinArtifact("lib-archive.objlist", "//objc:lib").getExecPathString(), + "-arch_only", + "armv7", + "-syslibroot", + AppleToolchain.sdkDir(), + "-o", + Iterables.getOnlyElement(archiveAction.getOutputs()).getExecPathString())); + assertThat(baseArtifactNames(archiveAction.getInputs())) + .containsAllOf("a.o", "b.o", "lib-archive.objlist"); + assertThat(baseArtifactNames(archiveAction.getOutputs())).containsExactly("liblib.a"); + assertRequiresDarwin(archiveAction); + } + + @Test + public void testFullyLinkArchiveAction_simulator() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--cpu=ios_i386", + "--ios_cpu=i386"); + createLibraryTargetWriter("//objc:lib_dep") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "a.h", "b.h") + .write(); + createLibraryTargetWriter("//objc2:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h", "d.h") + .setList("deps", "//objc:lib_dep") + .write(); + CommandAction linkAction = + (CommandAction) getGeneratingActionForLabel("//objc2:lib_fully_linked.a"); + assertRequiresDarwin(linkAction); + assertThat(linkAction.getArguments()) + .isEqualTo( + ImmutableList.of( + "tools/osx/crosstool/iossim/libtool", + "-static", + "-arch_only", + "i386", + "-syslibroot", + AppleToolchain.sdkDir(), + "-o", + Iterables.getOnlyElement(linkAction.getOutputs()).getExecPathString(), + getBinArtifact("liblib.a", "//objc2:lib").getExecPathString(), + getBinArtifact("liblib_dep.a", "//objc:lib_dep").getExecPathString())); + // TODO(hlopko): make containsExactly once crosstools are updated so + // link_dynamic_library.sh is not needed anymore + assertThat(baseArtifactNames(linkAction.getInputs())).containsAllOf( + "liblib_dep.a", + "liblib.a", + CROSSTOOL_LINK_MIDDLEMAN); + } + + @Test + public void testFullyLinkArchiveAction_device() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--cpu=ios_armv7", + "--ios_cpu=armv7"); + createLibraryTargetWriter("//objc:lib_dep") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "a.h", "b.h") + .write(); + createLibraryTargetWriter("//objc2:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h", "d.h") + .setList("deps", "//objc:lib_dep") + .write(); + CommandAction linkAction = + (CommandAction) getGeneratingActionForLabel("//objc2:lib_fully_linked.a"); + assertRequiresDarwin(linkAction); + assertThat(linkAction.getArguments()) + .isEqualTo( + ImmutableList.of( + "tools/osx/crosstool/ios/libtool", + "-static", + "-arch_only", + "armv7", + "-syslibroot", + AppleToolchain.sdkDir(), + "-o", + Iterables.getOnlyElement(linkAction.getOutputs()).getExecPathString(), + getBinArtifact("liblib.a", "//objc2:lib").getExecPathString(), + getBinArtifact("liblib_dep.a", "//objc:lib_dep").getExecPathString())); + // TODO(hlopko): make containsExactly once crosstools are updated so + // link_dynamic_library.sh is not needed anymore + assertThat(baseArtifactNames(linkAction.getInputs())).containsAllOf( + "liblib_dep.a", + "liblib.a", + CROSSTOOL_LINK_MIDDLEMAN); + } + + @Test + public void checkDoesNotStoreObjcLibsAsCC() throws Exception { + createLibraryTargetWriter("//objc:lib_dep") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "a.h", "b.h") + .write(); + createLibraryTargetWriter("//objc2:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h", "d.h") + .setList("deps", "//objc:lib_dep") + .write(); + ObjcProvider objcProvider = providerForTarget("//objc2:lib"); + assertThat(objcProvider.get(CC_LIBRARY)).isEmpty(); + } + + @Test + public void testIncludesDirsGetPassedToCompileAction() throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("includes", "../third_party/foo", "opensource/bar") + .write(); + CommandAction compileAction = compileAction("//lib:lib", "a.o"); + + for (String path : + rootedIncludePaths( + getAppleCrosstoolConfiguration(), "third_party/foo", "lib/opensource/bar")) { + assertThat(Joiner.on("").join(compileAction.getArguments())).contains("-I" + path); + } + } + + @Test + public void testPropagatesDefinesToDependersTransitively() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--cpu=ios_x86_64", + "--ios_cpu=x86_64"); + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m") + .setAndCreateFiles("non_arc_srcs", "b.m") + .setList("defines", "A=foo", "B", "MONKEYS=$(TARGET_CPU)") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m") + .setAndCreateFiles("non_arc_srcs", "b.m") + .setList("deps", "//lib1:lib1") + .setList("defines", "C=bar", "D") + .write(); + createBinaryTargetWriter("//bin:bin") + .setAndCreateFiles("srcs", "c.m") + .setList("deps", "//lib2:lib2") + .write(); + + assertThat(compileAction("//lib1:lib1", "a.o").getArguments()) + .containsAllOf("-DA=foo", "-DB", "-DMONKEYS=ios_x86_64") + .inOrder(); + assertThat(compileAction("//lib1:lib1", "b.o").getArguments()) + .containsAllOf("-DA=foo", "-DB", "-DMONKEYS=ios_x86_64") + .inOrder(); + assertThat(compileAction("//lib2:lib2", "a.o").getArguments()) + .containsAllOf("-DA=foo", "-DB", "-DMONKEYS=ios_x86_64", "-DC=bar", "-DD") + .inOrder(); + assertThat(compileAction("//lib2:lib2", "b.o").getArguments()) + .containsAllOf("-DA=foo", "-DB", "-DMONKEYS=ios_x86_64", "-DC=bar", "-DD") + .inOrder(); + // TODO: Add tests for //bin:bin once experimental_objc_binary is implemented + } + + @Test + public void testDuplicateDefines() throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m") + .setList("defines", "foo=bar", "foo=bar") + .write(); + int timesDefinesAppear = 0; + for (String arg : compileAction("//lib:lib", "a.o").getArguments()) { + if (arg.equals("-Dfoo=bar")) { + timesDefinesAppear++; + } + } + assertWithMessage("Duplicate define \"foo=bar\" should occur only once in command line") + .that(timesDefinesAppear) + .isEqualTo(1); + } + + @Test + public void checkDefinesFromCcLibraryDep() throws Exception { + checkDefinesFromCcLibraryDep(RULE_TYPE); + } + + @Test + public void testCppSourceCompilesWithCppFlags() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + createLibraryTargetWriter("//objc:x") + .setAndCreateFiles("srcs", "a.mm", "b.cc", "c.mm", "d.cxx", "e.c", "f.m", "g.C") + .write(); + assertThat(compileAction("//objc:x", "a.o").getArguments()).contains("-std=gnu++11"); + assertThat(compileAction("//objc:x", "b.o").getArguments()).contains("-std=gnu++11"); + assertThat(compileAction("//objc:x", "c.o").getArguments()).contains("-std=gnu++11"); + assertThat(compileAction("//objc:x", "d.o").getArguments()).contains("-std=gnu++11"); + assertThat(compileAction("//objc:x", "e.o").getArguments()).doesNotContain("-std=gnu++11"); + assertThat(compileAction("//objc:x", "f.o").getArguments()).doesNotContain("-std=gnu++11"); + assertThat(compileAction("//objc:x", "g.o").getArguments()).contains("-std=gnu++11"); + } + + @Test + public void testAssetCatalogsAttributeErrorForNotInXcAssetsDir() throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + scratch.file("lib/ac/notinxcassets1"); + scratch.file("lib/ac/notinxcassets2"); + scratch.file("lib/ac/foo.xcassets/isinxcassets"); + checkError("lib", "lib", + String.format(ObjcCommon.NOT_IN_CONTAINER_ERROR_FORMAT, + "lib/ac/notinxcassets2", ImmutableList.of(ObjcCommon.ASSET_CATALOG_CONTAINER_TYPE)), + "objc_library(name = 'lib', srcs = ['src.m'], asset_catalogs = glob(['ac/**']))"); + } + + @Test + public void testXcdatamodelsAttributeErrorForNotInXcdatamodelDir() throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + scratch.file("lib/xcd/notinxcdatamodel1"); + scratch.file("lib/xcd/notinxcdatamodel2"); + scratch.file("lib/xcd/foo.xcdatamodel/isinxcdatamodel"); + scratch.file("lib/xcd/bar.xcdatamodeld/isinxcdatamodeld"); + checkError("lib", "lib", + String.format(ObjcCommon.NOT_IN_CONTAINER_ERROR_FORMAT, + "lib/xcd/notinxcdatamodel1", Xcdatamodels.CONTAINER_TYPES), + "objc_library(name = 'lib', srcs = ['src.m'], datamodels = glob(['xcd/**']))"); + } + + @Test + public void testProvidesStoryboardOptions() throws Exception { + checkProvidesStoryboardObjects(RULE_TYPE); + } + + @Test + public void testDoesNotUseCxxUnfilteredFlags() throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + createLibraryTargetWriter("//lib:lib") + .setList("srcs", "a.m") + .write(); + // -pthread is an unfiltered_cxx_flag in the osx crosstool. + assertThat(compileAction("//lib:lib", "a.o").getArguments()).doesNotContain("-pthread"); + } + + @Test + public void testDoesNotUseDotdPruning() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--objc_use_dotd_pruning=false"); + createLibraryTargetWriter("//lib:lib") + .setList("srcs", "a.m") + .write(); + CppCompileAction compileAction = (CppCompileAction) compileAction("//lib:lib", "a.o"); + assertThat(compileAction.discoverInputsFromDotdFiles(null, null, null)).isEmpty(); + } + + @Test + public void testProvidesObjcLibraryAndHeaders() throws Exception { + ConfiguredTarget target = + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "a.h", "b.h") + .write(); + ConfiguredTarget depender = + createLibraryTargetWriter("//objc2:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h", "d.h") + .setList("deps", "//objc:lib") + .write(); + assertThat(Artifact.toRootRelativePaths(target.getProvider(ObjcProvider.class).get(LIBRARY))) + .containsExactly("objc/liblib.a"); + assertThat(Artifact.toRootRelativePaths(depender.getProvider(ObjcProvider.class).get(LIBRARY))) + .containsExactly("objc/liblib.a", "objc2/liblib.a"); + assertThat(Artifact.toRootRelativePaths(target.getProvider(ObjcProvider.class).get(HEADER))) + .containsExactly("objc/a.h", "objc/b.h"); + assertThat(Artifact.toRootRelativePaths(depender.getProvider(ObjcProvider.class).get(HEADER))) + .containsExactly("objc/a.h", "objc/b.h", "objc2/c.h", "objc2/d.h"); + } + + @Test + public void testWeakSdkFrameworks_objcProvider() throws Exception { + createLibraryTargetWriter("//base_lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("weak_sdk_frameworks", "foo") + .write(); + createLibraryTargetWriter("//depender_lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("weak_sdk_frameworks", "bar") + .setList("deps", "//base_lib:lib") + .write(); + + ObjcProvider baseProvider = providerForTarget("//base_lib:lib"); + ObjcProvider dependerProvider = providerForTarget("//depender_lib:lib"); + + assertThat(baseProvider.get(WEAK_SDK_FRAMEWORK)).containsExactly(new SdkFramework("foo")); + assertThat(dependerProvider.get(WEAK_SDK_FRAMEWORK)) + .containsExactly(new SdkFramework("foo"), new SdkFramework("bar")); + } + + @Test + public void testErrorIfDepDoesNotExist() throws Exception { + checkErrorIfNotExist("deps", "[':nonexistent']"); + } + + @Test + public void testArIsNotImplicitOutput() throws Exception { + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .write(); + try { + reporter.removeHandler(failFastHandler); + getTarget("//lib:liblib.a"); + fail("should have thrown"); + } catch (NoSuchTargetException expected) { + } + } + + @Test + public void testErrorForAbsoluteIncludesPath() throws Exception { + scratch.file("x/a.m"); + checkError( + "x", + "x", + String.format(ABSOLUTE_INCLUDES_PATH_FORMAT, "/absolute/path"), + "objc_library(", + " name = 'x',", + " srcs = ['a.m'],", + " includes = ['/absolute/path'],", + ")"); + } + + @Test + public void testExportsBundleDependencies() throws Exception { + scratch.file("bundle/bar/x.bundle/1"); + scratch.file( + "bundle/BUILD", + "objc_bundle(", + " name = 'bundle',", + " bundle_imports = glob(['bar/**']),", + ")"); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("bundles", "//bundle:bundle") + .write(); + ObjcProvider provider = providerForTarget("//lib:lib"); + assertThat(provider.get(BUNDLE_FILE)) + .contains(new BundleableFile(getSourceArtifact("bundle/bar/x.bundle/1"), "x.bundle/1")); + } + + @Test + public void testDylibsProvided() throws Exception { + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("sdk_dylibs", "libdy1", "libdy2") + .write(); + ObjcProvider provider = providerForTarget("//lib:lib"); + assertThat(provider.get(SDK_DYLIB)).containsExactly("libdy1", "libdy2").inOrder(); + } + + @Test + public void testPopulatesCompilationArtifacts() throws Exception { + checkPopulatesCompilationArtifacts(RULE_TYPE); + } + + @Test + public void testProvidesXcassetCatalogsTransitively() throws Exception { + scratch.file("lib1/ac.xcassets/foo"); + scratch.file("lib1/ac.xcassets/bar"); + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .set("asset_catalogs", "glob(['ac.xcassets/**'])") + .write(); + scratch.file("lib2/ac.xcassets/baz"); + scratch.file("lib2/ac.xcassets/42"); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .set("asset_catalogs", "glob(['ac.xcassets/**'])") + .setList("deps", "//lib1:lib1") + .write(); + + ObjcProvider lib2Provider = providerForTarget("//lib2:lib2"); + assertThat(Artifact.toExecPaths(lib2Provider.get(ASSET_CATALOG))) + .containsExactly( + "lib1/ac.xcassets/foo", + "lib1/ac.xcassets/bar", + "lib2/ac.xcassets/baz", + "lib2/ac.xcassets/42"); + assertThat(lib2Provider.get(XCASSETS_DIR)) + .containsExactly( + PathFragment.create("lib1/ac.xcassets"), PathFragment.create("lib2/ac.xcassets")); + + ObjcProvider lib1Provider = providerForTarget("//lib1:lib1"); + assertThat(Artifact.toExecPaths(lib1Provider.get(ASSET_CATALOG))) + .containsExactly("lib1/ac.xcassets/foo", "lib1/ac.xcassets/bar"); + assertThat(lib1Provider.get(XCASSETS_DIR)) + .containsExactly(PathFragment.create("lib1/ac.xcassets")) + .inOrder(); + } + + @Test + public void testObjcListFileInArchiveGeneration() throws Exception { + scratch.file("lib/a.m"); + scratch.file("lib/b.m"); + scratch.file("lib/BUILD", "objc_library(name = 'lib1', srcs = ['a.m', 'b.m'])"); + ConfiguredTarget target = getConfiguredTarget("//lib:lib1"); + Artifact objlist = getBinArtifact("lib1-archive.objlist", target); + ParameterFileWriteAction action = (ParameterFileWriteAction) getGeneratingAction(objlist); + assertThat(action.getContents()) + .containsExactlyElementsIn( + Artifact.toExecPaths(inputsEndingWith(archiveAction("//lib:lib1"), ".o"))); + } + + @Test + public void testErrorsWrongFileTypeForSrcsWhenCompiling() throws Exception { + checkErrorsWrongFileTypeForSrcsWhenCompiling(RULE_TYPE); + } + + @Test + public void testCompilationActionsForDebug() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.DBG, CodeCoverageMode.NONE); + } + + @Test + public void testClangCoptsForDebugModeWithoutGlib() throws Exception { + checkClangCoptsForDebugModeWithoutGlib(RULE_TYPE); + } + + @Test + public void testCompilationActionsForOptimized() throws Exception { + checkClangCoptsForCompilationMode(RULE_TYPE, CompilationMode.OPT, CodeCoverageMode.NONE); + } + + @Test + public void testUsesDefinesFromTransitiveCcDeps() throws Exception { + scratch.file( + "package/BUILD", + "cc_library(", + " name = 'cc_lib',", + " srcs = ['a.cc'],", + " defines = ['FOO'],", + ")", + "", + "objc_library(", + " name = 'objc_lib',", + " srcs = ['b.m'],", + " deps = [':cc_lib'],", + ")"); + + CommandAction compileAction = compileAction("//package:objc_lib", "b.o"); + assertThat(compileAction.getArguments()).contains("-DFOO"); + } + + @Test + public void testAllowVariousNonBlacklistedTypesInHeaders() throws Exception { + checkAllowVariousNonBlacklistedTypesInHeaders(RULE_TYPE); + } + + @Test + public void testWarningForBlacklistedTypesInHeaders() throws Exception { + checkWarningForBlacklistedTypesInHeaders(RULE_TYPE); + } + + @Test + public void testBundleInformationPropagatedThroughLibraries() throws Exception { + checkNestedBundleInformationPropagatedToDependers(RULE_TYPE); + } + + @Test + public void testAppleSdkVersionEnv() throws Exception { + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + CommandAction action = compileAction("//objc:lib", "a.o"); + + assertAppleSdkVersionEnv(action); + } + + @Test + public void testNonDefaultAppleSdkVersionEnv() throws Exception { + useConfiguration("--ios_sdk_version=8.1"); + + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + CommandAction action = compileAction("//objc:lib", "a.o"); + + assertAppleSdkVersionEnv(action, "8.1"); + } + + @Test + public void testXcodeVersionEnv() throws Exception { + useConfiguration("--xcode_version=5.8"); + + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + CommandAction action = compileAction("//objc:lib", "a.o"); + + assertXcodeVersionEnv(action, "5.8"); + } + + @Test + public void testIosSdkVersionCannotBeDefinedButEmpty() throws Exception { + try { + useConfiguration("--ios_sdk_version="); + fail("Should fail for empty ios_sdk_version"); + } catch (OptionsParsingException e) { + assertThat(e).hasMessageThat().contains("--ios_sdk_version"); + } + } + + private void checkErrorIfNotExist(String attribute, String value) throws Exception { + scratch.file("x/a.m"); + checkError( + "x", + "x", + "in " + + attribute + + " attribute of objc_library rule //x:x: rule '//x:nonexistent' does not exist", + "objc_library(", + " name = 'x',", + " srcs = ['a.m'],", + attribute + " = " + value, + ")"); + } + + @Test + public void testUsesDotdPruning() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, "--objc_use_dotd_pruning"); + createLibraryTargetWriter("//lib:lib").setList("srcs", "a.m").write(); + CppCompileAction compileAction = (CppCompileAction) compileAction("//lib:lib", "a.o"); + try { + compileAction.discoverInputsFromDotdFiles(null, null, null); + fail("Expected ActionExecutionException"); + } catch (ActionExecutionException expected) { + assertThat(expected).hasMessageThat().contains("error while parsing .d file"); + } + } + + @Test + public void testAppleSdkDefaultPlatformEnv() throws Exception { + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + CommandAction action = compileAction("//objc:lib", "a.o"); + + assertAppleSdkPlatformEnv(action, "iPhoneSimulator"); + } + + @Test + public void testAppleSdkDevicePlatformEnv() throws Exception { + useConfiguration("--cpu=ios_arm64"); + + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .write(); + CommandAction action = compileAction("//objc:lib", "a.o"); + + assertAppleSdkPlatformEnv(action, "iPhoneOS"); + } + + @Test + public void testApplePlatformEnvForCcLibraryDep() throws Exception { + useConfiguration( + "--experimental_disable_go", "--experimental_disable_jvm", "--cpu=ios_i386", + "--crosstool_top=//tools/osx/crosstool:crosstool"); + + scratch.file("package/BUILD", + "cc_library(", + " name = 'cc_lib',", + " srcs = ['a.cc'],", + ")", + "", + "objc_binary(", + " name = 'objc_bin',", + " srcs = ['b.m'],", + " deps = [':cc_lib'],", + ")"); + + Action binLinkAction = linkAction("//package:objc_bin"); + Artifact artifact = + ActionsTestUtil.getFirstArtifactEndingWith(binLinkAction.getInputs(), "libcc_lib.a"); + Action cppLibLinkAction = getGeneratingAction(artifact); + Artifact cppLibArtifact = + ActionsTestUtil.getFirstArtifactEndingWith(cppLibLinkAction.getInputs(), ".o"); + + CppCompileAction action = (CppCompileAction) getGeneratingAction(cppLibArtifact); + assertAppleSdkVersionEnv(action.getEnvironment()); + } + + @Test + public void testDoesNotPropagateProtoIncludes() throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + scratch.file( + "x/BUILD", + "proto_library(", + " name = 'protos',", + " srcs = ['data.proto'],", + ")", + "objc_proto_library(", + " name = 'objc_proto_lib',", + " deps = [':protos'],", + " portable_proto_filters = ['data_filter.pbascii'],", + ")"); + createLibraryTargetWriter("//a:lib") + .setList("srcs", "a.m") + .setList("deps", "//x:objc_proto_lib") + .write(); + createLibraryTargetWriter("//b:lib").setList("srcs", "b.m").setList("deps", "//a:lib").write(); + + CommandAction compileAction1 = compileAction("//a:lib", "a.o"); + CommandAction compileAction2 = compileAction("//b:lib", "b.o"); + + assertThat(Joiner.on(" ").join(compileAction1.getArguments())).contains("objc_proto_lib"); + assertThat(Joiner.on(" ").join(compileAction2.getArguments())).doesNotContain("objc_proto_lib"); + } + + @Test + public void testExportsJ2ObjcProviders() throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + ConfiguredTarget lib = createLibraryTargetWriter("//a:lib").write(); + assertThat(lib.getProvider(J2ObjcEntryClassProvider.class)).isNotNull(); + assertThat(lib.getProvider(J2ObjcMappingFileProvider.class)).isNotNull(); + } + + @Test + public void testObjcProtoLibraryDoesNotCrash() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all"); + scratch.file( + "x/BUILD", + "objc_library(", + " name = 'objc',", + " srcs = ['source.m'],", + " deps = [':objc_proto_lib'],", + ")", + "proto_library(", + " name = 'protos',", + " srcs = ['data.proto'],", + ")", + "objc_proto_library(", + " name = 'objc_proto_lib',", + " deps = [':protos'],", + " portable_proto_filters = ['data_filter.pbascii'],", + ")"); + assertThat(getConfiguredTarget("//x:objc")).isNotNull(); + } + + @Test + public void testLegacyObjcProtoLibraryDoesNotCrash() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all"); + scratch.file( + "x/BUILD", + "objc_library(", + " name = 'objc',", + " srcs = ['source.m'],", + " deps = [':objc_proto_lib'],", + ")", + "proto_library(", + " name = 'protos',", + " srcs = ['data.proto'],", + ")", + "objc_proto_library(", + " name = 'objc_proto_lib',", + " deps = [':protos'],", + ")"); + assertThat(getConfiguredTarget("//x:objc")).isNotNull(); + } + + @Test + public void testObjcImportDoesNotCrash() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all"); + scratch.file( + "x/BUILD", + "objc_library(", + " name = 'objc',", + " srcs = ['source.m'],", + " deps = [':import'],", + ")", + "objc_import(", + " name = 'import',", + " archives = ['archive.a'],", + ")"); + assertThat(getConfiguredTarget("//x:objc")).isNotNull(); + } + + @Test + public void testCompilationActionsWithIQuotesInCopts() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--cpu=ios_i386", + "--ios_cpu=i386"); + createLibraryTargetWriter("//objc:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "c.h") + .setList("copts", "-iquote foo/bar", "-iquote bam/baz") + .write(); + + CommandAction compileActionA = compileAction("//objc:lib", "a.o"); + String action = String.join(" ", compileActionA.getArguments()); + assertThat(action).contains("-iquote foo/bar"); + assertThat(action).contains("-iquote bam/baz"); + } + @Test + public void testCollectCodeCoverageWithGCOVFlags() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, "--collect_code_coverage"); + createLibraryTargetWriter("//objc:x") + .setAndCreateFiles("srcs", "a.mm", "b.cc", "c.mm", "d.cxx", "e.c", "f.m", "g.C") + .write(); + List<String> copts = ImmutableList.of("-fprofile-arcs", "-ftest-coverage"); + assertThat(compileAction("//objc:x", "a.o").getArguments()).containsAllIn(copts); + assertThat(compileAction("//objc:x", "b.o").getArguments()).containsAllIn(copts); + assertThat(compileAction("//objc:x", "c.o").getArguments()).containsAllIn(copts); + assertThat(compileAction("//objc:x", "d.o").getArguments()).containsAllIn(copts); + assertThat(compileAction("//objc:x", "e.o").getArguments()).containsAllIn(copts); + assertThat(compileAction("//objc:x", "f.o").getArguments()).containsAllIn(copts); + assertThat(compileAction("//objc:x", "g.o").getArguments()).containsAllIn(copts); + } + + @Test + public void testCollectCodeCoverageWithLLVMCOVFlags() throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--collect_code_coverage", + "--experimental_use_llvm_covmap"); + createLibraryTargetWriter("//objc:x") + .setAndCreateFiles("srcs", "a.mm", "b.cc", "c.mm", "d.cxx", "e.c", "f.m", "g.C") + .write(); + List<String> copts = ImmutableList.of("-fprofile-instr-generate", "-fcoverage-mapping"); + assertThat(compileAction("//objc:x", "a.o").getArguments()).containsAllIn(copts); + assertThat(compileAction("//objc:x", "b.o").getArguments()).containsAllIn(copts); + assertThat(compileAction("//objc:x", "c.o").getArguments()).containsAllIn(copts); + assertThat(compileAction("//objc:x", "d.o").getArguments()).containsAllIn(copts); + assertThat(compileAction("//objc:x", "e.o").getArguments()).containsAllIn(copts); + assertThat(compileAction("//objc:x", "f.o").getArguments()).containsAllIn(copts); + assertThat(compileAction("//objc:x", "g.o").getArguments()).containsAllIn(copts); + } + + @Test + public void testNoG0IfGeneratesDsym() throws Exception { + useConfiguration("--apple_generate_dsym", "-c", "opt"); + createLibraryTargetWriter("//x:x").setList("srcs", "a.m").write(); + CommandAction compileAction = compileAction("//x:x", "a.o"); + assertThat(compileAction.getArguments()).doesNotContain("-g0"); + } + + @Test + public void testFilesToCompileOutputGroup() throws Exception { + checkFilesToCompileOutputGroup(RULE_TYPE); + } + + @Test + public void testSysrootArgSpecifiedWithGrteTopFlag() throws Exception { + MockObjcSupport.setup(mockToolsConfig, "default_grte_top : '//x'"); + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--experimental_objc_crosstool=all", + "--cpu=ios_x86_64", + "--ios_cpu=x86_64"); + scratch.file( + "x/BUILD", + "objc_library(", + " name = 'objc',", + " srcs = ['source.m'],", + ")", + "filegroup(", + " name = 'everything',", + " srcs = ['header.h'],", + ")"); + CommandAction compileAction = compileAction("//x:objc", "source.o"); + assertThat(compileAction.getArguments()).contains("--sysroot=x"); + } + + @Test + public void testDefaultEnabledFeatureIsUsed() throws Exception { + MockObjcSupport.setup(mockToolsConfig, + "feature {", + " name: 'default'", + " enabled : true", + " flag_set {", + " action: 'objc-compile'", + " flag_group {", + " flag: '-dummy'", + " }", + " }", + "}"); + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--experimental_objc_crosstool=all", + "--cpu=ios_x86_64", + "--ios_cpu=x86_64"); + scratch.file( + "x/BUILD", + "objc_library(", + " name = 'objc',", + " srcs = ['source.m'],", + ")"); + CommandAction compileAction = compileAction("//x:objc", "source.o"); + assertThat(compileAction.getArguments()).contains("-dummy"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcProtoAspectTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcProtoAspectTest.java new file mode 100644 index 0000000000..83611a5b4b --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcProtoAspectTest.java @@ -0,0 +1,227 @@ +// 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.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.util.BuildViewTestCase; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.packages.util.MockObjcSupport; +import com.google.devtools.build.lib.packages.util.MockProtoSupport; +import com.google.devtools.build.lib.vfs.PathFragment; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for the objc_proto_library aspect. */ +@RunWith(JUnit4.class) +public final class ObjcProtoAspectTest extends BuildViewTestCase { + + @Before + public final void initializeToolsConfigMock() throws Exception { + MockProtoSupport.setup(mockToolsConfig); + MockObjcSupport.setupObjcProto(mockToolsConfig); + } + + @Test + public void testObjcProtoAspectPropagatesProvider() throws Exception { + scratch.file( + "x/BUILD", + "proto_library(", + " name = 'protos',", + " srcs = ['data.proto'],", + ")", + "objc_proto_library(", + " name = 'x',", + " deps = [':protos'],", + " portable_proto_filters = ['data_filter.pbascii'],", + ")"); + ConfiguredTarget topTarget = getObjcProtoAspectConfiguredTarget("//x:x"); + ObjcProtoProvider objcProtoProvider = topTarget.getProvider(ObjcProtoProvider.class); + assertThat(objcProtoProvider).isNotNull(); + } + + @Test + public void testObjcProtoAspectPropagatesProtobufProvider() throws Exception { + scratch.file( + "x/BUILD", + "proto_library(", + " name = 'protos',", + " srcs = ['data.proto'],", + ")", + "objc_proto_library(", + " name = 'x',", + " deps = [':protos'],", + " portable_proto_filters = ['data_filter.pbascii'],", + ")"); + ConfiguredTarget topTarget = getObjcProtoAspectConfiguredTarget("//x:x"); + ObjcProtoProvider objcProtoProvider = topTarget.getProvider(ObjcProtoProvider.class); + assertThat(objcProtoProvider).isNotNull(); + assertThat(Artifact.toExecPaths(objcProtoProvider.getProtobufHeaders())) + .containsExactly("objcproto/include/header.h"); + + Artifact header = Iterables.getOnlyElement(objcProtoProvider.getProtobufHeaders()); + PathFragment includePath = header.getExecPath().getParentDirectory(); + PathFragment genIncludePath = + PathFragment.create( + getConfiguration(targetConfig, AppleCrosstoolTransition.APPLE_CROSSTOOL_TRANSITION) + .getGenfilesFragment() + + "/" + + includePath); + + assertThat(objcProtoProvider.getProtobufHeaderSearchPaths()) + .containsExactly(includePath, genIncludePath); + } + + @Test + public void testObjcProtoAspectDoesNotPropagateProviderWhenNoProtos() throws Exception { + scratch.file("x/BUILD", + "objc_library(", + " name = 'x',", + " srcs = ['A.m'],", + ")"); + ConfiguredTarget topTarget = getObjcProtoAspectConfiguredTarget("//x:x"); + ObjcProtoProvider objcProtoProvider = topTarget.getProvider(ObjcProtoProvider.class); + assertThat(objcProtoProvider).isNull(); + } + + @Test + public void testObjcProtoAspectBundlesDuplicateSymbols() throws Exception { + scratch.file( + "x/BUILD", + "objc_library(", + " name = 'x',", + " srcs = ['A.m'],", + " deps = [", + " ':objc_proto',", + " ':objc_proto_duplicate',", + " ],", + ")", + "proto_library(", + " name = 'protos',", + " srcs = ['data.proto'],", + ")", + "objc_proto_library(", + " name = 'objc_proto',", + " deps = [':protos'],", + " portable_proto_filters = ['data_filter.pbascii'],", + ")", + "objc_proto_library(", + " name = 'objc_proto_duplicate',", + " deps = [':protos'],", + " portable_proto_filters = ['data_filter.pbascii'],", + ")"); + ConfiguredTarget topTarget = getObjcProtoAspectConfiguredTarget("//x:x"); + ObjcProtoProvider objcProtoProvider = topTarget.getProvider(ObjcProtoProvider.class); + assertThat(objcProtoProvider).isNotNull(); + + assertThat(Artifact.toExecPaths(Iterables.concat(objcProtoProvider.getProtoGroups()))) + .containsExactly("x/data.proto"); + assertThat(Artifact.toExecPaths(objcProtoProvider.getPortableProtoFilters())) + .containsExactly("x/data_filter.pbascii"); + } + + @Test + public void testObjcProtoAspectPropagatesGeneratedFilter() throws Exception { + scratch.file( + "x/BUILD", + "objc_library(", + " name = 'x',", + " srcs = ['A.m'],", + " deps = [", + " ':objc_proto',", + " ],", + ")", + "proto_library(", + " name = 'protos',", + " srcs = ['data.proto'],", + ")", + "objc_proto_library(", + " name = 'objc_proto',", + " deps = [':protos'],", + " uses_protobuf = 1,", + ")"); + ConfiguredTarget topTarget = getObjcProtoAspectConfiguredTarget("//x:x"); + ObjcProtoProvider objcProtoProvider = topTarget.getProvider(ObjcProtoProvider.class); + assertThat(objcProtoProvider).isNotNull(); + + assertThat(Artifact.toExecPaths(objcProtoProvider.getPortableProtoFilters())) + .containsExactly( + getConfiguration(targetConfig, AppleCrosstoolTransition.APPLE_CROSSTOOL_TRANSITION) + .getGenfilesFragment() + + "/x/_proto_filters/objc_proto/generated_filter_file.pbascii"); + } + + @Test + public void testObjcProtoAspectPropagatesFiltersFromDependenciesOfObjcProtoLibrary() + throws Exception { + scratch.file( + "x/BUILD", + "objc_library(", + " name = 'x',", + " srcs = ['A.m'],", + " deps = [", + " ':objc_proto_all',", + " ],", + ")", + "proto_library(", + " name = 'protos',", + " srcs = ['data.proto'],", + ")", + "objc_proto_library(", + " name = 'objc_proto_all',", + " deps = [':objc_proto_1', ':objc_proto_2'],", + " uses_protobuf = 1,", + ")", + "objc_proto_library(", + " name = 'objc_proto_1',", + " deps = [':protos'],", + " portable_proto_filters = ['filter.pbascii'],", + ")", + "objc_proto_library(", + " name = 'objc_proto_2',", + " deps = [':protos'],", + " uses_protobuf = 1,", + ")"); + ConfiguredTarget topTarget = getObjcProtoAspectConfiguredTarget("//x:x"); + ObjcProtoProvider objcProtoProvider = topTarget.getProvider(ObjcProtoProvider.class); + assertThat(objcProtoProvider).isNotNull(); + + assertThat(Artifact.toExecPaths(objcProtoProvider.getPortableProtoFilters())) + .containsAllOf( + "x/filter.pbascii", + getConfiguration(targetConfig, AppleCrosstoolTransition.APPLE_CROSSTOOL_TRANSITION) + .getGenfilesFragment() + + "/x/_proto_filters/objc_proto_2/generated_filter_file.pbascii"); + } + + private ConfiguredTarget getObjcProtoAspectConfiguredTarget(String label) throws Exception { + scratch.file("bin/BUILD", + "objc_binary(", + " name = 'link_target',", + " deps = ['" + label + "'],", + ")"); + + return view.getPrerequisiteConfiguredTargetForTesting( + reporter, + getConfiguredTarget("//bin:link_target"), + Label.parseAbsoluteUnchecked(label), + masterConfig); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcProtoLibraryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcProtoLibraryTest.java new file mode 100644 index 0000000000..a2689cd8fc --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcProtoLibraryTest.java @@ -0,0 +1,854 @@ +// 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.ImmutableSet; +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.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.cpp.CppModuleMapAction; +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.setupObjcProto(mockToolsConfig); + } + + @Before + public final void createFiles() throws Exception { + scratch.file( + "package/BUILD", + "apple_binary(", + " name = 'opl_binary',", + " deps = [':opl_protobuf'],", + " platform_type = 'tvos'", + ")", + "objc_library(", + " name = 'non_strict_lib',", + " deps = [':strict_lib'],", + ")", + "", + "objc_library(", + " name = 'strict_lib',", + " deps = [':opl_protobuf'],", + ")", + "", + "objc_proto_library(", + " name = 'opl',", + " deps = [':protolib'],", + ")", + "", + "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',", + " ],", + ")", + "objc_proto_library(", + " name = 'opl_well_known_types',", + " deps = [':protolib_well_known_types'],", + ")", + "", + "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 = ['//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<Artifact> filesToBuild = getFilesToBuild(getConfiguredTarget("//package:opl")); + assertThat(Artifact.toRootRelativePaths(filesToBuild)) + .containsAllOf( + "package/libopl.a", + "package/_generated_protos/opl/package/FileA.pb.h", + "package/_generated_protos/opl/package/FileA.pb.m", + "package/_generated_protos/opl/package/dir/FileB.pb.h", + "package/_generated_protos/opl/package/dir/FileB.pb.m", + "package/_generated_protos/opl/dep/File.pb.h"); + } + + @Test + public void testOutputsProtobuf() throws Exception { + NestedSet<Artifact> 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 testOutputsWithAutoUnionExperiment() throws Exception { + NestedSet<Artifact> filesToBuild = getFilesToBuild(getConfiguredTarget("//package:opl")); + assertThat(Artifact.toRootRelativePaths(filesToBuild)) + .containsAllOf( + "package/libopl.a", + "package/_generated_protos/opl/package/FileA.pb.h", + "package/_generated_protos/opl/package/FileA.pb.m", + "package/_generated_protos/opl/package/dir/FileB.pb.h", + "package/_generated_protos/opl/package/dir/FileB.pb.m", + "package/_generated_protos/opl/dep/File.pb.h"); + } + + @Test + public void testDependingOnProtobufObjcProtoLibrary() throws Exception { + NestedSet<Artifact> 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 testOutputsProtobufWithAutoUnionExperiment() throws Exception { + NestedSet<Artifact> 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 testPB2GeneratedFileNames() throws Exception { + NestedSet<Artifact> filesToBuild = + getFilesToBuild(getConfiguredTarget("//package:opl_pb2_special_names")); + assertThat(Artifact.toRootRelativePaths(filesToBuild)) + .containsAllOf( + "package/_generated_protos/opl_pb2_special_names/package/J2ObjcDescriptor.pb.h", + "package/_generated_protos/opl_pb2_special_names/package/J2ObjcDescriptor.pb.m", + "package/_generated_protos/opl_pb2_special_names/package/Http.pb.h", + "package/_generated_protos/opl_pb2_special_names/package/Http.pb.m", + "package/_generated_protos/opl_pb2_special_names/package/Https.pb.h", + "package/_generated_protos/opl_pb2_special_names/package/Https.pb.m", + "package/_generated_protos/opl_pb2_special_names/package/SomeUrlBlah.pb.h", + "package/_generated_protos/opl_pb2_special_names/package/SomeUrlBlah.pb.m", + "package/_generated_protos/opl_pb2_special_names/package/ThumbnailUrl.pb.h", + "package/_generated_protos/opl_pb2_special_names/package/ThumbnailUrl.pb.m", + "package/_generated_protos/opl_pb2_special_names/package/Url.pb.h", + "package/_generated_protos/opl_pb2_special_names/package/Url.pb.m", + "package/_generated_protos/opl_pb2_special_names/package/Url2Https.pb.h", + "package/_generated_protos/opl_pb2_special_names/package/Url2Https.pb.m", + "package/_generated_protos/opl_pb2_special_names/package/Urlbar.pb.h", + "package/_generated_protos/opl_pb2_special_names/package/Urlbar.pb.m"); + } + + @Test + public void testProtobufGeneratedFileNames() throws Exception { + NestedSet<Artifact> 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 testOutputsPB2WithWellKnownTypes() throws Exception { + NestedSet<Artifact> filesToBuild = + getFilesToBuild(getConfiguredTarget("//package:opl_well_known_types")); + assertThat(Artifact.toRootRelativePaths(filesToBuild)) + .containsAllOf( + "package/_generated_protos/opl_well_known_types/package/FileA.pb.h", + "package/_generated_protos/opl_well_known_types/package/FileA.pb.m"); + assertThat(Artifact.toRootRelativePaths(filesToBuild)) + .containsNoneOf( + "package/_generated_protos/opl_well_known_types/objcproto/WellKnownType.pb.h", + "package/_generated_protos/opl_well_known_types/objcproto/WellKnownType.pb.m"); + } + + @Test + public void testOutputsProtobufWithWellKnownTypes() throws Exception { + NestedSet<Artifact> 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<Artifact> filesToBuild = getFilesToBuild(getConfiguredTarget("//package:gen_opl")); + assertThat(Artifact.toRootRelativePaths(filesToBuild)) + .containsAllOf( + "package/libgen_opl.a", + "package/_generated_protos/gen_opl/package/FileAGenfile.pb.h", + "package/_generated_protos/gen_opl/package/FileAGenfile.pb.m"); + } + + @Test + public void testSourceGenerationAction() throws Exception { + Artifact sourceFile = + ActionsTestUtil.getFirstArtifactEndingWith( + getFilesToBuild(getConfiguredTarget("//package:opl")), "/FileA.pb.m"); + SpawnAction action = (SpawnAction) getGeneratingAction(sourceFile); + + Artifact inputFileList = + ActionsTestUtil.getFirstArtifactEndingWith(action.getInputs(), "/_proto_input_files"); + + ImmutableList<String> protoInputs = + ImmutableList.of("dep/file.proto", "package/file_a.proto", "package/dir/file_b.proto"); + + assertThat(action.getArguments()) + .containsExactly( + "/usr/bin/python", + "tools/objc/compile_protos.py", + "--input-file-list", + inputFileList.getExecPathString(), + "--output-dir", + // 2x parent directory because the package has one element ("package") + sourceFile.getExecPath().getParentDirectory().getParentDirectory().toString(), + "--working-dir", ".") + .inOrder(); + assertRequiresDarwin(action); + assertThat(Artifact.toRootRelativePaths(action.getInputs())).containsAllOf( + "tools/objc/compile_protos.py", + "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 testProtobufSourceGenerationAction() 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<String> protoInputs = + ImmutableList.of("dep/file.proto", "package/file_a.proto", "package/dir/file_b.proto"); + + BuildConfiguration topLevelConfig = getAppleCrosstoolConfiguration(); + assertThat(action.getArguments()) + .containsExactly( + "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( + "tools/objc/protobuf_compiler_wrapper.sh", + "tools/objc/protobuf_compiler_helper.py", + "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 testProtobufWithWellKnownTypesProtoListInput() 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<String> protoInputs = ImmutableList.of("package/file_a.proto"); + + assertThat(Artifact.toRootRelativePaths(action.getInputs())).containsAllIn(protoInputs); + assertThat(action.getInputs()).contains(inputFileList); + + assertThat(Artifact.toRootRelativePaths(action.getInputs())) + .contains("objcproto/well_known_type.proto"); + + FileWriteAction inputListAction = (FileWriteAction) getGeneratingAction(inputFileList); + assertThat(inputListAction.getFileContents()).isEqualTo(sortedJoin(protoInputs)); + } + + @Test + public void testUseObjcHeaders() throws Exception { + scratch.file("objcheaderpackage/BUILD", + "objc_proto_library(", + " name = 'opl',", + " deps = ['//package:protolib'],", + " use_objc_header_names = 1,", + ")"); + + Artifact sourceFile = + ActionsTestUtil.getFirstArtifactEndingWith( + getFilesToBuild(getConfiguredTarget("//objcheaderpackage:opl")), "/FileA.pb.m"); + SpawnAction action = (SpawnAction) getGeneratingAction(sourceFile); + + assertThat(action.getArguments()).contains("--use-objc-header-names"); + + NestedSet<Artifact> filesToBuild = + getFilesToBuild(getConfiguredTarget("//objcheaderpackage:opl")); + assertThat(Artifact.toRootRelativePaths(filesToBuild)).containsAllOf( + "objcheaderpackage/_generated_protos/opl/package/FileA.pbobjc.h", + "objcheaderpackage/_generated_protos/opl/package/FileA.pb.m", + "objcheaderpackage/_generated_protos/opl/package/dir/FileB.pbobjc.h", + "objcheaderpackage/_generated_protos/opl/package/dir/FileB.pb.m" + ); + } + + @Test + public void testProtobufCompilationAction() throws Exception { + useConfiguration("--ios_cpu=i386"); + + ConfiguredTarget target = getConfiguredTarget("//package:opl_protobuf"); + Artifact sourceFile = + ActionsTestUtil.getFirstArtifactEndingWith(getFilesToBuild(target), "/FileA.pbobjc.m"); + SpawnAction generateAction = (SpawnAction) getGeneratingAction(sourceFile); + + assertThat(generateAction).isNotNull(); + } + + + @Test + public void testProtobufObjcProviderWithAutoUnionExperiment() 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 testPerProtoIncludes() throws Exception { + ConfiguredTarget target = getConfiguredTarget("//package:opl"); + Artifact headerFile = ActionsTestUtil.getFirstArtifactEndingWith( + getFilesToBuild(target), "/FileA.pb.h"); + + ObjcProvider provider = providerForTarget("//package:opl"); + assertThat(provider.get(ObjcProvider.INCLUDE).toSet()).containsExactly( + // 2x parent directory because the package has one element ("package") + headerFile.getExecPath().getParentDirectory().getParentDirectory() + ); + } + + @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 = [],", + ")"); + } + + // This is a test for deprecated functionality. + @Test + public void testErrorForDepWithFilegroupWithoutProtoFiles() throws Exception { + checkError( + "x", + "x", + ProtoAttributes.NO_PROTOS_ERROR, + "objc_proto_library(", + " name = 'x',", + " deps = [':fg'],", + ")", + "filegroup(", + " name = 'fg',", + " srcs = ['file.dat'],", + ")"); + } + + @Test + public void testWarningForProtoSourceDeps() throws Exception { + checkWarning( + "x", + "x", + ProtoAttributes.FILES_DEPRECATED_WARNING, + "objc_proto_library(", + " name = 'x',", + " deps = ['foo.proto'],", + ")"); + } + + @Test + public void testWarningForFilegroupDeps() throws Exception { + checkWarning( + "x", + "x", + ProtoAttributes.FILES_DEPRECATED_WARNING, + "filegroup(", + " name = 'protos',", + " srcs = ['foo.proto'],", + ")", + "objc_proto_library(", + " name = 'x',", + " deps = [':protos'],", + ")"); + } + + @Test + public void testObjcCopts() throws Exception { + useConfiguration("--objccopt=-foo"); + + List<String> args = compileAction("//package:opl", "FileA.pb.o").getArguments(); + assertThat(args).contains("-foo"); + } + + @Test + public void testObjcCopts_argumentOrdering() throws Exception { + useConfiguration("--objccopt=-foo"); + + List<String> args = compileAction("//package:opl", "FileA.pb.o").getArguments(); + assertThat(args).containsAllOf("-fno-objc-arc", "-foo").inOrder(); + } + + @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 testErrorWhenDependingOnPB2FromProtobufTarget() throws Exception { + checkError( + "x", + "x", + ProtoAttributes.PROTOCOL_BUFFERS2_IN_PROTOBUF_DEPS_ERROR, + "objc_proto_library(", + " name = 'x',", + " portable_proto_filters = ['filter.pbascii'],", + " deps = [':pb2', ':protos_b'],", + ")", + "objc_proto_library(", + " name = 'pb2',", + " deps = [':protos_a'],", + ")", + "proto_library(", + " name = 'protos_a',", + " srcs = ['a.proto'],", + ")", + "proto_library(", + " name = 'protos_b',", + " srcs = ['b.proto'],", + ")"); + } + + @Test + public void testErrorWhenDependingOnPB2FromPB2Target() throws Exception { + checkError( + "x", + "x", + ProtoAttributes.OBJC_PROTO_LIB_DEP_IN_PROTOCOL_BUFFERS2_DEPS_ERROR, + "objc_proto_library(", + " name = 'x',", + " deps = [':pb2', ':protos_b'],", + ")", + "objc_proto_library(", + " name = 'pb2',", + " deps = [':protos_a'],", + ")", + "proto_library(", + " name = 'protos_a',", + " srcs = ['a.proto'],", + ")", + "proto_library(", + " name = 'protos_b',", + " srcs = ['b.proto'],", + ")"); + } + + @Test + public void testErrorForPortableProtoFiltersWithUseObjcHeaderNames() throws Exception { + checkErrorForPortableProtoFilterWithPb2Option("use_objc_header_names = 1"); + } + + @Test + public void testErrorForPortableProtoFiltersWithPerProtoIncludes() throws Exception { + checkErrorForPortableProtoFilterWithPb2Option("per_proto_includes = 1"); + } + + @Test + public void testErrorForPortableProtoFiltersWithOptionsFile() throws Exception { + checkErrorForPortableProtoFilterWithPb2Option("options_file = 'options_file.txt'"); + } + + @Test + public void testErrorForUsesProtobufWithUseObjcHeaderNames() throws Exception { + checkErrorForUsesProtobufWithPb2Option("use_objc_header_names = 1"); + } + + @Test + public void testErrorForUsesProtobufWithPerProtoIncludes() throws Exception { + checkErrorForUsesProtobufWithPb2Option("per_proto_includes = 1"); + } + + @Test + public void testErrorForUsesProtobufWithOptionsFile() throws Exception { + checkErrorForUsesProtobufWithPb2Option("options_file = 'options_file.txt'"); + } + + @Test + public void testModulemapCreatedForNonLinkingTargets() throws Exception { + checkOnlyLibModuleMapsArePresentForTarget("//package:opl_protobuf"); + } + + @Test + public void testModulemapNotCreatedForLinkingTargets() throws Exception { + checkOnlyLibModuleMapsArePresentForTarget("//package:opl_binary"); + } + + @Test + public void testErrorForPortableProtoFilterFilesInDeps() throws Exception { + checkError( + "x", + "x", + ProtoAttributes.FILES_NOT_ALLOWED_ERROR, + "objc_proto_library(", + " name = 'x',", + " portable_proto_filters = ['proto_filter.txt'],", + " deps = [':protos'],", + ")", + "filegroup(", + " name = 'protos',", + " srcs = ['file.proto'],", + ")"); + } + + @Test + public void testErrorForUsesProtobufAsFalseWithFilters() throws Exception { + checkError( + "x", + "x", + ProtoAttributes.USES_PROTOBUF_CANT_BE_SPECIFIED_AS_FALSE, + "objc_proto_library(", + " name = 'x',", + " uses_protobuf = 0,", + " portable_proto_filters = ['myfilter.pbascii'],", + " deps = [':protos'],", + ")", + "proto_library(", + " name = 'protos',", + " srcs = ['file.proto'],", + ")"); + } + + private void checkErrorForPortableProtoFilterWithPb2Option(String pb2Option) throws Exception { + checkError( + "x", + "x", + ProtoAttributes.PROTOBUF_ATTRIBUTES_NOT_EXCLUSIVE_ERROR, + "objc_proto_library(", + " name = 'x',", + " portable_proto_filters = ['proto_filter.txt'],", + " " + pb2Option + ",", + " deps = [':protos'],", + ")", + "proto_library(", + " name = 'protos',", + " srcs = ['file.proto'],", + ")"); + } + + private void checkErrorForUsesProtobufWithPb2Option(String pb2Option) throws Exception { + checkError( + "x", + "x", + ProtoAttributes.PROTOBUF_ATTRIBUTES_NOT_EXCLUSIVE_ERROR, + "objc_proto_library(", + " name = 'x',", + " uses_protobuf = 1,", + " " + pb2Option + ",", + " deps = [':protos'],", + ")", + "proto_library(", + " name = 'protos',", + " srcs = ['file.proto'],", + ")"); + } + + private static String sortedJoin(Iterable<String> elements) { + return Joiner.on('\n').join(Ordering.natural().immutableSortedCopy(elements)); + } + + private void checkOnlyLibModuleMapsArePresentForTarget(String target) throws Exception { + Artifact libModuleMap = + getGenfilesArtifact( + "opl_protobuf.modulemaps/module.modulemap", + getConfiguredTarget("//package:opl_protobuf")); + Artifact protolibModuleMap = + getGenfilesArtifact( + "protobuf_lib.modulemaps/module.modulemap", + getConfiguredTarget("//objcproto:protobuf_lib")); + + ObjcProvider provider = providerForTarget(target); + assertThat(Artifact.toRootRelativePaths(provider.get(ObjcProvider.MODULE_MAP).toSet())) + .containsExactlyElementsIn( + Artifact.toRootRelativePaths(ImmutableSet.of(libModuleMap, protolibModuleMap))); + } + + @Test + public void testObjcProvider() throws Exception { + ConfiguredTarget target = getConfiguredTarget("//package:opl"); + Artifact headerFile = + ActionsTestUtil.getFirstArtifactEndingWith(getFilesToBuild(target), "/FileA.pb.h"); + Artifact sourceFile = + ActionsTestUtil.getFirstArtifactEndingWith(getFilesToBuild(target), "/FileA.pb.m"); + + ObjcProvider provider = providerForTarget("//package:opl"); + assertThat(provider.get(ObjcProvider.INCLUDE).toSet()) + .contains(headerFile.getExecPath().getParentDirectory().getParentDirectory()); + + ConfiguredTarget libProtoBufTarget = getConfiguredTarget("//objcproto:ProtocolBuffers_lib"); + assertThat(provider.get(ObjcProvider.LIBRARY).toSet()) + .containsExactly( + getBinArtifact("libopl.a", target), + getBinArtifact("libProtocolBuffers_lib.a", libProtoBufTarget)); + + assertThat(provider.get(ObjcProvider.HEADER).toSet()).containsAllOf(headerFile, sourceFile); + + assertThat(provider.get(ObjcProvider.INCLUDE).toSet()) + .contains(headerFile.getExecPath().getParentDirectory().getParentDirectory()); + } + + @Test + public void testProtobufObjcProvider() 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()); + + ConfiguredTarget libProtoBufTarget = getConfiguredTarget("//objcproto:protobuf_lib"); + assertThat(provider.get(ObjcProvider.LIBRARY).toSet()) + .containsExactly(getBinArtifact("libprotobuf_lib.a", libProtoBufTarget)); + + assertThat(provider.get(ObjcProvider.HEADER).toSet()).contains(headerFile); + + assertThat(provider.get(ObjcProvider.INCLUDE).toSet()) + .contains(headerFile.getExecPath().getParentDirectory().getParentDirectory()); + } + + @Test + public void testCompilationActionInCoverageMode() throws Exception { + useConfiguration("--collect_code_coverage"); + + ConfiguredTarget target = getConfiguredTarget("//package:opl"); + CommandAction linkAction = + (CommandAction) getGeneratingAction(getBinArtifact("libopl.a", target)); + + CommandAction compileAction = + (CommandAction) + getGeneratingAction( + ActionsTestUtil.getFirstArtifactEndingWith(linkAction.getInputs(), "/FileA.pb.o")); + + assertThat(Artifact.toRootRelativePaths(compileAction.getOutputs())) + .containsAllOf( + "package/_objs/opl/package/_generated_protos/opl/package/FileA.pb.o", + "package/_objs/opl/package/_generated_protos/opl/package/FileA.pb.gcno"); + } + + @Test + public void testModuleMapActionFiltersHeaders() throws Exception { + ConfiguredTarget configuredTarget = getConfiguredTarget("//package:opl"); + Artifact moduleMap = getGenfilesArtifact("opl.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/package/FileA.pb.h", + "package/_generated_protos/opl/package/dir/FileB.pb.h", + "package/_generated_protos/opl/dep/File.pb.h"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcProtoProviderTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcProtoProviderTest.java new file mode 100644 index 0000000000..031a9516bd --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcProtoProviderTest.java @@ -0,0 +1,95 @@ +// 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.collect.ImmutableList; +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.util.BuildViewTestCase; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.collect.nestedset.Order; +import com.google.devtools.build.lib.vfs.PathFragment; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link ObjcProvider}. */ +@RunWith(JUnit4.class) +public class ObjcProtoProviderTest extends BuildViewTestCase { + + @Before + public final void setup() throws Exception { + // Empty target just so artifacts can be created. + scratch.file("x/BUILD", + "objc_library(", + " name = 'x',", + ")"); + } + + @Test + public void emptyProvider() { + ObjcProtoProvider empty = new ObjcProtoProvider.Builder().build(); + assertThat(empty.getProtoGroups()).isEmpty(); + } + + @Test + public void onlyPropagatesProvider() throws Exception { + Artifact foo = getTestArtifact("foo"); + ObjcProtoProvider onlyPropagates = + new ObjcProtoProvider.Builder() + .addProtoGroup(NestedSetBuilder.<Artifact>create(Order.NAIVE_LINK_ORDER, foo)) + .build(); + assertThat(Iterables.concat(onlyPropagates.getProtoGroups())).containsExactly(foo); + } + + @Test + public void propagatesThroughDependers() throws Exception { + Artifact foo = getTestArtifact("foo"); + Artifact bar = getTestArtifact("bar"); + Artifact baz = getTestArtifact("baz"); + Artifact header = getTestArtifact("protobuf"); + PathFragment searchPath = header.getExecPath().getParentDirectory(); + + ObjcProtoProvider base1 = + new ObjcProtoProvider.Builder() + .addProtoGroup(NestedSetBuilder.<Artifact>create(Order.NAIVE_LINK_ORDER, foo)) + .addPortableProtoFilters(NestedSetBuilder.<Artifact>create(Order.STABLE_ORDER, baz)) + .addProtobufHeaders(NestedSetBuilder.<Artifact>create(Order.STABLE_ORDER, header)) + .build(); + + ObjcProtoProvider base2 = + new ObjcProtoProvider.Builder() + .addProtoGroup(NestedSetBuilder.<Artifact>create(Order.NAIVE_LINK_ORDER, bar)) + .addProtobufHeaderSearchPaths( + NestedSetBuilder.<PathFragment>create(Order.LINK_ORDER, searchPath)) + .build(); + + ObjcProtoProvider depender = + new ObjcProtoProvider.Builder().addTransitive(ImmutableList.of(base1, base2)).build(); + assertThat(Iterables.concat(depender.getProtoGroups())).containsExactly(foo, bar); + assertThat(depender.getPortableProtoFilters()).containsExactly(baz); + assertThat(depender.getProtobufHeaders()).containsExactly(header); + assertThat(depender.getProtobufHeaderSearchPaths()).containsExactly(searchPath); + } + + private Artifact getTestArtifact(String name) throws Exception { + ConfiguredTarget target = getConfiguredTarget("//x:x"); + return getBinArtifact(name, target); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcProviderTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcProviderTest.java new file mode 100644 index 0000000000..2b7f3fe6a3 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcProviderTest.java @@ -0,0 +1,159 @@ +// 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 static com.google.common.truth.Truth.assertWithMessage; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.lib.rules.objc.ObjcProvider.Key; +import com.google.devtools.build.lib.syntax.SkylarkNestedSet; +import com.google.devtools.build.lib.vfs.PathFragment; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link ObjcProvider}. */ +@RunWith(JUnit4.class) +public class ObjcProviderTest { + + @Test + public void emptyProvider() { + ObjcProvider empty = new ObjcProvider.Builder().build(); + assertThat(empty.get(ObjcProvider.SDK_DYLIB)).isEmpty(); + } + + @Test + public void onlyPropagatesProvider() { + ObjcProvider onlyPropagates = new ObjcProvider.Builder() + .add(ObjcProvider.SDK_DYLIB, "foo") + .build(); + assertThat(onlyPropagates.get(ObjcProvider.SDK_DYLIB)).containsExactly("foo"); + } + + @Test + public void onlyNonPropagatesProvider() { + ObjcProvider dep = new ObjcProvider.Builder() + .add(ObjcProvider.SDK_DYLIB, "foo") + .build(); + ObjcProvider notPropagates = new ObjcProvider.Builder() + .addTransitiveWithoutPropagating(ImmutableList.of(dep)) + .build(); + assertThat(notPropagates.get(ObjcProvider.SDK_DYLIB)).containsExactly("foo"); + } + + @Test + public void propagatesAndNonPropagatesProvider() { + ObjcProvider dep = new ObjcProvider.Builder() + .add(ObjcProvider.SDK_DYLIB, "foo") + .build(); + ObjcProvider provider = new ObjcProvider.Builder() + .addTransitiveWithoutPropagating(ImmutableList.of(dep)) + .add(ObjcProvider.SDK_DYLIB, "bar") + .build(); + assertThat(provider.get(ObjcProvider.SDK_DYLIB)).containsExactly("foo", "bar").inOrder(); + } + + @Test + public void doesNotPropagate() { + ObjcProvider dep = new ObjcProvider.Builder() + .add(ObjcProvider.SDK_DYLIB, "foo") + .build(); + ObjcProvider provider = new ObjcProvider.Builder() + .addTransitiveWithoutPropagating(ImmutableList.of(dep)) + .add(ObjcProvider.SDK_DYLIB, "bar") + .build(); + ObjcProvider depender = new ObjcProvider.Builder() + .addTransitiveAndPropagate(provider) + .build(); + assertThat(depender.get(ObjcProvider.SDK_DYLIB)).containsExactly("bar"); + } + + @Test + public void strictDependencyDoesNotPropagateMoreThanOneLevel() { + PathFragment strictInclude = PathFragment.create("strict_path"); + PathFragment propagatedInclude = PathFragment.create("propagated_path"); + + ObjcProvider strictDep = + new ObjcProvider.Builder() + .addForDirectDependents(ObjcProvider.INCLUDE, strictInclude) + .build(); + ObjcProvider propagatedDep = + new ObjcProvider.Builder().add(ObjcProvider.INCLUDE, propagatedInclude).build(); + + ObjcProvider provider = + new ObjcProvider.Builder() + .addTransitiveAndPropagate(ImmutableList.of(strictDep, propagatedDep)) + .build(); + ObjcProvider depender = new ObjcProvider.Builder().addTransitiveAndPropagate(provider).build(); + + assertThat(provider.get(ObjcProvider.INCLUDE)) + .containsExactly(strictInclude, propagatedInclude); + assertThat(depender.get(ObjcProvider.INCLUDE)).containsExactly(propagatedInclude); + } + + @Test + public void strictDependencyDoesNotPropagateMoreThanOneLevelOnSkylark() { + PathFragment strictInclude = PathFragment.create("strict_path"); + PathFragment propagatedInclude = PathFragment.create("propagated_path"); + + ObjcProvider strictDep = + new ObjcProvider.Builder() + .addForDirectDependents(ObjcProvider.INCLUDE, strictInclude) + .build(); + ObjcProvider propagatedDep = + new ObjcProvider.Builder().add(ObjcProvider.INCLUDE, propagatedInclude).build(); + + ObjcProvider provider = + new ObjcProvider.Builder() + .addTransitiveAndPropagate(ImmutableList.of(strictDep, propagatedDep)) + .build(); + ObjcProvider depender = new ObjcProvider.Builder().addTransitiveAndPropagate(provider).build(); + + assertThat( + ((SkylarkNestedSet) provider.getValue(ObjcProvider.INCLUDE.getSkylarkKeyName())) + .toCollection()) + .containsExactly(strictInclude.toString(), propagatedInclude.toString()); + assertThat( + ((SkylarkNestedSet) depender.getValue(ObjcProvider.INCLUDE.getSkylarkKeyName())) + .toCollection()) + .containsExactly(propagatedInclude.toString()); + } + + @Test + public void keysExportedToSkylark() throws Exception { + List<Field> keyFields = new ArrayList<>(); + for (Field field : ObjcProvider.class.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers()) && field.getType() == ObjcProvider.Key.class) { + keyFields.add(field); + } + } + ImmutableSet<Key<?>> allRegisteredKeys = ImmutableSet.<Key<?>>builder() + .addAll(ObjcProvider.KEYS_FOR_SKYLARK) + .addAll(ObjcProvider.KEYS_NOT_IN_SKYLARK) + .build(); + + for (Field field : keyFields) { + ObjcProvider.Key<?> key = (Key<?>) field.get(null); + assertWithMessage("Key %s must either be exposed to skylark or explicitly blacklisted", + key.getSkylarkKeyName()).that(allRegisteredKeys).contains(key); + } + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcRuleTestCase.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcRuleTestCase.java new file mode 100644 index 0000000000..2fb8a81a48 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcRuleTestCase.java @@ -0,0 +1,4852 @@ +// 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 static com.google.common.truth.Truth.assertWithMessage; +import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.baseArtifactNames; +import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.getFirstArtifactEndingWith; +import static com.google.devtools.build.lib.rules.objc.LegacyCompilationSupport.AUTOMATIC_SDK_FRAMEWORKS; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.GENERAL_RESOURCE_FILE; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.HEADER; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.INCLUDE; +import static com.google.devtools.build.lib.rules.objc.ObjcProvider.STORYBOARD; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.BundlingRule.FAMILIES_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.BundlingRule.INFOPLIST_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.CLANG; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.CLANG_PLUSPLUS; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.DSYMUTIL; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.LIPO; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.APP_ICON_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.BUNDLE_ID_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.ENTITLEMENTS_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.LAUNCH_IMAGE_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.PROVISIONING_PROFILE_ATTR; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.SRCS_TYPE; +import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.STRIP; +import static com.google.devtools.build.lib.rules.objc.ReleaseBundlingSupport.NO_ASSET_CATALOG_ERROR_FORMAT; +import static org.junit.Assert.fail; + +import com.dd.plist.NSDictionary; +import com.dd.plist.PropertyListParser; +import com.google.common.base.Joiner; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multiset; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandAction; +import com.google.devtools.build.lib.actions.ExecutionInfoSpecifier; +import com.google.devtools.build.lib.actions.ExecutionRequirements; +import com.google.devtools.build.lib.actions.FailAction; +import com.google.devtools.build.lib.actions.util.ActionsTestUtil; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.FileProvider; +import com.google.devtools.build.lib.analysis.FilesToRunProvider; +import com.google.devtools.build.lib.analysis.OutputGroupProvider; +import com.google.devtools.build.lib.analysis.RunfilesSupport; +import com.google.devtools.build.lib.analysis.actions.BinaryFileWriteAction; +import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; +import com.google.devtools.build.lib.analysis.actions.FileWriteAction; +import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.config.BuildOptions; +import com.google.devtools.build.lib.analysis.config.CompilationMode; +import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; +import com.google.devtools.build.lib.analysis.util.ScratchAttributeWriter; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.packages.Attribute.SplitTransition; +import com.google.devtools.build.lib.packages.util.MockJ2ObjcSupport; +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.AppleCommandLineOptions; +import com.google.devtools.build.lib.rules.apple.AppleConfiguration; +import com.google.devtools.build.lib.rules.apple.AppleConfiguration.ConfigurationDistinguisher; +import com.google.devtools.build.lib.rules.apple.AppleToolchain; +import com.google.devtools.build.lib.rules.apple.DottedVersion; +import com.google.devtools.build.lib.rules.apple.Platform; +import com.google.devtools.build.lib.rules.apple.Platform.PlatformType; +import com.google.devtools.build.lib.rules.apple.XcodeVersionProperties; +import com.google.devtools.build.lib.rules.cpp.CppLinkAction; +import com.google.devtools.build.lib.rules.objc.CompilationSupport.ExtraLinkArgs; +import com.google.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.BundleFile; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.Control; +import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.MergeZip; +import com.google.devtools.build.xcode.plmerge.proto.PlMergeProtos; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import org.junit.Before; + +/** + * Superclass for all Obj-C rule tests. + * + * <p>TODO(matvore): split this up into more helper classes, especially the check... methods, which + * are many and not shared by all objc_ rules. + * <p>TODO(matvore): find a more concise way to repeat common tests (in particular, those which + * simply call a check... method) across several rule types. + */ +public abstract class ObjcRuleTestCase extends BuildViewTestCase { + protected static final String MOCK_ACTOOLWRAPPER_PATH = "tools/objc/actoolwrapper"; + protected static final String MOCK_IBTOOLWRAPPER_PATH = "tools/objc/ibtoolwrapper"; + protected static final String MOCK_BUNDLEMERGE_PATH = "tools/objc/bundlemerge"; + protected static final String MOCK_MOMCWRAPPER_PATH = "tools/objc/momcwrapper"; + protected static final String MOCK_SWIFTSTDLIBTOOLWRAPPER_PATH = + "tools/objc/swiftstdlibtoolwrapper"; + protected static final String MOCK_LIBTOOL_PATH = "tools/objc/libtool"; + protected static final String MOCK_XCRUNWRAPPER_PATH = "tools/objc/xcrunwrapper"; + protected static final ImmutableList<String> FASTBUILD_COPTS = + ImmutableList.of("-O0", "-DDEBUG=1"); + + protected static final DottedVersion DEFAULT_IOS_SDK_VERSION = + DottedVersion.fromString(AppleCommandLineOptions.DEFAULT_IOS_SDK_VERSION); + + private String artifactPrefix; + + /** + * Returns the configuration obtained by applying the apple crosstool configuration transtion to + * this {@code BuildViewTestCase}'s target configuration. + */ + protected BuildConfiguration getAppleCrosstoolConfiguration() throws InterruptedException { + return getConfiguration(targetConfig, AppleCrosstoolTransition.APPLE_CROSSTOOL_TRANSITION); + } + + /** Specification of code coverage behavior. */ + public enum CodeCoverageMode { + // No code coverage information. + NONE, + // Code coverage in gcov format. + GCOV, + // Code coverage in llvm-covmap format. + LLVMCOV; + } + + /** + * Returns the bin dir for artifacts built for a given Apple architecture and minimum OS + * version (as set by a configuration transition) and configuration distinguisher but the global + * default for {@code --cpu}. + * + * @param arch the given Apple architecture which artifacts are built under this configuration. + * Note this will likely be different than the value of {@code --cpu}. + * @param configurationDistinguisher the configuration distinguisher used to describe the + * a configuration transition + * @param minOsVersion the minimum os version for which to compile artifacts in the + * configuration + */ + protected String configurationBin( + String arch, ConfigurationDistinguisher configurationDistinguisher, + DottedVersion minOsVersion) { + return configurationDir(arch, configurationDistinguisher, minOsVersion) + "bin/"; + } + + /** + * Returns the genfiles dir for artifacts built for a given Apple architecture and minimum OS + * version (as set by a configuration transition) and configuration distinguisher but the global + * default for {@code --cpu}. + * + * @param arch the given Apple architecture which artifacts are built under this configuration. + * Note this will likely be different than the value of {@code --cpu}. + * @param configurationDistinguisher the configuration distinguisher used to describe the + * a configuration transition + * @param minOsVersion the minimum os version for which to compile artifacts in the + * configuration + */ + protected String configurationGenfiles( + String arch, ConfigurationDistinguisher configurationDistinguisher, + DottedVersion minOsVersion) { + return configurationDir(arch, configurationDistinguisher, minOsVersion) + "genfiles"; + } + + private String configurationDir( + String arch, ConfigurationDistinguisher configurationDistinguisher, + DottedVersion minOsVersion) { + switch (configurationDistinguisher) { + case UNKNOWN: + return String.format("blaze-out/ios_%s-fastbuild/", arch); + case IOS_EXTENSION: // Intentional fall-through. + case IOS_APPLICATION: + case WATCH_OS1_EXTENSION: + case APPLEBIN_IOS: + return String.format("blaze-out/ios-%1$s-min%3$s-%2$s-ios_%1$s-fastbuild/", + arch, configurationDistinguisher.toString().toLowerCase(Locale.US), minOsVersion); + case APPLEBIN_WATCHOS: + return String.format("blaze-out/watchos-%1$s-min%3$s-%2$s-watchos_%1$s-fastbuild/", + arch, configurationDistinguisher.toString().toLowerCase(Locale.US), minOsVersion); + default: + throw new AssertionError(); + } + } + + /** + * Returns the bin dir for artifacts built for a given Apple architecture (as set by a + * configuration transition) and configuration distinguisher but the global default for + * {@code --cpu} and the platform default for minimum OS. + * + * @param arch the given Apple architecture which artifacts are built under this configuration. + * Note this will likely be different than the value of {@code --cpu} + * @param configurationDistinguisher the configuration distinguisher used to describe the + * a configuration transition + */ + protected String configurationBin( + String arch, ConfigurationDistinguisher configurationDistinguisher) { + return configurationBin(arch, configurationDistinguisher, + defaultMinimumOs(configurationDistinguisher)); + } + + /** + * Returns the bin dir for artifacts with the given iOS architecture as set through {@code --cpu} + * and configuration distinguisher, assuming {@code --ios_multi_cpus} isn't set. + */ + protected static String iosConfigurationCcDepsBin( + String arch, ConfigurationDistinguisher configurationDistinguisher) { + switch (configurationDistinguisher) { + case IOS_EXTENSION: + case WATCH_OS1_EXTENSION: + case APPLEBIN_IOS: + return String.format("blaze-out/%s-ios_%s-fastbuild/bin/", + configurationDistinguisher.toString().toLowerCase(Locale.US), arch); + case UNKNOWN: // Intentional fall-through. + case IOS_APPLICATION: + return String.format("blaze-out/ios_%s-fastbuild/bin/", arch); + default: + throw new AssertionError(); + } + } + + /** + * Returns the default minimum os version that dependencies under a given configuration + * distinguisher (and thus a given platform type) will be compiled for. + */ + protected static DottedVersion defaultMinimumOs( + ConfigurationDistinguisher configurationDistinguisher) { + switch (configurationDistinguisher) { + case UNKNOWN: + case IOS_EXTENSION: + return IosExtension.EXTENSION_MINIMUM_OS_VERSION; + case IOS_APPLICATION: + case APPLEBIN_IOS: + return DEFAULT_IOS_SDK_VERSION; + case WATCH_OS1_EXTENSION: + return WatchUtils.MINIMUM_OS_VERSION; + case APPLEBIN_WATCHOS: + return DottedVersion.fromString(XcodeVersionProperties.DEFAULT_WATCHOS_SDK_VERSION); + default: + throw new AssertionError(); + } + } + + /** + * Returns the genfiles dir for iOS builds in the root architecture. + */ + protected static String rootConfigurationGenfiles() { + return "blaze-out/gcc-4.4.0-glibc-2.3.6-grte-k8-fastbuild/genfiles/"; + } + + protected String execPathEndingWith(Iterable<Artifact> artifacts, String suffix) { + return getFirstArtifactEndingWith(artifacts, suffix).getExecPathString(); + } + + @Before + public final void initializeMockToolsConfig() throws Exception { + MockObjcSupport.setup(mockToolsConfig); + MockProtoSupport.setup(mockToolsConfig); + MockObjcSupport.setupObjcProto(mockToolsConfig); + } + + protected static String frameworkDir(ConfiguredTarget target) { + AppleConfiguration configuration = + target.getConfiguration().getFragment(AppleConfiguration.class); + return frameworkDir(configuration.getSingleArchPlatform()); + } + + protected static String frameworkDir(Platform platform) { + return AppleToolchain.platformDir( + platform.getNameInPlist()) + AppleToolchain.DEVELOPER_FRAMEWORK_PATH; + } + + /** + * Creates an {@code objc_library} target writer for the label indicated by the given String. + */ + protected ScratchAttributeWriter createLibraryTargetWriter(String labelString) { + return ScratchAttributeWriter.fromLabelString(this, "objc_library", labelString); + } + + /** + * Creates an {@code objc_binary} target writer for the label indicated by the given String. + */ + protected ScratchAttributeWriter createBinaryTargetWriter(String labelString) { + return ScratchAttributeWriter.fromLabelString(this, "objc_binary", labelString); + } + + private static String compilationModeFlag(CompilationMode mode) { + switch (mode) { + case DBG: + return "dbg"; + case OPT: + return "opt"; + case FASTBUILD: + return "fastbuild"; + } + throw new AssertionError(); + } + + private static List<String> compilationModeCopts(CompilationMode mode) { + switch (mode) { + case DBG: + return ImmutableList.<String>builder() + .addAll(ObjcConfiguration.DBG_COPTS) + .addAll(ObjcConfiguration.GLIBCXX_DBG_COPTS) + .build(); + case OPT: + return ObjcConfiguration.OPT_COPTS; + case FASTBUILD: + return FASTBUILD_COPTS; + } + throw new AssertionError(); + } + + protected static String listAttribute(String name, Iterable<String> values) { + StringBuilder result = new StringBuilder(); + for (String value : values) { + if (result.length() == 0) { + result.append(name).append(" = ["); + } + result.append(String.format("'%s',", value)); + } + if (result.length() != 0) { + result.append("],"); + } + return result.toString(); + } + + /** Returns the treatment of the crosstool for this test case. */ + protected ObjcCrosstoolMode getObjcCrosstoolMode() { + return ObjcCrosstoolMode.ALL; + } + + @Override + protected void useConfiguration(String... args) throws Exception { + // By default, objc tests assume the case of --experimental_objc_crosstool=all. The "Legacy" + // subclasses explicitly override to test --experimental_objc_crosstool=off. + useConfiguration(getObjcCrosstoolMode(), args); + } + + protected void useConfiguration(ObjcCrosstoolMode objcCrosstoolMode, String... args) + throws Exception { + ImmutableList.Builder<String> extraArgsBuilder = ImmutableList.builder(); + if (objcCrosstoolMode != ObjcCrosstoolMode.OFF) { + String crosstoolModeFlag = + objcCrosstoolMode == ObjcCrosstoolMode.ALL + ? "--experimental_objc_crosstool=all" + : "--experimental_objc_crosstool=library"; + extraArgsBuilder.add(crosstoolModeFlag); + } + extraArgsBuilder + .add("--experimental_disable_go") + .add("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + + ImmutableList<String> extraArgs = extraArgsBuilder.build(); + args = Arrays.copyOf(args, args.length + extraArgs.size()); + for (int i = 0; i < extraArgs.size(); i++) { + args[(args.length - extraArgs.size()) + i] = extraArgs.get(i); + } + + super.useConfiguration(args); + } + + /** + * @param extraAttributes individual strings which contain a whole attribute to be added to the + * generated target, e.g. "deps = ['foo']" + */ + protected ConfiguredTarget addBinaryBasedTarget( + String ruleType, + String packageName, + String targetName, + List<String> srcs, + List<String> deps, + String... extraAttributes) + throws Exception { + for (String source : srcs) { + scratch.file(String.format("%s/%s", packageName, source)); + } + scratch.file(String.format("%s/BUILD", packageName), + ruleType + "(name = '" + targetName + "',", + listAttribute("srcs", srcs), + listAttribute("deps", deps), + Joiner.on(",\n").join(extraAttributes), + ")"); + return getConfiguredTarget(String.format("//%s:%s", packageName, targetName)); + } + + /** + * @param extraAttributes individual strings which contain a whole attribute to be added to the + * generated target, e.g. "deps = ['foo']" + */ + protected ConfiguredTarget addSimpleIosTest( + String packageName, + String targetName, + List<String> srcs, + List<String> deps, + String... extraAttributes) + throws Exception { + return addBinaryBasedTarget( + "ios_test", packageName, targetName, srcs, deps, extraAttributes); + } + + /** + * Returns the arguments to pass to clang for specifying module map artifact location and + * module name. + * + * @param packagePath the path to the package this target is in + * @param targetName the name of the target + */ + protected List<String> moduleMapArtifactArguments(String packagePath, String targetName) { + Artifact moduleMapArtifact = + getGenfilesArtifact( + targetName + ".modulemaps/module.modulemap", packagePath + ":" + targetName); + String moduleName = packagePath.replace("//", "").replace("/", "_") + "_" + targetName; + + return ImmutableList.of("-iquote", + moduleMapArtifact.getExecPath().getParentDirectory().toString(), + "-fmodule-name=" + moduleName); + } + + /** + * Returns all child configurations resulting from a given split transition on a given + * configuration. + */ + protected List<BuildConfiguration> getSplitConfigurations(BuildConfiguration configuration, + SplitTransition<BuildOptions> splitTransition) throws InterruptedException { + ImmutableList.Builder<BuildConfiguration> splitConfigs = ImmutableList.builder(); + + for (BuildOptions splitOptions : splitTransition.split(configuration.getOptions())) { + splitConfigs.add(getSkyframeExecutor().getConfigurationForTesting( + reporter, configuration.fragmentClasses(), splitOptions)); + } + + return splitConfigs.build(); + } + + protected void checkLinkActionCorrect(RuleType ruleType, ExtraLinkArgs extraLinkArgs) + throws Exception { + useConfiguration( + "--cpu=ios_i386", + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go"); + + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + + scratch.file("x/a.m"); + ruleType.scratchTarget(scratch, + "srcs", "['a.m']", "deps", "['//lib1:lib1', '//lib2:lib2']"); + CommandAction action = linkAction("//x:x"); + assertRequiresDarwin(action); + assertThat(Artifact.toRootRelativePaths(action.getInputs())) + .containsAllOf("x/libx.a", "lib1/liblib1.a", "lib2/liblib2.a", "x/x-linker.objlist"); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("x/x_bin"); + + verifyLinkAction(Iterables.getOnlyElement(action.getOutputs()), + getBinArtifact("x-linker.objlist", "//x:x"), "i386", + ImmutableList.of("libx.a", "liblib1.a", "liblib2.a"), ImmutableList.<PathFragment>of(), + extraLinkArgs); + } + + // Regression test for b/29094356. + protected void checkLinkActionDuplicateInputs(RuleType ruleType, ExtraLinkArgs extraLinkArgs) + throws Exception { + useConfiguration( + "--experimental_disable_go", + "--experimental_disable_jvm", + "--cpu=ios_i386", + "--crosstool_top=//tools/osx/crosstool:crosstool"); + + scratch.file("lib/BUILD", + "cc_library(", + " name = 'cclib',", + " srcs = ['dep.c'],", + " deps = ['//lib2:lib2'],", + ")"); + + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + + ruleType.scratchTarget( + scratch, "srcs", "['a.m']", "deps", "['//lib:cclib', '//lib1:lib1', '//lib2:lib2']"); + CommandAction action = linkAction("//x:x"); + assertRequiresDarwin(action); + + verifyObjlist(action, "x-linker.objlist", + execPathEndingWith(action.getInputs(), "x/libx.a"), + execPathEndingWith(action.getInputs(), "lib2/liblib2.a"), + execPathEndingWith(action.getInputs(), "lib1/liblib1.a"), + execPathEndingWith(action.getInputs(), "lib/libcclib.a")); + } + + /** + * Verifies a {@code -filelist} file's contents. + * + * @param originalAction the action which uses the filelist artifact + * @param objlistName the path suffix of the filelist artifact + * @param inputArchives path suffixes of the expected contents of the filelist + */ + protected void verifyObjlist(Action originalAction, String objlistName, + String... inputArchives) { + Artifact filelistArtifact = + getFirstArtifactEndingWith(originalAction.getInputs(), objlistName); + + ParameterFileWriteAction fileWriteAction = + (ParameterFileWriteAction) getGeneratingAction(filelistArtifact); + ImmutableList.Builder<String> execPaths = ImmutableList.builder(); + for (String inputArchive : inputArchives) { + execPaths.add(execPathEndingWith(originalAction.getInputs(), inputArchive)); + } + + assertThat(fileWriteAction.getContents()).containsExactlyElementsIn(execPaths.build()); + } + + /** + * Verifies a link action is registered correctly. + * + * @param binArtifact the output artifact which a link action should be registered to generate + * @param filelistArtifact the input filelist artifact + * @param arch the architecture (for example, "i386") which the binary is to be created for + * @param inputArchives the suffixes (basenames or relative paths with basenames) of the input + * archive files for the link action + * @param importedFrameworks custom framework path fragments + * @param extraLinkArgs extra link arguments expected on the link action + */ + protected void verifyLinkAction(Artifact binArtifact, Artifact filelistArtifact, String arch, + List<String> inputArchives, List<PathFragment> importedFrameworks, + ExtraLinkArgs extraLinkArgs) { + final CommandAction binAction = (CommandAction) getGeneratingAction(binArtifact); + + for (String inputArchive : inputArchives) { + // Verify each input archive is present in the action inputs. + getFirstArtifactEndingWith(binAction.getInputs(), inputArchive); + } + ImmutableList.Builder<String> frameworkPathFragmentParents = ImmutableList.builder(); + ImmutableList.Builder<String> frameworkPathBaseNames = ImmutableList.builder(); + for (PathFragment importedFramework : importedFrameworks) { + frameworkPathFragmentParents.add(importedFramework.getParentDirectory().toString()); + frameworkPathBaseNames.add(importedFramework.getBaseName()); + } + + ImmutableList<String> expectedCommandLineFragments = ImmutableList.<String>builder() + .add("-mios-simulator-version-min=" + DEFAULT_IOS_SDK_VERSION) + .add("-arch " + arch) + .add("-isysroot " + AppleToolchain.sdkDir()) + .add(AppleToolchain.sdkDir() + AppleToolchain.DEVELOPER_FRAMEWORK_PATH) + .add(frameworkDir(Platform.forTarget(PlatformType.IOS, arch))) + .addAll(frameworkPathFragmentParents.build()) + .add("-Xlinker -objc_abi_version -Xlinker 2") + .add("-Xlinker -rpath -Xlinker @executable_path/Frameworks") + .add("-fobjc-link-runtime") + .add("-ObjC") + .addAll( + Interspersing.beforeEach( + "-framework", SdkFramework.names(AUTOMATIC_SDK_FRAMEWORKS))) + .addAll( + Interspersing.beforeEach( + "-framework", frameworkPathBaseNames.build())) + .add("-filelist") + .add(filelistArtifact.getExecPathString()) + .add("-o") + .addAll(Artifact.toExecPaths(binAction.getOutputs())) + .addAll(extraLinkArgs) + .build(); + + String linkArgs = Joiner.on(" ").join(binAction.getArguments()); + for (String expectedCommandLineFragment : expectedCommandLineFragments) { + assertThat(linkArgs).contains(expectedCommandLineFragment); + } + } + + protected void checkLinkActionWithTransitiveCppDependency( + RuleType ruleType, ExtraLinkArgs extraLinkArgs) throws Exception { + + createLibraryTargetWriter("//lib1:lib1").setAndCreateFiles("srcs", "a.mm").write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("deps", "//lib1") + .write(); + + scratch.file("x/c.m"); + ruleType.scratchTarget(scratch, "srcs", "['c.m']", "deps", "['//lib2:lib2']"); + + CommandAction action = linkAction("//x:x"); + assertThat(action.getArguments().get(2)) + .startsWith( + MOCK_XCRUNWRAPPER_PATH + " " + CLANG_PLUSPLUS + " -stdlib=libc++ -std=gnu++11"); + } + + protected Map<String, String> mobileProvisionProfiles(BundleMergeProtos.Control control) { + Map<String, String> profiles = new HashMap<>(); + for (BundleFile bundleFile : control.getBundleFileList()) { + if (bundleFile.getBundlePath() + .endsWith(ReleaseBundlingSupport.PROVISIONING_PROFILE_BUNDLE_FILE)) { + assertWithMessage("Should not have multiple entries for same source file") + .that(profiles.put(bundleFile.getSourceFile(), bundleFile.getBundlePath())) + .isNull(); + } + } + return profiles; + } + + protected void checkFilesToRun(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + FilesToRunProvider filesToRun = target.getProvider(FilesToRunProvider.class); + assertThat(filesToRun.getExecutable().getRootRelativePathString()) + .isEqualTo("x/x_runner.sh"); + RunfilesSupport runfilesSupport = filesToRun.getRunfilesSupport(); + assertThat(Artifact.toRootRelativePaths(runfilesSupport.getRunfiles().getArtifacts())) + .containsExactly( + "x/x.ipa", + "x/x_runner.sh", + "tools/objc/StdRedirect.dylib"); + } + + protected void assertAppleSdkVersionEnv(Map<String, String> env) throws Exception { + assertAppleSdkVersionEnv(env, DEFAULT_IOS_SDK_VERSION); + } + + protected void assertAppleSdkVersionEnv(Map<String, String> env, DottedVersion versionNumber) + throws Exception { + assertThat(env).containsEntry("APPLE_SDK_VERSION_OVERRIDE", versionNumber.toString()); + } + + protected void assertAppleSdkPlatformEnv( + Map<String, String> env, String platformName) throws Exception { + assertThat(env).containsEntry("APPLE_SDK_PLATFORM", platformName); + } + + protected void assertAppleSdkVersionEnv(CommandAction action) throws Exception { + assertAppleSdkVersionEnv(action, DEFAULT_IOS_SDK_VERSION.toString()); + } + + protected void assertAppleSdkVersionEnv(CommandAction action, String versionString) + throws Exception { + assertThat(action.getEnvironment()) + .containsEntry("APPLE_SDK_VERSION_OVERRIDE", versionString); + } + + protected void assertAppleSdkPlatformEnv(CommandAction action, String platformName) + throws Exception { + assertThat(action.getEnvironment()).containsEntry("APPLE_SDK_PLATFORM", platformName); + } + + protected void assertXcodeVersionEnv(CommandAction action, String versionNumber) + throws Exception { + assertThat(action.getEnvironment()).containsEntry("XCODE_VERSION_OVERRIDE", versionNumber); + } + + protected void checkNoRunfilesSupportForDevice(RuleType ruleType) throws Exception { + useConfiguration("--cpu=ios_armv7"); + ruleType.scratchTarget(scratch); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + FilesToRunProvider filesToRun = target.getProvider(FilesToRunProvider.class); + assertThat(filesToRun.getRunfilesSupport()).isNull(); + } + + protected void checkGenerateRunnerScriptAction(RuleType ruleType) throws Exception { + useConfiguration( + "--cpu=ios_i386", "--ios_simulator_device=iPhone X", "--ios_simulator_version=3"); + + ruleType.scratchTarget(scratch); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + Artifact runnerScript = getBinArtifact("x_runner.sh", target); + TemplateExpansionAction action = (TemplateExpansionAction) getGeneratingAction(runnerScript); + assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())) + .containsExactly("ios_runner.sh.mac_template"); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("x/x_runner.sh"); + assertThat(action.getSubstitutions()) + .containsExactly( + Substitution.of("%ipa_file%", "x/x.ipa"), + Substitution.of("%sim_device%", "'iPhone X'"), + Substitution.of("%sdk_version%", "3"), + Substitution.of("%app_name%", "x"), + Substitution.of("%std_redirect_dylib_path%", "tools/objc/StdRedirect.dylib")); + } + + protected void checkGenerateRunnerScriptAction_escaped(RuleType ruleType) throws Exception { + useConfiguration("--cpu=ios_i386", "--ios_simulator_device=iPhone X'"); + + ruleType.scratchTarget(scratch); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + Artifact runnerScript = getBinArtifact("x_runner.sh", target); + TemplateExpansionAction action = (TemplateExpansionAction) getGeneratingAction(runnerScript); + assertThat(action.getSubstitutions()) + .contains(Substitution.of("%sim_device%", "'iPhone X'\\'''")); + } + + protected void checkDeviceSigningAction(RuleType ruleType) throws Exception { + useConfiguration("--cpu=ios_armv7"); + + scratch.file("x/entitlements.entitlements"); + ruleType.scratchTarget(scratch, ENTITLEMENTS_ATTR, "'entitlements.entitlements'"); + SpawnAction action = (SpawnAction) ipaGeneratingAction(); + assertRequiresDarwin(action); + assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())) + .containsExactly("x.entitlements", "foo.mobileprovision", "x.unprocessed.ipa"); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())).containsExactly("x/x.ipa"); + } + + protected void checkSigningWithCertName(RuleType ruleType) throws Exception { + useConfiguration("--cpu=ios_armv7", "--ios_signing_cert_name=Foo Bar"); + + scratch.file("x/entitlements.entitlements"); + ruleType.scratchTarget(scratch, ENTITLEMENTS_ATTR, "'entitlements.entitlements'"); + SpawnAction action = (SpawnAction) ipaGeneratingAction(); + assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())) + .containsExactly("x.entitlements", "foo.mobileprovision", "x.unprocessed.ipa"); + assertThat(Joiner.on(' ').join(action.getArguments())).contains("--sign \"Foo Bar\""); + + assertThat(Artifact.toRootRelativePaths(action.getOutputs())).containsExactly("x/x.ipa"); + } + + protected void checkPostProcessingAction(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, "ipa_post_processor", "'tool.sh'"); + + SpawnAction action = (SpawnAction) ipaGeneratingAction(); + assertRequiresDarwin(action); + assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())) + .containsExactly("tool.sh", "x.unprocessed.ipa"); + + assertThat(Joiner.on(' ').join(action.getArguments())).contains("x/tool.sh ${t}"); + + assertThat(Artifact.toRootRelativePaths(action.getOutputs())).containsExactly("x/x.ipa"); + } + + protected void checkSigningAndPostProcessing(RuleType ruleType) throws Exception { + useConfiguration("--cpu=ios_armv7"); + ruleType.scratchTarget( + scratch, "ipa_post_processor", "'tool.sh'", ENTITLEMENTS_ATTR, + "'entitlements.entitlements'"); + + SpawnAction action = (SpawnAction) ipaGeneratingAction(); + assertRequiresDarwin(action); + assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())) + .containsExactly("tool.sh", "x.entitlements", "foo.mobileprovision", "x.unprocessed.ipa"); + + assertThat(normalizeBashArgs(action.getArguments())) + .containsAllOf("x/tool.sh", "--sign") + .inOrder(); + } + + protected void checkNoEntitlementsDefined(RuleType ruleType) throws Exception { + useConfiguration("--cpu=ios_armv7", "--nodevice_debug_entitlements"); + + ruleType.scratchTarget(scratch); + + SpawnAction ipaAction = (SpawnAction) ipaGeneratingAction(); + Artifact entitlements = getFirstArtifactEndingWith(ipaAction.getInputs(), ".entitlements"); + SpawnAction substitutionAction = (SpawnAction) getGeneratingAction(entitlements); + assertThat(Joiner.on(' ').join(substitutionAction.getArguments())).contains("sed"); + + Artifact prefix = + getFirstArtifactEndingWith(substitutionAction.getInputs(), ".team_prefix_file"); + SpawnAction prefixAction = (SpawnAction) getGeneratingAction(prefix); + assertThat(baseArtifactNames(prefixAction.getInputs())).containsExactly("foo.mobileprovision"); + assertThat(Joiner.on(' ').join(prefixAction.getArguments())) + .contains("Print ApplicationIdentifierPrefix:0"); + + Artifact extractedEntitlements = + getFirstArtifactEndingWith(substitutionAction.getInputs(), ".entitlements_with_variables"); + SpawnAction extractionAction = (SpawnAction) getGeneratingAction(extractedEntitlements); + assertThat(baseArtifactNames(extractionAction.getInputs())) + .containsExactly("foo.mobileprovision"); + assertThat(Joiner.on(' ').join(extractionAction.getArguments())).contains("Print Entitlements"); + } + + protected void checkEntitlementsDefined(RuleType ruleType) throws Exception { + useConfiguration("--cpu=ios_armv7", "--nodevice_debug_entitlements"); + + ruleType.scratchTarget(scratch, ENTITLEMENTS_ATTR, "'bar.entitlements'"); + + SpawnAction ipaAction = (SpawnAction) ipaGeneratingAction(); + Artifact entitlements = getFirstArtifactEndingWith(ipaAction.getInputs(), ".entitlements"); + SpawnAction substitutionAction = (SpawnAction) getGeneratingAction(entitlements); + + Artifact prefix = + getFirstArtifactEndingWith(substitutionAction.getInputs(), ".team_prefix_file"); + SpawnAction prefixAction = (SpawnAction) getGeneratingAction(prefix); + assertThat(prefixAction).isNotNull(); + + assertThat(Artifact.toExecPaths(substitutionAction.getInputs())).contains("x/bar.entitlements"); + assertThat( + getFirstArtifactEndingWith( + substitutionAction.getInputs(), ".entitlements_with_variables")) + .isNull(); + } + + protected void checkExtraEntitlements(RuleType ruleType) throws Exception { + useConfiguration("--cpu=ios_armv7", "--extra_entitlements=//foo:extra.entitlements"); + + ruleType.scratchTarget(scratch); + + scratch.file("foo/extra.entitlements"); + scratch.file("foo/BUILD", "exports_files(['extra.entitlements'])"); + + SpawnAction ipaAction = (SpawnAction) ipaGeneratingAction(); + Artifact entitlements = getFirstArtifactEndingWith(ipaAction.getInputs(), ".entitlements"); + SpawnAction mergeAction = (SpawnAction) getGeneratingAction(entitlements); + + assertThat(Artifact.toExecPaths(mergeAction.getInputs())).contains("foo/extra.entitlements"); + + Artifact mergeControl = + getFirstArtifactEndingWith(mergeAction.getInputs(), ".merge-entitlements-control"); + BinaryFileWriteAction mergeControleAction = + (BinaryFileWriteAction) getGeneratingAction(mergeControl); + + PlMergeProtos.Control mergeControlProto; + try (InputStream in = mergeControleAction.getSource().openStream()) { + mergeControlProto = PlMergeProtos.Control.parseFrom(in); + } + + assertThat(mergeControlProto.getSourceFileList()).contains("foo/extra.entitlements"); + } + + protected void checkFastbuildDebugEntitlements(RuleType ruleType) throws Exception { + useConfiguration("--cpu=ios_armv7"); + assertDebugEntitlements(ruleType); + } + + protected void checkDebugEntitlements(RuleType ruleType) throws Exception { + useConfiguration("--cpu=ios_armv7", "--compilation_mode=dbg"); + assertDebugEntitlements(ruleType); + } + + protected void checkOptNoDebugEntitlements(RuleType ruleType) throws Exception { + useConfiguration("--cpu=ios_armv7", "--compilation_mode=opt"); + assertNoDebugEntitlements(ruleType); + } + + protected void checkExplicitNoDebugEntitlements(RuleType ruleType) throws Exception { + useConfiguration("--cpu=ios_armv7", "--nodevice_debug_entitlements"); + assertNoDebugEntitlements(ruleType); + } + + private void assertDebugEntitlements(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch); + + SpawnAction ipaAction = (SpawnAction) ipaGeneratingAction(); + Artifact entitlements = getFirstArtifactEndingWith(ipaAction.getInputs(), ".entitlements"); + SpawnAction mergeAction = (SpawnAction) getGeneratingAction(entitlements); + + assertThat(Artifact.toExecPaths(mergeAction.getInputs())) + .contains("tools/objc/device_debug_entitlements.plist"); + + Artifact mergeControl = + getFirstArtifactEndingWith(mergeAction.getInputs(), ".merge-entitlements-control"); + BinaryFileWriteAction mergeControleAction = + (BinaryFileWriteAction) getGeneratingAction(mergeControl); + + PlMergeProtos.Control mergeControlProto; + try (InputStream in = mergeControleAction.getSource().openStream()) { + mergeControlProto = PlMergeProtos.Control.parseFrom(in); + } + + assertThat(mergeControlProto.getSourceFileList()) + .contains("tools/objc/device_debug_entitlements.plist"); + } + + private void assertNoDebugEntitlements(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch); + + SpawnAction ipaAction = (SpawnAction) ipaGeneratingAction(); + Artifact entitlements = getFirstArtifactEndingWith(ipaAction.getInputs(), ".entitlements"); + SpawnAction entitlementsAction = (SpawnAction) getGeneratingAction(entitlements); + + assertThat(Artifact.toExecPaths(entitlementsAction.getInputs())) + .doesNotContain("tools/objc/device_debug_entitlements.plist"); + } + + protected void checkCompilesSources(RuleType ruleType) throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .setList("deps", "//lib1:lib1") + .write(); + scratch.file("x/a.m"); + scratch.file("x/b.m"); + scratch.file("x/a.h"); + scratch.file("x/private.h"); + ruleType.scratchTarget(scratch, "srcs", "['a.m', 'b.m', 'private.h']", "hdrs", "['a.h']", + "deps", "['//lib2:lib2']"); + CommandAction compileA = compileAction("//x:x", "a.o"); + + assertThat(Artifact.toRootRelativePaths(compileA.getInputs())) + .containsExactly("x/a.m", "x/a.h", "x/private.h", "lib1/hdr.h", "lib2/hdr.h", + MOCK_XCRUNWRAPPER_PATH); + assertThat(Artifact.toRootRelativePaths(compileA.getOutputs())) + .containsExactly("x/_objs/x/x/a.o", "x/_objs/x/x/a.d"); + } + + protected void checkCompilesSourcesWithModuleMapsEnabled(RuleType ruleType) throws Exception { + useConfiguration("--experimental_objc_enable_module_maps"); + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .setList("deps", "//lib1:lib1") + .write(); + + ruleType.scratchTarget( + scratch, "srcs", "['a.m', 'b.m']", "hdrs", "['a.h']", "deps", "['//lib2:lib2']"); + CommandAction compileA = compileAction("//x:x", "a.o"); + + assertThat(Artifact.toRootRelativePaths(compileA.getInputs())) + .containsAllOf( + "lib1/lib1.modulemaps/module.modulemap", + "lib2/lib2.modulemaps/module.modulemap", + "x/x.modulemaps/module.modulemap"); + } + + protected void checkCompileWithDotMFileInHeaders(RuleType ruleType) throws Exception { + scratch.file("bin/a.m"); + scratch.file("bin/b.m"); + scratch.file("bin/h.m"); + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + scratch.file("x/a.m"); + ruleType.scratchTarget(scratch, + "srcs", "['a.m', 'b.m']", + "hdrs", "['h.m']", + "deps", "['//lib1:lib1', '//lib2:lib2']"); + Action linkAction = linkAction("//x:x"); + Artifact libBin = getFirstArtifactEndingWith(linkAction.getInputs(), "libx.a"); + Action linkBinAFile = getGeneratingAction(libBin); + Artifact aObjFile = getFirstArtifactEndingWith(linkBinAFile.getInputs(), "a.o"); + CommandAction compileA = (CommandAction) getGeneratingAction(aObjFile); + + assertThat(compileA.getArguments()).contains("x/a.m"); + assertThat(compileA.getArguments()).doesNotContain("x/h.m"); + assertThat(getFirstArtifactEndingWith(linkBinAFile.getInputs(), "h.o")).isNull(); + } + + protected void checkCompileWithTextualHeaders(RuleType ruleType) throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + ruleType.scratchTarget(scratch, + "srcs", "['a.m']", + "textual_hdrs", "['t.h']", + "deps", "['//lib1:lib1', '//lib2:lib2']"); + CommandAction compileA = compileAction("//x:x", "a.o"); + + assertThat(Artifact.toRootRelativePaths(compileA.getInputs())) + .containsAllOf("x/a.m", "x/t.h", "lib1/hdr.h", "lib2/hdr.h"); + } + + protected void checkLinksFrameworksOfSelfAndTransitiveDependencies(RuleType ruleType) + throws Exception { + createLibraryTargetWriter("//base_lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("sdk_frameworks", "foo") + .write(); + scratch.file("x/a.m"); + ruleType.scratchTarget(scratch, + "srcs", "['a.m']", + "deps", "['//base_lib:lib']", + "sdk_frameworks", "['bar']"); + + assertThat(Joiner.on(" ").join(linkAction("//x:x").getArguments())) + .contains("-framework foo -framework bar"); + } + + protected void checkLinksWeakFrameworksOfSelfAndTransitiveDependencies(RuleType ruleType) + throws Exception { + createLibraryTargetWriter("//base_lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("weak_sdk_frameworks", "foo") + .write(); + + ruleType.scratchTarget( + scratch, "srcs", "['a.m']", "deps", "['//base_lib:lib']", "weak_sdk_frameworks", "['bar']"); + + assertThat(Joiner.on(" ").join(linkAction("//x:x").getArguments())) + .contains("-weak_framework foo -weak_framework bar"); + } + + protected void checkLinkWithFrameworkImportsIncludesFlagsAndInputArtifacts(RuleType ruleType) + throws Exception { + ConfiguredTarget lib = addLibWithDepOnFrameworkImport(); + scratch.file("x/a.m"); + ruleType.scratchTarget(scratch, + "srcs", "['a.m']", + "deps", "['" + lib.getLabel() + "']"); + + CommandAction linkAction = linkAction("//x:x"); + String linkActionArgs = Joiner.on(" ").join(linkAction.getArguments()); + assertThat(linkActionArgs).contains("-framework fx1 -framework fx2"); + assertThat(linkActionArgs).contains("-F fx"); + assertThat(linkAction.getInputs()).containsAllOf( + getSourceArtifact("fx/fx1.framework/a"), + getSourceArtifact("fx/fx1.framework/b"), + getSourceArtifact("fx/fx2.framework/c"), + getSourceArtifact("fx/fx2.framework/d")); + } + + protected void checkLinkIncludeOrderFrameworksAndSystemLibsFirst(RuleType ruleType) + throws Exception { + useConfiguration("--noobjc_includes_prioritize_static_libs"); + scratch.file("fx/fx1.framework"); + scratch.file("fx/BUILD", "objc_framework(name = 'fx')"); + scratch.file("x/a.m"); + ruleType.scratchTarget( + scratch, "srcs", "['a.m']", "sdk_frameworks", "['fx']", "sdk_dylibs", "['libdy1']"); + + CommandAction linkAction = linkAction("//x:x"); + String linkActionArgs = Joiner.on(" ").join(linkAction.getArguments()); + + assertThat(linkActionArgs.indexOf("-F")).isLessThan(linkActionArgs.indexOf("-filelist")); + assertThat(linkActionArgs.indexOf("-l")).isLessThan(linkActionArgs.indexOf("-filelist")); + } + + protected void checkLinkIncludeOrderStaticLibsFirst(RuleType ruleType) throws Exception { + scratch.file("fx/fx1.framework"); + scratch.file("fx/BUILD", "objc_framework(name = 'fx')"); + scratch.file("x/a.m"); + ruleType.scratchTarget( + scratch, "srcs", "['a.m']", "sdk_frameworks", "['fx']", "sdk_dylibs", "['libdy1']"); + + CommandAction linkAction = linkAction("//x:x"); + String linkActionArgs = Joiner.on(" ").join(linkAction.getArguments()); + + assertThat(linkActionArgs.indexOf(".a")).isLessThan(linkActionArgs.indexOf("-F")); + assertThat(linkActionArgs.indexOf(".a")).isLessThan(linkActionArgs.indexOf("-l")); + } + + protected ObjcProvider providerForTarget(String label) throws Exception { + return getConfiguredTarget(label).getProvider(ObjcProvider.class); + } + + protected CommandAction archiveAction(String label) throws Exception { + ConfiguredTarget target = getConfiguredTarget(label); + return (CommandAction) + getGeneratingAction(getBinArtifact("lib" + target.getLabel().getName() + ".a", target)); + } + + protected Iterable<Artifact> inputsEndingWith(Action action, final String suffix) { + return Iterables.filter(action.getInputs(), new Predicate<Artifact>() { + @Override + public boolean apply(Artifact artifact) { + return artifact.getExecPathString().endsWith(suffix); + } + }); + } + + /** + * Asserts that the given action can specify execution requirements, and requires execution on + * darwin. + */ + protected void assertRequiresDarwin(ExecutionInfoSpecifier action) { + assertThat(action.getExecutionInfo()).containsKey(ExecutionRequirements.REQUIRES_DARWIN); + } + + /** + * Asserts that the given action can specify execution requirements, but does not require + * execution on darwin. + */ + protected void assertNotRequiresDarwin(Action action) { + ExecutionInfoSpecifier executionInfoSpecifier = (ExecutionInfoSpecifier) action; + assertThat(executionInfoSpecifier.getExecutionInfo()) + .doesNotContainKey(ExecutionRequirements.REQUIRES_DARWIN); + } + + protected ConfiguredTarget addBinWithTransitiveDepOnFrameworkImport() throws Exception { + ConfiguredTarget lib = addLibWithDepOnFrameworkImport(); + return createBinaryTargetWriter("//bin:bin") + .setAndCreateFiles("srcs", "a.m") + .setList("deps", lib.getLabel().toString()) + .write(); + + } + + protected ConfiguredTarget addLibWithDepOnFrameworkImport() throws Exception { + scratch.file("fx/fx1.framework/a"); + scratch.file("fx/fx1.framework/b"); + scratch.file("fx/fx2.framework/c"); + scratch.file("fx/fx2.framework/d"); + scratch.file("fx/BUILD", + "objc_framework(", + " name = 'fx',", + " framework_imports = glob(['fx1.framework/*', 'fx2.framework/*']),", + " sdk_frameworks = ['CoreLocation'],", + " weak_sdk_frameworks = ['MediaAccessibility'],", + " sdk_dylibs = ['libdy1'],", + ")"); + return createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("deps", "//fx:fx") + .write(); + } + + protected CommandAction compileAction(String ownerLabel, String objFileName) throws Exception { + Action archiveAction = archiveAction(ownerLabel); + return (CommandAction) + getGeneratingAction( + getFirstArtifactEndingWith(archiveAction.getInputs(), "/" + objFileName)); + } + + /** + * Verifies simply that some rule type creates the {@link CompilationArtifacts} object + * successfully; in particular, makes sure it is not ignoring attributes. If the scope of + * {@link CompilationArtifacts} expands, make sure this method tests it properly. + * + * <p>This test only makes sure the attributes are not being ignored - it does not test any + * other functionality in depth, which is covered by other unit tests. + */ + protected void checkPopulatesCompilationArtifacts(RuleType ruleType) throws Exception { + scratch.file("x/a.m"); + scratch.file("x/b.m"); + scratch.file("x/c.pch"); + ruleType.scratchTarget(scratch, + "srcs", "['a.m']", + "non_arc_srcs", "['b.m']", + "pch", "'c.pch'"); + ImmutableList<String> includeFlags = ImmutableList.of("-include", "x/c.pch"); + assertContainsSublist(compileAction("//x:x", "a.o").getArguments(), includeFlags); + assertContainsSublist(compileAction("//x:x", "b.o").getArguments(), includeFlags); + } + + protected void checkProvidesHdrsAndIncludes(RuleType ruleType) throws Exception { + scratch.file("x/a.h"); + ruleType.scratchTarget(scratch, + "hdrs", "['a.h']", + "includes", "['incdir']"); + ObjcProvider provider = + getConfiguredTarget("//x:x", getAppleCrosstoolConfiguration()) + .getProvider(ObjcProvider.class); + assertThat(provider.get(HEADER)).containsExactly(getSourceArtifact("x/a.h")); + assertThat(provider.get(INCLUDE)) + .containsExactly( + PathFragment.create("x/incdir"), + getAppleCrosstoolConfiguration().getGenfilesFragment().getRelative("x/incdir")); + } + + protected void checkCompilesWithHdrs(RuleType ruleType) throws Exception { + scratch.file("x/a.m"); + scratch.file("x/a.h"); + ruleType.scratchTarget(scratch, + "srcs", "['a.m']", + "hdrs", "['a.h']"); + assertThat(compileAction("//x:x", "a.o").getInputs()).contains(getSourceArtifact("x/a.h")); + } + + protected void checkArchivesPrecompiledObjectFiles(RuleType ruleType) throws Exception { + scratch.file("x/a.m"); + scratch.file("x/b.o"); + ruleType.scratchTarget(scratch, "srcs", "['a.m', 'b.o']"); + assertThat(Artifact.toRootRelativePaths(archiveAction("//x:x").getInputs())).contains("x/b.o"); + } + + protected void checkPopulatesBundling(RuleType ruleType) throws Exception { + scratch.file("x/a.m"); + scratch.file("x/info.plist"); + scratch.file("x/assets.xcassets/1"); + ruleType.scratchTarget(scratch, + "srcs", "['a.m']", + INFOPLIST_ATTR, "'info.plist'", + "asset_catalogs", "['assets.xcassets/1']"); + String targetName = "//x:x"; + ConfiguredTarget target = getConfiguredTarget(targetName); + PlMergeProtos.Control control = plMergeControl(targetName); + + assertThat(control.getSourceFileList()) + .contains(getSourceArtifact("x/info.plist").getExecPathString()); + + assertThat(linkAction("//x:x").getInputs()) + .contains(getBinArtifact("libx.a", target)); + + Artifact actoolzipOutput = getBinArtifact("x.actool.zip", target); + assertThat(getGeneratingAction(actoolzipOutput).getInputs()) + .contains(getSourceArtifact("x/assets.xcassets/1")); + } + + /** + * Checks that a target at {@code //x:x}, which is already created, registered a correct merge + * bundle action based on certain arbitrary and default values which include nested bundles. + */ + private void checkMergeBundleActionsWithNestedBundle( + String bundleDir, BuildConfiguration bundleConfiguration) throws Exception { + BundleFile fooResource = BundleFile.newBuilder() + .setSourceFile("bndl/foo.data") + .setBundlePath("foo.data") + .setExternalFileAttribute(BundleableFile.DEFAULT_EXTERNAL_FILE_ATTRIBUTE) + .build(); + + BundleMergeProtos.Control topControl = bundleMergeControl("//x:x"); + BundleMergeProtos.Control nestedControl = + Iterables.getOnlyElement(topControl.getNestedBundleList()); + assertThat(topControl.getBundleRoot()).isEqualTo(bundleDir); + assertThat(nestedControl.getBundleRoot()).isEqualTo("bndl.bundle"); + assertThat(topControl.getBundleFileList()).doesNotContain(fooResource); + assertThat(nestedControl.getBundleFileList()).contains(fooResource); + + ConfiguredTarget bndlTarget = getConfiguredTarget("//bndl:bndl", bundleConfiguration); + Artifact bundlePlist = getBinArtifact("bndl-MergedInfo.plist", bndlTarget); + assertThat(nestedControl.getBundleInfoPlistFile()).isEqualTo(bundlePlist.getExecPathString()); + + Artifact actoolzipOutput = getBinArtifact("bndl.actool.zip", bndlTarget); + assertThat(nestedControl.getMergeZipList()) + .containsExactly(MergeZip.newBuilder() + .setEntryNamePrefix(bundleDir + "/bndl.bundle/") + .setSourcePath(actoolzipOutput.getExecPathString()) + .build()); + + assertThat(bundleMergeAction("//x:x").getInputs()) + .containsAllOf(getSourceArtifact("bndl/foo.data"), bundlePlist, actoolzipOutput); + } + + protected SpawnAction bundleMergeAction(String target) throws Exception { + Label targetLabel = Label.parseAbsolute(target); + ConfiguredTarget binary = getConfiguredTarget(target); + return (SpawnAction) + getGeneratingAction(getBinArtifact(targetLabel.getName() + artifactName(".unprocessed.ipa"), + binary)); + } + + protected void checkMergeBundleActionsWithNestedBundle(RuleType ruleType) throws Exception { + scratch.file("bndl/BUILD", + "objc_bundle_library(", + " name = 'bndl',", + " resources = ['foo.data'],", + " infoplist = 'bndl-Info.plist',", + " asset_catalogs = ['bar.xcassets/1'],", + ")"); + ruleType.scratchTarget(scratch, + "bundles", "['//bndl:bndl']"); + checkMergeBundleActionsWithNestedBundle( + getBundlePathInsideIpa(ruleType), targetConfig); + } + + // This checks that the proto bundling and grouping behavior works as expected. Grouping is based + // on the proto_library targets, given that each proto_library is complete in its closure (all + // the required deps are captured inside a proto_library). + // + // This particular tests sets up 3 proto groups, defined as [A, B], [B, C], [A, C, D]. The proto + // grouping support detects that, for example, since A doesn't appear in all groups with B or C, + // then it doesn't need any dependencies other than itself to be built. The same applies for B and + // C, The same cannot be said about D, which only appears with A and C, so we have to assume that + // D depends on A and C. + // + // These dependencies describe what the inputs will be to each of the generation/compilation + // actions. Denoting {[in] -> [out]} as an action with "in" being the required inputs, and "out" + // being the expected outputs, given the layout of the groups for this test, the actions should + // be: + // + // {[A] -> [A]} + // {[B] -> [B]} + // {[C] -> [C]} + // {[A, C, D] -> [D]} + // + // This test ensures that, for example, to generate DataA.pbobjc.{h,m}, only data_a.proto should + // be provided as an input, while the inputs to generate DataD.pbobjc.{h,m} should be + // data_a.proto, data_c.proto and data_d.proto. The same applies for the compilation actions, + // where the inputs are interpreted as .pbobjc.h files, and the output is a .pbobjc.o file. + protected void checkProtoBundlingAndLinking(RuleType ruleType) throws Exception { + scratch.file( + "protos/BUILD", + "proto_library(", + " name = 'protos_1',", + " srcs = ['data_a.proto', 'data_b.proto'],", + ")", + "proto_library(", + " name = 'protos_2',", + " srcs = ['data_b.proto', 'data_c.proto'],", + ")", + "proto_library(", + " name = 'protos_3',", + " srcs = ['data_c.proto', 'data_a.proto', 'data_d.proto'],", + ")", + "objc_proto_library(", + " name = 'objc_protos_a',", + " portable_proto_filters = ['filter_a.pbascii'],", + " deps = [':protos_1'],", + ")", + "objc_proto_library(", + " name = 'objc_protos_b',", + " portable_proto_filters = ['filter_b.pbascii'],", + " deps = [':protos_2', ':protos_3'],", + ")"); + scratch.file( + "libs/BUILD", + "objc_library(", + " name = 'objc_lib',", + " srcs = ['a.m'],", + " deps = ['//protos:objc_protos_a', '//protos:objc_protos_b']", + ")"); + + ruleType.scratchTarget( + scratch, + "srcs", "['main.m']", + "deps", "['//libs:objc_lib']", + "defines", "['SHOULDNOTBEINPROTOS']", + "copts", "['-ISHOULDNOTBEINPROTOS']"); + + BuildConfiguration childConfig = + Iterables.getOnlyElement( + getSplitConfigurations( + targetConfig, + new MultiArchSplitTransitionProvider.AppleBinaryTransition( + PlatformType.IOS, Optional.<DottedVersion>absent()))); + + ConfiguredTarget topTarget = getConfiguredTarget("//x:x", childConfig); + + assertObjcProtoProviderArtifactsArePropagated(topTarget); + assertBundledGenerationActionsAreDifferent(topTarget); + assertOnlyRequiredInputsArePresentForBundledGeneration(topTarget); + assertOnlyRequiredInputsArePresentForBundledCompilation(topTarget); + assertCoptsAndDefinesForBundlingTarget(topTarget); + assertBundledGroupsGetCreatedAndLinked(topTarget); + } + + protected ImmutableList<Artifact> getAllObjectFilesLinkedInBin(Artifact bin) { + ImmutableList.Builder<Artifact> objects = ImmutableList.builder(); + CommandAction binAction = (CommandAction) getGeneratingAction(bin); + for (Artifact binActionArtifact : binAction.getInputs()) { + if (binActionArtifact.getRootRelativePath().getPathString().endsWith(".a")) { + CommandAction linkAction = (CommandAction) getGeneratingAction(binActionArtifact); + for (Artifact linkActionArtifact : linkAction.getInputs()) { + if (linkActionArtifact.getRootRelativePath().getPathString().endsWith(".o")) { + objects.add(linkActionArtifact); + } + } + } + } + return objects.build(); + } + + private void assertObjcProtoProviderArtifactsArePropagated(ConfiguredTarget topTarget) + throws Exception { + ConfiguredTarget libTarget = + view.getPrerequisiteConfiguredTargetForTesting( + reporter, topTarget, Label.parseAbsoluteUnchecked("//libs:objc_lib"), masterConfig); + + ObjcProtoProvider protoProvider = libTarget.getProvider(ObjcProtoProvider.class); + assertThat(protoProvider).isNotNull(); + assertThat(protoProvider.getProtoGroups().toSet()).hasSize(3); + assertThat( + Artifact.toExecPaths( + ImmutableSet.copyOf(Iterables.concat(protoProvider.getProtoGroups())))) + .containsExactly( + "protos/data_a.proto", + "protos/data_b.proto", + "protos/data_c.proto", + "protos/data_d.proto"); + assertThat(Artifact.toExecPaths(protoProvider.getPortableProtoFilters())) + .containsExactly("protos/filter_a.pbascii", "protos/filter_b.pbascii"); + } + + private void assertBundledGenerationActionsAreDifferent(ConfiguredTarget topTarget) { + Artifact protoHeaderA = getBinArtifact("_generated_protos/x/protos/DataA.pbobjc.h", topTarget); + Artifact protoHeaderB = getBinArtifact("_generated_protos/x/protos/DataB.pbobjc.h", topTarget); + Artifact protoHeaderC = getBinArtifact("_generated_protos/x/protos/DataC.pbobjc.h", topTarget); + Artifact protoHeaderD = getBinArtifact("_generated_protos/x/protos/DataD.pbobjc.h", topTarget); + CommandAction protoActionA = (CommandAction) getGeneratingAction(protoHeaderA); + CommandAction protoActionB = (CommandAction) getGeneratingAction(protoHeaderB); + CommandAction protoActionC = (CommandAction) getGeneratingAction(protoHeaderC); + CommandAction protoActionD = (CommandAction) getGeneratingAction(protoHeaderD); + assertThat(protoActionA).isNotNull(); + assertThat(protoActionB).isNotNull(); + assertThat(protoActionC).isNotNull(); + assertThat(protoActionD).isNotNull(); + assertThat(protoActionA).isNotEqualTo(protoActionB); + assertThat(protoActionB).isNotEqualTo(protoActionC); + assertThat(protoActionC).isNotEqualTo(protoActionD); + } + + private void assertOnlyRequiredInputsArePresentForBundledGeneration(ConfiguredTarget topTarget) + throws Exception { + ConfiguredTarget libTarget = + view.getPrerequisiteConfiguredTargetForTesting( + reporter, topTarget, Label.parseAbsoluteUnchecked("//libs:objc_lib"), masterConfig); + ObjcProtoProvider protoProvider = libTarget.getProvider(ObjcProtoProvider.class); + + Artifact protoHeaderA = getBinArtifact("_generated_protos/x/protos/DataA.pbobjc.h", topTarget); + Artifact protoHeaderB = getBinArtifact("_generated_protos/x/protos/DataB.pbobjc.h", topTarget); + Artifact protoHeaderC = getBinArtifact("_generated_protos/x/protos/DataC.pbobjc.h", topTarget); + Artifact protoHeaderD = getBinArtifact("_generated_protos/x/protos/DataD.pbobjc.h", topTarget); + + CommandAction protoActionA = (CommandAction) getGeneratingAction(protoHeaderA); + CommandAction protoActionB = (CommandAction) getGeneratingAction(protoHeaderB); + CommandAction protoActionC = (CommandAction) getGeneratingAction(protoHeaderC); + CommandAction protoActionD = (CommandAction) getGeneratingAction(protoHeaderD); + + assertThat(protoActionA.getInputs()).containsAllIn(protoProvider.getPortableProtoFilters()); + assertThat(protoActionB.getInputs()).containsAllIn(protoProvider.getPortableProtoFilters()); + assertThat(protoActionC.getInputs()).containsAllIn(protoProvider.getPortableProtoFilters()); + assertThat(protoActionD.getInputs()).containsAllIn(protoProvider.getPortableProtoFilters()); + + assertThat(Artifact.toExecPaths(protoActionA.getInputs())).contains("protos/data_a.proto"); + assertThat(Artifact.toExecPaths(protoActionA.getInputs())) + .containsNoneOf("protos/data_b.proto", "protos/data_c.proto", "protos/data_d.proto"); + + assertThat(Artifact.toExecPaths(protoActionB.getInputs())).contains("protos/data_b.proto"); + assertThat(Artifact.toExecPaths(protoActionB.getInputs())) + .containsNoneOf("protos/data_a.proto", "protos/data_c.proto", "protos/data_d.proto"); + + assertThat(Artifact.toExecPaths(protoActionC.getInputs())).contains("protos/data_c.proto"); + assertThat(Artifact.toExecPaths(protoActionC.getInputs())) + .containsNoneOf("protos/data_a.proto", "protos/data_b.proto", "protos/data_d.proto"); + + assertThat(Artifact.toExecPaths(protoActionD.getInputs())).contains("protos/data_d.proto"); + assertThat(Artifact.toExecPaths(protoActionD.getInputs())) + .containsAllOf("protos/data_a.proto", "protos/data_c.proto"); + assertThat(Artifact.toExecPaths(protoActionD.getInputs())) + .doesNotContain("protos/data_b.proto"); + } + + private void assertOnlyRequiredInputsArePresentForBundledCompilation(ConfiguredTarget topTarget) { + Artifact protoHeaderA = getBinArtifact("_generated_protos/x/protos/DataA.pbobjc.h", topTarget); + Artifact protoHeaderB = getBinArtifact("_generated_protos/x/protos/DataB.pbobjc.h", topTarget); + Artifact protoHeaderC = getBinArtifact("_generated_protos/x/protos/DataC.pbobjc.h", topTarget); + Artifact protoHeaderD = getBinArtifact("_generated_protos/x/protos/DataD.pbobjc.h", topTarget); + + Artifact protoObjectA = + getBinArtifact("_objs/x/x/_generated_protos/x/protos/DataA.pbobjc.o", topTarget); + Artifact protoObjectB = + getBinArtifact("_objs/x/x/_generated_protos/x/protos/DataB.pbobjc.o", topTarget); + Artifact protoObjectC = + getBinArtifact("_objs/x/x/_generated_protos/x/protos/DataC.pbobjc.o", topTarget); + Artifact protoObjectD = + getBinArtifact("_objs/x/x/_generated_protos/x/protos/DataD.pbobjc.o", topTarget); + + CommandAction protoObjectActionA = (CommandAction) getGeneratingAction(protoObjectA); + CommandAction protoObjectActionB = (CommandAction) getGeneratingAction(protoObjectB); + CommandAction protoObjectActionC = (CommandAction) getGeneratingAction(protoObjectC); + CommandAction protoObjectActionD = (CommandAction) getGeneratingAction(protoObjectD); + + assertThat(protoObjectActionA).isNotNull(); + assertThat(protoObjectActionB).isNotNull(); + assertThat(protoObjectActionC).isNotNull(); + assertThat(protoObjectActionD).isNotNull(); + + assertThat(protoObjectActionA.getInputs()) + .containsNoneOf(protoHeaderB, protoHeaderC, protoHeaderD); + assertThat(protoObjectActionB.getInputs()) + .containsNoneOf(protoHeaderA, protoHeaderC, protoHeaderD); + assertThat(protoObjectActionC.getInputs()) + .containsNoneOf(protoHeaderA, protoHeaderB, protoHeaderD); + assertThat(protoObjectActionD.getInputs()) + .containsAllOf(protoHeaderA, protoHeaderC, protoHeaderD); + assertThat(protoObjectActionD.getInputs()) + .doesNotContain(protoHeaderB); + } + + private void assertCoptsAndDefinesForBundlingTarget(ConfiguredTarget topTarget) { + Artifact protoObject = + getBinArtifact("_objs/x/x/_generated_protos/x/protos/DataA.pbobjc.o", topTarget); + CommandAction protoObjectAction = (CommandAction) getGeneratingAction(protoObject); + assertThat(protoObjectAction).isNotNull(); + assertThat(protoObjectAction.getArguments()) + .containsNoneOf("-DSHOULDNOTBEINPROTOS", "-ISHOULDNOTBEINPROTOS"); + + Artifact binLib = getBinArtifact("libx.a", topTarget); + CommandAction binLibAction = (CommandAction) getGeneratingAction(binLib); + assertThat(binLibAction).isNotNull(); + + Artifact binSrcObject = getFirstArtifactEndingWith(binLibAction.getInputs(), "main.o"); + CommandAction binSrcObjectAction = (CommandAction) getGeneratingAction(binSrcObject); + assertThat(binSrcObjectAction).isNotNull(); + assertThat(binSrcObjectAction.getArguments()) + .containsAllOf("-DSHOULDNOTBEINPROTOS", "-ISHOULDNOTBEINPROTOS"); + } + + private void assertBundledGroupsGetCreatedAndLinked(ConfiguredTarget topTarget) { + Artifact protosGroup0Lib = getBinArtifact("libx_BundledProtos_0.a", topTarget); + Artifact protosGroup1Lib = getBinArtifact("libx_BundledProtos_1.a", topTarget); + Artifact protosGroup2Lib = getBinArtifact("libx_BundledProtos_2.a", topTarget); + Artifact protosGroup3Lib = getBinArtifact("libx_BundledProtos_3.a", topTarget); + + CommandAction protosLib0Action = (CommandAction) getGeneratingAction(protosGroup0Lib); + CommandAction protosLib1Action = (CommandAction) getGeneratingAction(protosGroup1Lib); + CommandAction protosLib2Action = (CommandAction) getGeneratingAction(protosGroup2Lib); + CommandAction protosLib3Action = (CommandAction) getGeneratingAction(protosGroup3Lib); + assertThat(protosLib0Action).isNotNull(); + assertThat(protosLib1Action).isNotNull(); + assertThat(protosLib2Action).isNotNull(); + assertThat(protosLib3Action).isNotNull(); + + Artifact bin = getBinArtifact("x_bin", topTarget); + CommandAction binAction = (CommandAction) getGeneratingAction(bin); + assertThat(binAction.getInputs()) + .containsAllOf(protosGroup0Lib, protosGroup1Lib, protosGroup2Lib, protosGroup3Lib); + } + + protected void checkProtoBundlingDoesNotHappen(RuleType ruleType) throws Exception { + scratch.file( + "protos/BUILD", + "proto_library(", + " name = 'protos',", + " srcs = ['data_a.proto'],", + ")", + "objc_proto_library(", + " name = 'objc_protos',", + " portable_proto_filters = ['filter_b.pbascii'],", + " deps = [':protos'],", + ")"); + scratch.file( + "libs/BUILD", + "objc_library(", + " name = 'objc_lib',", + " srcs = ['a.m'],", + " deps = ['//protos:objc_protos']", + ")"); + + ruleType.scratchTarget( + scratch, + "srcs", "['main.m']", + "deps", "['//libs:objc_lib']"); + + ConfiguredTarget topTarget = getConfiguredTarget("//x:x"); + Artifact protoHeader = getBinArtifact("_generated_protos/x/protos/DataA.pbobjc.h", topTarget); + CommandAction protoAction = (CommandAction) getGeneratingAction(protoHeader); + assertThat(protoAction).isNull(); + } + + protected void checkProtoBundlingWithTargetsWithNoDeps(RuleType ruleType) throws Exception { + scratch.file( + "protos/BUILD", + "proto_library(", + " name = 'protos_a',", + " srcs = ['data_a.proto'],", + ")", + "objc_proto_library(", + " name = 'objc_protos_a',", + " portable_proto_filters = ['filter_a.pbascii'],", + " deps = [':protos_a'],", + ")"); + scratch.file( + "libs/BUILD", + "objc_library(", + " name = 'objc_lib',", + " srcs = ['a.m'],", + " deps = ['//protos:objc_protos_a', ':no_deps_target'],", + ")", + "objc_framework(", + " name = 'no_deps_target',", + " framework_imports = ['x.framework'],", + ")"); + + ruleType.scratchTarget(scratch, "deps", "['//libs:objc_lib']"); + + ConfiguredTarget topTarget = getConfiguredTarget("//x:x"); + + ConfiguredTarget libTarget = + view.getPrerequisiteConfiguredTargetForTesting( + reporter, topTarget, Label.parseAbsoluteUnchecked("//libs:objc_lib"), masterConfig); + + ObjcProtoProvider protoProvider = libTarget.getProvider(ObjcProtoProvider.class); + assertThat(protoProvider).isNotNull(); + } + + protected void checkFrameworkDepLinkFlags(RuleType ruleType, + ExtraLinkArgs extraLinkArgs) throws Exception { + scratch.file( + "libs/BUILD", + "objc_library(", + " name = 'objc_lib',", + " srcs = ['a.m'],", + " deps = [':my_framework'],", + ")", + "objc_framework(", + " name = 'my_framework',", + " framework_imports = ['buzzbuzz.framework'],", + ")"); + + ruleType.scratchTarget(scratch, "deps", "['//libs:objc_lib']"); + + CommandAction linkAction = linkAction("//x:x"); + Artifact binArtifact = getFirstArtifactEndingWith(linkAction.getOutputs(), "x_bin"); + Artifact objList = getFirstArtifactEndingWith(linkAction.getInputs(), "x-linker.objlist"); + + verifyLinkAction( + binArtifact, + objList, + "x86_64", + ImmutableList.of("x/libx.a", "libobjc_lib.a"), + ImmutableList.of(PathFragment.create("libs/buzzbuzz")), + extraLinkArgs); + } + + protected void checkBundleLoaderIsCorrectlyPassedToTheLinker(RuleType ruleType) throws Exception { + scratch.file("bin/BUILD", + "apple_binary(", + " name = 'bin',", + " srcs = ['a.m'],", + " platform_type = 'ios',", + ")"); + + ruleType.scratchTarget(scratch, "binary_type", "'loadable_bundle'", "bundle_loader", + "'//bin:bin'"); + ConfiguredTarget binTarget = getConfiguredTarget("//bin:bin"); + + CommandAction linkAction = linkAction("//x:x"); + assertThat(Joiner.on(" ").join(linkAction.getArguments())) + .contains("-bundle_loader " + getBinArtifact("bin_lipobin", binTarget).getExecPath()); + assertThat(Joiner.on(" ").join(linkAction.getArguments())) + .contains("-Xlinker -rpath -Xlinker @loader_path/Frameworks"); + } + + /** + * @param bundleConfiguration the configuration in which the bundle is expected to be executed + */ + protected void checkMergeBundleActionsWithNestedBundle(BinaryRuleTypePair ruleTypePair, + BuildConfiguration bundleConfiguration) throws Exception { + scratch.file("bndl/BUILD", + "objc_bundle_library(", + " name = 'bndl',", + " resources = ['foo.data'],", + " infoplist = 'bndl-Info.plist',", + " asset_catalogs = ['bar.xcassets/1'],", + ")"); + ruleTypePair.scratchTargets(scratch, + "bundles", "['//bndl:bndl']"); + checkMergeBundleActionsWithNestedBundle(ruleTypePair.getBundleDir(), bundleConfiguration); + } + + protected Action lipoLibAction(String libLabel) throws Exception { + return actionProducingArtifact(libLabel, "_lipo.a"); + } + + protected Action lipoBinAction(String binLabel) throws Exception { + return actionProducingArtifact(binLabel, "_lipobin"); + } + + protected CommandAction linkAction(String binLabel) throws Exception { + CommandAction linkAction = (CommandAction) actionProducingArtifact(binLabel, "_bin"); + if (linkAction == null) { + // For multi-architecture rules, the link action is not in the target configuration, but + // across a configuration transition. + Action lipoAction = lipoBinAction(binLabel); + if (lipoAction != null) { + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), "_bin"); + linkAction = (CommandAction) getGeneratingAction(binArtifact); + } + } + return linkAction; + } + + protected CommandAction linkLibAction(String libLabel) throws Exception { + CommandAction linkAction = (CommandAction) actionProducingArtifact(libLabel, "-fl.a"); + + if (linkAction == null) { + // For multi-architecture rules, the link action is not in the target configuration, but + // across a configuration transition. + Action lipoAction = lipoLibAction(libLabel); + if (lipoAction != null) { + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), "-fl.a"); + linkAction = (CommandAction) getGeneratingAction(binArtifact); + } + } + return linkAction; + } + + protected Action actionProducingArtifact(String targetLabel, + String artifactSuffix) throws Exception { + ConfiguredTarget libraryTarget = getConfiguredTarget(targetLabel); + Label parsedLabel = Label.parseAbsolute(targetLabel); + Artifact linkedLibrary = getBinArtifact( + parsedLabel.getName() + artifactSuffix, + libraryTarget); + return getGeneratingAction(linkedLibrary); + } + + protected void addTargetWithAssetCatalogs(RuleType ruleType) throws Exception { + scratch.file("x/foo.xcassets/foo"); + scratch.file("x/bar.xcassets/bar"); + ruleType.scratchTarget(scratch, + "asset_catalogs", "['foo.xcassets/foo', 'bar.xcassets/bar']"); + } + + /** + * Checks that a target at {@code //x:x}, which is already created, registered a correct actool + * action based on the given targetDevice and platform, setting certain arbitrary and default + * values. + */ + protected void checkActoolActionCorrectness(DottedVersion minimumOsVersion, String targetDevice, + String platform) throws Exception { + Artifact actoolZipOut = getBinArtifact("x" + artifactName(".actool.zip"), + getConfiguredTarget("//x:x")); + Artifact actoolPartialInfoplist = + getBinArtifact("x" + artifactName(".actool-PartialInfo.plist"), "//x:x"); + SpawnAction actoolZipAction = (SpawnAction) getGeneratingAction(actoolZipOut); + assertThat(actoolZipAction.getArguments()) + .containsExactly( + MOCK_ACTOOLWRAPPER_PATH, + actoolZipOut.getExecPathString(), + "--platform", platform, + "--output-partial-info-plist", actoolPartialInfoplist.getExecPathString(), + "--minimum-deployment-target", minimumOsVersion.toString(), + "--target-device", targetDevice, + "x/foo.xcassets", "x/bar.xcassets") + .inOrder(); + assertRequiresDarwin(actoolZipAction); + + assertThat(Artifact.toExecPaths(actoolZipAction.getInputs())) + .containsExactly( + "x/foo.xcassets/foo", + "x/bar.xcassets/bar", + MOCK_ACTOOLWRAPPER_PATH); + assertThat(Artifact.toExecPaths(actoolZipAction.getOutputs())) + .containsExactly( + actoolZipOut.getExecPathString(), + actoolPartialInfoplist.getExecPathString()); + } + + /** + * Checks that a target at {@code //x:x}, which is already created, registered a correct actool + * action based on certain arbitrary and default values for iphone simulator. + */ + protected void checkActoolActionCorrectness(DottedVersion minimumOsVersion) throws Exception { + checkActoolActionCorrectness(minimumOsVersion, "iphone", "iphonesimulator"); + } + + protected void checkAssetCatalogAttributeError(RuleType ruleType, String attribute) + throws Exception { + checkAssetCatalogAttributeError(ruleType, attribute, INFOPLIST_ATTR, "'pl.plist'"); + } + + protected void checkAssetCatalogAttributeError(RuleType ruleType, String attribute, + String infoplistAttribute, String infoPlists) throws Exception { + scratch.file("x/pl.plist"); + checkError("x", "x", String.format(NO_ASSET_CATALOG_ERROR_FORMAT, "3.1415926"), + ruleType.target(scratch, "x", "x", + infoplistAttribute, infoPlists, + attribute, "'3.1415926'")); + } + + protected SpawnAction actoolZipActionForIpa(String target) throws Exception { + Artifact binActoolZipOut = + getFirstArtifactEndingWith(bundleMergeAction(target).getInputs(), ".actool.zip"); + return (SpawnAction) getGeneratingAction(binActoolZipOut); + } + + /** + * Checks that a target at {@code //x:x}, which is already created, registered an actool with + * correct arguments based on certain arbitrary and default values. + */ + private void checkActoolZipInvocationCorrectness(DottedVersion minimumOsVersion) + throws Exception { + SpawnAction actoolZipAction = actoolZipActionForIpa("//x:x"); + assertThat(actoolZipAction.getArguments()) + .containsExactly( + MOCK_ACTOOLWRAPPER_PATH, + execPathEndingWith(actoolZipAction.getOutputs(), "x.actool.zip"), + "--platform", "iphonesimulator", + "--output-partial-info-plist", + execPathEndingWith(actoolZipAction.getOutputs(), "actool-PartialInfo.plist"), + "--minimum-deployment-target", minimumOsVersion.toString(), + "--target-device", "iphone", + "lib/ac.xcassets", + "--app-icon", "foo", + "--launch-image", "bar") + .inOrder(); + } + + protected void checkSpecifyAppIconAndLaunchImageUsingXcassetsOfDependency(RuleType ruleType) + throws Exception { + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("asset_catalogs", "ac.xcassets/foo") + .write(); + ruleType.scratchTarget(scratch, + "srcs", "['src.m']", + "deps", "['//lib:lib']", + APP_ICON_ATTR, "'foo'", + LAUNCH_IMAGE_ATTR, "'bar'"); + checkActoolZipInvocationCorrectness(DEFAULT_IOS_SDK_VERSION); + } + + protected void checkSpecifyAppIconAndLaunchImageUsingXcassetsOfDependency( + BinaryRuleTypePair ruleTypePair, DottedVersion minimumOsVersion) throws Exception { + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("asset_catalogs", "ac.xcassets/foo") + .write(); + ruleTypePair.scratchTargets(scratch, + "deps", "['//lib:lib']", + APP_ICON_ATTR, "'foo'", + LAUNCH_IMAGE_ATTR, "'bar'"); + checkActoolZipInvocationCorrectness(minimumOsVersion); + } + + /** + * Verifies that targeted device family information is passed to actoolzip for the given targeted + * families. + * + * @param packageName where to place the rule during testing - this should be different every time + * the method is invoked + * @param buildFileContents contents of the BUILD file for the {@code packageName} package + * @param targetDevices the values to {@code --target-device} expected in the actoolzip invocation + */ + private void checkPassesFamiliesToActool(String packageName, String buildFileContents, + String... targetDevices) throws Exception { + scratch.file(String.format("%s/BUILD", packageName), buildFileContents); + ConfiguredTarget target = getConfiguredTarget(String.format("//%s:x", packageName)); + Artifact actoolZipOut = getBinArtifact("x.actool.zip", target); + SpawnAction actoolZipAction = (SpawnAction) getGeneratingAction(actoolZipOut); + + List<String> arguments = actoolZipAction.getArguments(); + for (String targetDevice : targetDevices) { + assertContainsSublist(arguments, ImmutableList.of("--target-device", targetDevice)); + } + + assertWithMessage("Incorrect number of --target-device flags in arguments [" + arguments + "]") + .that(Collections.frequency(arguments, "--target-device")) + .isEqualTo(targetDevices.length); + } + + private void checkPassesFamiliesToActool(RuleType ruleType, String packageName, + String familiesAttribute, String... actoolFlags) throws Exception { + String buildFileContents = ruleType.target(scratch, packageName, "x", + FAMILIES_ATTR, familiesAttribute, + "asset_catalogs", "['foo.xcassets/1']"); + checkPassesFamiliesToActool(packageName, buildFileContents, actoolFlags); + } + + protected void checkPassesFamiliesToActool(RuleType ruleType) throws Exception { + checkPassesFamiliesToActool(ruleType, "iphone", "['iphone']", "iphone"); + checkPassesFamiliesToActool(ruleType, "ipad", "['ipad']", "ipad"); + checkPassesFamiliesToActool(ruleType, "both", "['iphone', 'ipad']", "ipad", "iphone"); + checkPassesFamiliesToActool(ruleType, "both_reverse", "['ipad', 'iphone']", "ipad", "iphone"); + } + + private void checkPassesFamiliesToActool(BinaryRuleTypePair ruleTypePair, String packageName, + String familiesAttribute, String families, String... actoolFlags) throws Exception { + String buildFileContents = ruleTypePair.targets(scratch, packageName, + "asset_catalogs", "['foo.xcassets/1']", + familiesAttribute, families); + checkPassesFamiliesToActool(packageName, buildFileContents, actoolFlags); + } + + protected void checkPassesFamiliesToActool(BinaryRuleTypePair ruleTypePair) throws Exception { + checkPassesFamiliesToActool(ruleTypePair, FAMILIES_ATTR); + } + + protected void checkPassesFamiliesToActool(BinaryRuleTypePair ruleTypePair, + String familiesAttribute) throws Exception { + checkPassesFamiliesToActool(ruleTypePair, "iphone", familiesAttribute, "['iphone']", "iphone"); + checkPassesFamiliesToActool(ruleTypePair, "ipad", familiesAttribute, "['ipad']", "ipad"); + checkPassesFamiliesToActool(ruleTypePair, "both", familiesAttribute, "['iphone', 'ipad']", + "ipad", "iphone"); + checkPassesFamiliesToActool(ruleTypePair, "both_reverse", familiesAttribute, + "['ipad', 'iphone']", "ipad", "iphone"); + } + + /** + * Verifies that targeted device family information is passed to ibtool for the given targeted + * families. + * + * @param packageName where to place the rule during testing - this should be different every time + * the method is invoked + * @param buildFileContents contents of the BUILD file for the {@code packageName} package + * @param targetDevices the values to {@code --target-device} expected in the ibtool invocation + */ + private void checkPassesFamiliesToIbtool(String packageName, String buildFileContents, + String... targetDevices) throws Exception { + scratch.file(String.format("%s/BUILD", packageName), buildFileContents); + ConfiguredTarget target = getConfiguredTarget(String.format("//%s:x", packageName)); + + Artifact storyboardZipOut = getBinArtifact("x/foo.storyboard.zip", target); + SpawnAction storyboardZipAction = (SpawnAction) getGeneratingAction(storyboardZipOut); + + List<String> arguments = storyboardZipAction.getArguments(); + for (String targetDevice : targetDevices) { + assertContainsSublist(arguments, ImmutableList.of("--target-device", targetDevice)); + } + + assertWithMessage("Incorrect number of --target-device flags in arguments [" + arguments + "]") + .that(Collections.frequency(arguments, "--target-device")) + .isEqualTo(targetDevices.length); + } + + private void checkPassesFamiliesToIbtool(RuleType ruleType, String packageName, + String families, String... targetDevices) throws Exception { + String buildFileContents = ruleType.target(scratch, packageName, "x", + FAMILIES_ATTR, families, + "storyboards", "['foo.storyboard']"); + checkPassesFamiliesToIbtool(packageName, buildFileContents, targetDevices); + } + + protected void checkPassesFamiliesToIbtool(RuleType ruleType) throws Exception { + checkPassesFamiliesToIbtool(ruleType, "iphone", "['iphone']", "iphone"); + checkPassesFamiliesToIbtool(ruleType, "ipad", "['ipad']", "ipad"); + checkPassesFamiliesToIbtool(ruleType, "both", "['iphone', 'ipad']", + "ipad", "iphone"); + checkPassesFamiliesToIbtool(ruleType, "both_reverse", "['ipad', 'iphone']", + "ipad", "iphone"); + } + + private void checkPassesFamiliesToIbtool(BinaryRuleTypePair ruleTypePair, String packageName, + String familyAttribute, String families, String... targetDevices) throws Exception { + String buildFileContents = ruleTypePair.targets(scratch, packageName, + familyAttribute, families, + "storyboards", "['foo.storyboard']"); + checkPassesFamiliesToIbtool(packageName, buildFileContents, targetDevices); + } + + protected void checkPassesFamiliesToIbtool(BinaryRuleTypePair ruleTypePair) throws Exception { + checkPassesFamiliesToIbtool(ruleTypePair, FAMILIES_ATTR); + } + + protected void checkPassesFamiliesToIbtool(BinaryRuleTypePair ruleTypePair, + String familyAttribute) throws Exception { + checkPassesFamiliesToIbtool(ruleTypePair, "iphone", familyAttribute, "['iphone']", + "iphone"); + checkPassesFamiliesToIbtool(ruleTypePair, "ipad", familyAttribute, "['ipad']", + "ipad"); + checkPassesFamiliesToIbtool(ruleTypePair, "both", familyAttribute, + "['iphone', 'ipad']", "ipad", "iphone"); + checkPassesFamiliesToIbtool(ruleTypePair, "both_reverse", familyAttribute, + "['ipad', 'iphone']", "ipad", "iphone"); + } + + private void checkReportsErrorsForInvalidFamiliesAttribute( + RuleType ruleType, String packageName, String familyAttribute, String families) + throws Exception { + checkError(packageName, "x", ReleaseBundling.INVALID_FAMILIES_ERROR, + ruleType.target(scratch, packageName, "x", familyAttribute, families)); + } + + protected void checkReportsErrorsForInvalidFamiliesAttribute(RuleType ruleType) + throws Exception { + checkReportsErrorsForInvalidFamiliesAttribute(ruleType, FAMILIES_ATTR); + } + + protected void checkReportsErrorsForInvalidFamiliesAttribute(RuleType ruleType, + String familyAttribute) throws Exception { + checkReportsErrorsForInvalidFamiliesAttribute(ruleType, "a", familyAttribute, "['foo']"); + checkReportsErrorsForInvalidFamiliesAttribute(ruleType, "b", familyAttribute, "[]"); + checkReportsErrorsForInvalidFamiliesAttribute(ruleType, "c", familyAttribute, + "['iphone', 'ipad', 'iphone']"); + checkReportsErrorsForInvalidFamiliesAttribute(ruleType, "d", familyAttribute, + "['iphone', 'bar']"); + } + + /** + * @param extraAttributes individual strings which contain a whole attribute to be added to the + * generated target, e.g. "deps = ['foo']" + */ + protected void addBinAndLibWithResources( + String attributeName, + String libFile, + String binFile, + String binaryType, + String... extraAttributes) + throws Exception { + scratch.file("lib/" + libFile); + + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .set(attributeName, "['" + libFile + "']") + .write(); + + scratch.file("bin/" + binFile); + scratch.file( + "bin/BUILD", + binaryType + "(", + " name = 'bin',", + " srcs = ['src.m'],", + " deps = ['//lib:lib'],", + " " + attributeName + " = ['" + binFile + "'],", + Joiner.on(',').join(extraAttributes), + ")"); + } + + protected void checkCollectsResourceFilesTransitively( + String targetLabel, + Collection<String> binBundleMergeInputs, + Collection<String> libBundleMergeInputs, + ImmutableSetMultimap<String, Multiset<String>> filesByTarget) + throws Exception { + Action mergeBundleAction = bundleMergeAction(targetLabel); + + assertThat(Artifact.toRootRelativePaths(mergeBundleAction.getInputs())) + .containsAllIn(binBundleMergeInputs); + } + + protected void checkLinksDylibsTransitively(RuleType ruleType) + throws Exception { + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("sdk_dylibs", "libdy1", "libdy2") + .write(); + ruleType.scratchTarget(scratch, + "sdk_dylibs", "['libdy3']", + "deps", "['//lib:lib']"); + + CommandAction action = linkAction("//x:x"); + assertThat(Joiner.on(" ").join(action.getArguments())).contains("-ldy1 -ldy2 -ldy3"); + } + + protected BinaryFileWriteAction bundleMergeControlAction(String binaryLabelString) + throws Exception { + Label binaryLabel = Label.parseAbsolute(binaryLabelString); + ConfiguredTarget binary = getConfiguredTarget(binaryLabelString); + return (BinaryFileWriteAction) getGeneratingAction( + getBinArtifact(binaryLabel.getName() + artifactName(".ipa-control"), binary)); + } + + protected BundleMergeProtos.Control bundleMergeControl(String binaryLabel) + throws Exception { + try (InputStream in = bundleMergeControlAction(binaryLabel).getSource() + .openStream()) { + return BundleMergeProtos.Control.parseFrom(in); + } + } + + protected void checkNoDebugSymbolFileWithoutAppleFlag(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, "srcs", "['a.m']"); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + + Artifact plistArtifact = getBinArtifact("bin.app.dSYM/Contents/Info.plist", target); + Artifact debugSymbolArtifact = + getBinArtifact("bin.app.dSYM/Contents/Resources/DWARF/bin", target); + CommandAction plistAction = (CommandAction) getGeneratingAction(plistArtifact); + CommandAction debugSymbolAction = (CommandAction) getGeneratingAction(debugSymbolArtifact); + CommandAction linkAction = (CommandAction) getGeneratingAction(getBinArtifact("x_bin", target)); + + assertThat(linkAction.getArguments().get(2)).doesNotContain(DSYMUTIL); + assertThat(plistAction).isNull(); + assertThat(debugSymbolAction).isNull(); + } + + protected ConfiguredTarget createTargetWithStoryboards(RuleType ruleType) throws Exception { + scratch.file("x/1.storyboard"); + scratch.file("x/2.storyboard"); + scratch.file("x/subdir_for_no_reason/en.lproj/loc.storyboard"); + scratch.file("x/ja.lproj/loc.storyboard"); + ruleType.scratchTarget(scratch, "storyboards", "glob(['*.storyboard', '**/*.storyboard'])"); + return getConfiguredTarget("//x:x"); + } + + private ConfiguredTarget createTargetWithStoryboards(BinaryRuleTypePair ruleTypePair) + throws Exception { + scratch.file("x/1.storyboard"); + scratch.file("x/2.storyboard"); + scratch.file("x/subdir_for_no_reason/en.lproj/loc.storyboard"); + scratch.file("x/ja.lproj/loc.storyboard"); + ruleTypePair.scratchTargets(scratch, "storyboards", + "glob(['*.storyboard', '**/*.storyboard'])"); + return getConfiguredTarget("//x:x"); + } + + private ConfiguredTarget createTargetWithSwift(RuleType ruleType) throws Exception { + scratch.file("x/main.m"); + + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def swift_rule_impl(ctx):", + " return struct(objc=apple_common.new_objc_provider(uses_swift=True))", + "swift_rule = rule(implementation = swift_rule_impl, attrs = {})"); + + scratch.file( + "x/BUILD", + "load('//examples/rule:apple_rules.bzl', 'swift_rule')", + "swift_rule(name='swift_bin')", + ruleType.getRuleTypeName() + "(", + " name = 'x',", + " srcs = ['main.m'],", + " deps = [':swift_bin'],", + ")"); + + return getConfiguredTarget("//x:x"); + } + + protected void checkProvidesStoryboardObjects(RuleType ruleType) throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + createTargetWithStoryboards(ruleType); + ObjcProvider provider = providerForTarget("//x:x"); + ImmutableList<Artifact> storyboardInputs = ImmutableList.of( + getSourceArtifact("x/1.storyboard"), + getSourceArtifact("x/2.storyboard"), + getSourceArtifact("x/subdir_for_no_reason/en.lproj/loc.storyboard"), + getSourceArtifact("x/ja.lproj/loc.storyboard")); + + assertThat(provider.get(GENERAL_RESOURCE_FILE)) + .containsExactlyElementsIn(storyboardInputs); + assertThat(provider.get(STORYBOARD)) + .containsExactlyElementsIn(storyboardInputs); + } + + protected void checkRegistersStoryboardCompileActions( + BinaryRuleTypePair ruleTypePair, DottedVersion minimumOsVersion, + String platformName) throws Exception { + checkRegistersStoryboardCompileActions( + createTargetWithStoryboards(ruleTypePair), minimumOsVersion, + ImmutableList.of(platformName)); + } + + protected void checkRegistersStoryboardCompileActions(RuleType ruleType, + String platformName) throws Exception { + checkRegistersStoryboardCompileActions( + createTargetWithStoryboards(ruleType), DEFAULT_IOS_SDK_VERSION, + ImmutableList.of(platformName)); + } + + private void checkRegistersStoryboardCompileActions( + ConfiguredTarget target, DottedVersion minimumOsVersion, List<String> targetDevices) { + Artifact storyboardZip = getBinArtifact("x/1.storyboard.zip", target); + CommandAction compileAction = (CommandAction) getGeneratingAction(storyboardZip); + assertThat(compileAction.getInputs()).containsExactly( + getSourceArtifact(MOCK_IBTOOLWRAPPER_PATH), + getSourceArtifact("x/1.storyboard") + ); + String archiveRoot = targetDevices.contains("watch") ? "." : "1.storyboardc"; + assertThat(compileAction.getOutputs()).containsExactly(storyboardZip); + assertThat(compileAction.getArguments()) + .containsExactlyElementsIn(new CustomCommandLine.Builder() + .add(MOCK_IBTOOLWRAPPER_PATH) + .add(storyboardZip.getExecPathString()) + .add(archiveRoot) // archive root + .add("--minimum-deployment-target").add(minimumOsVersion.toString()) + .add("--module").add("x") + .addBeforeEach("--target-device", targetDevices) + .add("x/1.storyboard") + .build() + .arguments()) + .inOrder(); + + storyboardZip = getBinArtifact("x/ja.lproj/loc.storyboard.zip", target); + compileAction = (CommandAction) getGeneratingAction(storyboardZip); + assertThat(compileAction.getInputs()).containsExactly( + getSourceArtifact(MOCK_IBTOOLWRAPPER_PATH), + getSourceArtifact("x/ja.lproj/loc.storyboard") + ); + assertThat(compileAction.getOutputs()).containsExactly(storyboardZip); + archiveRoot = targetDevices.contains("watch") ? "ja.lproj/" : "ja.lproj/loc.storyboardc"; + assertThat(compileAction.getArguments()) + .containsExactlyElementsIn(new CustomCommandLine.Builder() + .add(MOCK_IBTOOLWRAPPER_PATH) + .add(storyboardZip.getExecPathString()) + .add(archiveRoot) // archive root + .add("--minimum-deployment-target").add(minimumOsVersion.toString()) + .add("--module").add("x") + .addBeforeEach("--target-device", targetDevices) + .add("x/ja.lproj/loc.storyboard") + .build().arguments()) + .inOrder(); + } + + protected void assertSwiftStdlibToolAction( + ConfiguredTarget target, + String platformName, + String zipName, + String bundlePath, + String toolchain) + throws Exception { + String zipArtifactName = String.format("%s.%s.zip", target.getTarget().getName(), zipName); + Artifact swiftLibsZip = getBinArtifact(zipArtifactName, target); + Artifact binary = getBinArtifact("x_lipobin", target); + SpawnAction toolAction = (SpawnAction) getGeneratingAction(swiftLibsZip); + + assertThat(toolAction.getInputs()).containsExactly( + binary, + getSourceArtifact(MOCK_SWIFTSTDLIBTOOLWRAPPER_PATH)); + assertThat(toolAction.getOutputs()).containsExactly(swiftLibsZip); + + CustomCommandLine.Builder expectedCommandLine = + CustomCommandLine.builder().add(MOCK_SWIFTSTDLIBTOOLWRAPPER_PATH); + + if (toolchain != null) { + expectedCommandLine.add("--toolchain").add(toolchain); + } + + expectedCommandLine + .add("--output_zip_path") + .add(swiftLibsZip.getExecPathString()) + .add("--bundle_path") + .add(bundlePath) + .add("--platform") + .add(platformName) + .add("--scan-executable") + .add(binary.getExecPathString()); + + assertThat(toolAction.getArguments()).isEqualTo(expectedCommandLine.build().arguments()); + } + + protected void checkRegisterSwiftSupportActions( + RuleType ruleType, String platformName, String toolchain) throws Exception { + checkRegisterSwiftSupportActions(createTargetWithSwift(ruleType), platformName, toolchain); + } + + protected void checkRegisterSwiftSupportActions( + RuleType ruleType, String platformName) throws Exception { + checkRegisterSwiftSupportActions(createTargetWithSwift(ruleType), platformName, null); + } + + protected void checkRegisterSwiftSupportActions( + ConfiguredTarget target, String platformName, String toolchain) throws Exception { + assertSwiftStdlibToolAction( + target, platformName, "swiftsupport", "SwiftSupport/" + platformName, toolchain); + } + + protected void checkRegisterSwiftSupportActions( + ConfiguredTarget target, String platformName) throws Exception { + assertSwiftStdlibToolAction( + target, platformName, "swiftsupport", "SwiftSupport/" + platformName, null); + } + + protected void checkRegisterSwiftStdlibActions( + RuleType ruleType, String platformName, String toolchain) throws Exception { + checkRegisterSwiftStdlibActions(createTargetWithSwift(ruleType), platformName, toolchain); + } + + protected void checkRegisterSwiftStdlibActions( + RuleType ruleType, String platformName) throws Exception { + checkRegisterSwiftStdlibActions(createTargetWithSwift(ruleType), platformName, null); + } + + protected void checkRegisterSwiftStdlibActions( + ConfiguredTarget target, String platformName, String toolchain) throws Exception { + assertSwiftStdlibToolAction(target, platformName, "swiftstdlib", "Frameworks", toolchain); + } + + protected void checkRegisterSwiftStdlibActions( + ConfiguredTarget target, String platformName) throws Exception { + assertSwiftStdlibToolAction(target, platformName, "swiftstdlib", "Frameworks", null); + } + + /** + * Checks that a target at {@code //x:x}, which is already created, merges xcdatamodel zips + * properly based on certain arbitrary and default values. + */ + private void checkMergesXcdatamodelZips(String bundleDir, String binarysMergeZip) + throws Exception { + Action mergeBundleAction = bundleMergeAction("//x:x"); + Iterable<Artifact> mergeInputs = mergeBundleAction.getInputs(); + assertThat(Artifact.toRootRelativePaths(mergeInputs)) + .containsAllOf("x/x/foo.zip", binarysMergeZip); + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + assertThat(control.getMergeZipList()) + .containsExactly( + MergeZip.newBuilder() + .setEntryNamePrefix(bundleDir + "/") + .setSourcePath( + getFirstArtifactEndingWith(mergeInputs, "x/foo.zip").getExecPathString()) + .build(), + MergeZip.newBuilder() + .setEntryNamePrefix(bundleDir + "/") + .setSourcePath( + getFirstArtifactEndingWith(mergeInputs, binarysMergeZip).getExecPathString()) + .build()); + } + + protected void checkMergesXcdatamodelZips(RuleType ruleType) throws Exception { + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("datamodels", "foo.xcdatamodel/1") + .write(); + ruleType.scratchTarget(scratch, + "deps", "['//lib:lib']", + "datamodels", "['bar.xcdatamodeld/barx.xcdatamodel/2']"); + checkMergesXcdatamodelZips(getBundlePathInsideIpa(ruleType), "x/x/bar.zip"); + } + + protected void checkMergesXcdatamodelZips(BinaryRuleTypePair ruleTypePair) throws Exception { + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("datamodels", "foo.xcdatamodel/1") + .write(); + ruleTypePair.scratchTargets(scratch, + "deps", "['//lib:lib']", + "datamodels", "['bar.xcdatamodeld/barx.xcdatamodel/2']"); + checkMergesXcdatamodelZips(ruleTypePair.getBundleDir(), "x/x/bar.zip"); + } + + protected void checkIncludesStoryboardOutputZipsAsMergeZips(RuleType ruleType) throws Exception { + scratch.file("lib/libsb.storyboard"); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("storyboards", "libsb.storyboard") + .write(); + scratch.file("bndl/bndlsb.storyboard"); + scratch.file("bndl/BUILD", + "objc_bundle_library(", + " name = 'bndl',", + " storyboards = ['bndlsb.storyboard'],", + ")"); + + scratch.file("x/xsb.storyboard"); + ruleType.scratchTarget(scratch, + "storyboards", "['xsb.storyboard']", + "deps", "['//lib:lib']", + "bundles", "['//bndl:bndl']"); + + Artifact libsbOutputZip = getBinArtifact("x/libsb.storyboard.zip", "//x:x"); + Artifact bndlsbOutputZip = getBinArtifact("bndl/bndlsb.storyboard.zip", "//bndl:bndl"); + Artifact xsbOutputZip = getBinArtifact("x/xsb.storyboard.zip", "//x:x"); + + BundleMergeProtos.Control mergeControl = bundleMergeControl("//x:x"); + String prefix = getBundlePathInsideIpa(ruleType) + "/"; + assertThat(mergeControl.getMergeZipList()).containsExactly( + BundleMergeProtos.MergeZip.newBuilder() + .setEntryNamePrefix(prefix) + .setSourcePath(libsbOutputZip.getExecPathString()) + .build(), + BundleMergeProtos.MergeZip.newBuilder() + .setEntryNamePrefix(prefix) + .setSourcePath(xsbOutputZip.getExecPathString()) + .build()); + + BundleMergeProtos.Control nestedMergeControl = + Iterables.getOnlyElement(mergeControl.getNestedBundleList()); + assertThat(nestedMergeControl.getMergeZipList()).containsExactly( + BundleMergeProtos.MergeZip.newBuilder() + .setEntryNamePrefix(prefix + "bndl.bundle/") + .setSourcePath(bndlsbOutputZip.getExecPathString()) + .build()); + + Action mergeAction = bundleMergeAction("//x:x"); + assertThat(mergeAction.getInputs()) + .containsAllOf(libsbOutputZip, xsbOutputZip, bndlsbOutputZip); + } + + protected void checkIncludesStoryboardOutputZipsAsMergeZips(BinaryRuleTypePair ruleTypePair, + BuildConfiguration nestedConfiguration) throws Exception { + scratch.file("lib/libsb.storyboard"); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("storyboards", "libsb.storyboard") + .write(); + + scratch.file("bndl/bndlsb.storyboard"); + scratch.file("bndl/BUILD", + "objc_bundle_library(", + " name = 'bndl',", + " storyboards = ['bndlsb.storyboard'],", + ")"); + + scratch.file("x/xsb.storyboard"); + ruleTypePair.scratchTargets(scratch, + "storyboards", "['xsb.storyboard']", + "deps", "['//lib:lib']", + "bundles", "['//bndl:bndl']"); + + Artifact libsbOutputZip = getBinArtifact("x/libsb.storyboard.zip", "//x:x"); + Artifact bndlsbOutputZip = getBinArtifact( + "bndl/bndlsb.storyboard.zip", getConfiguredTarget("//bndl:bndl", nestedConfiguration)); + Artifact xsbOutputZip = getBinArtifact("x/xsb.storyboard.zip", "//x:x"); + + String bundleDir = ruleTypePair.getBundleDir(); + Control mergeControl = bundleMergeControl("//x:x"); + assertThat(mergeControl.getMergeZipList()).containsExactly( + MergeZip.newBuilder() + .setEntryNamePrefix(bundleDir + "/") + .setSourcePath(libsbOutputZip.getExecPathString()) + .build(), + MergeZip.newBuilder() + .setEntryNamePrefix(bundleDir + "/") + .setSourcePath(xsbOutputZip.getExecPathString()) + .build()); + + Control nestedMergeControl = + Iterables.getOnlyElement(mergeControl.getNestedBundleList()); + assertThat(nestedMergeControl.getMergeZipList()).containsExactly( + MergeZip.newBuilder() + .setEntryNamePrefix(bundleDir + "/bndl.bundle/") + .setSourcePath(bndlsbOutputZip.getExecPathString()) + .build()); + } + + protected List<String> rootedIncludePaths( + BuildConfiguration configuration, String... unrootedPaths) { + ImmutableList.Builder<String> rootedPaths = new ImmutableList.Builder<>(); + for (String unrootedPath : unrootedPaths) { + rootedPaths.add(unrootedPath) + .add(configuration.getGenfilesFragment().getRelative(unrootedPath).getSafePathString()); + } + return rootedPaths.build(); + } + + protected void checkErrorsWrongFileTypeForSrcsWhenCompiling(RuleType ruleType) + throws Exception { + scratch.file("fg/BUILD", + "filegroup(", + " name = 'fg',", + " srcs = ['non_matching', 'matchingh.h', 'matchingc.c'],", + ")"); + checkError("x1", "x1", + "does not match expected type: " + SRCS_TYPE, + ruleType.target(scratch, "x1", "x1", + "srcs", "['//fg:fg']")); + } + + protected void prepareAlwayslinkCheck(RuleType ruleType) throws Exception { + scratch.file( + "imp1/BUILD", + "objc_import(", + " name = 'imp1',", + " archives = ['imp1.a'],", + " alwayslink = 1,", + ")"); + scratch.file("imp2/BUILD", + "objc_import(", + " name = 'imp2',", + " archives = ['imp2.a'],", + ")"); + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .set("alwayslink", 1) + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .set("alwayslink", 0) + .write(); + ruleType.scratchTarget(scratch, + "srcs", "['a.m']", + "deps", "['//imp1:imp1', '//imp2:imp2', '//lib1:lib1', '//lib2:lib2']"); + } + + protected void checkForceLoadsAlwayslinkTargets(RuleType ruleType, ExtraLinkArgs extraLinkArgs) + throws Exception { + prepareAlwayslinkCheck(ruleType); + CommandAction action = linkAction("//x:x"); + + verifyObjlist(action, "x-linker.objlist", + execPathEndingWith(action.getInputs(), "x/libx.a"), + execPathEndingWith(action.getInputs(), "lib2.a"), + execPathEndingWith(action.getInputs(), "imp2.a")); + Iterable<String> forceLoadArchives = ImmutableList.of( + execPathEndingWith(action.getInputs(), "imp1.a"), + execPathEndingWith(action.getInputs(), "lib1.a")); + assertThat(action.getArguments()) + .containsExactly( + "/bin/bash", + "-c", + Joiner.on(" ") + .join( + new ImmutableList.Builder<String>() + .add(MOCK_XCRUNWRAPPER_PATH) + .add(CLANG) + .add("-filelist") + .add(execPathEndingWith(action.getInputs(), "x-linker.objlist")) + .add("-mios-simulator-version-min=" + DEFAULT_IOS_SDK_VERSION) + .add("-arch") + .add("x86_64") + .add("-isysroot", AppleToolchain.sdkDir()) + .add( + "-F", AppleToolchain.sdkDir() + AppleToolchain.DEVELOPER_FRAMEWORK_PATH) + .add("-F", frameworkDir(getConfiguredTarget("//x:x"))) + .add("-Xlinker", "-objc_abi_version", "-Xlinker", "2") + .add("-Xlinker", "-rpath", "-Xlinker", "@executable_path/Frameworks") + .add("-fobjc-link-runtime") + .add("-ObjC") + .addAll( + Interspersing.beforeEach( + "-framework", SdkFramework.names(AUTOMATIC_SDK_FRAMEWORKS))) + .add("-o") + .addAll(Artifact.toExecPaths(action.getOutputs())) + .addAll(Interspersing.beforeEach("-force_load", forceLoadArchives)) + .addAll(extraLinkArgs) + .build())) + .inOrder(); + } + + protected void checkObjcCopts(RuleType ruleType) throws Exception { + useConfiguration("--objccopt=-foo"); + + scratch.file("x/a.m"); + ruleType.scratchTarget(scratch, "srcs", "['a.m']"); + List<String> args = compileAction("//x:x", "a.o").getArguments(); + assertThat(args).contains("-foo"); + } + + protected void checkObjcCopts_argumentOrdering(RuleType ruleType) throws Exception { + useConfiguration("--objccopt=-foo"); + + scratch.file("x/a.m"); + ruleType.scratchTarget(scratch, + "srcs", "['a.m']", + "copts", "['-bar']"); + List<String> args = compileAction("//x:x", "a.o").getArguments(); + assertThat(args).containsAllOf("-fobjc-arc", "-foo", "-bar").inOrder(); + } + + protected void checkClangCoptsForCompilationMode(RuleType ruleType, CompilationMode mode, + CodeCoverageMode codeCoverageMode) throws Exception { + ImmutableList.Builder<String> allExpectedCoptsBuilder = ImmutableList.<String>builder() + .addAll(CompilationSupport.DEFAULT_COMPILER_FLAGS) + .addAll(compilationModeCopts(mode)); + + switch (codeCoverageMode) { + case NONE: + useConfiguration("--compilation_mode=" + compilationModeFlag(mode)); + break; + case GCOV: + allExpectedCoptsBuilder.addAll(CompilationSupport.CLANG_GCOV_COVERAGE_FLAGS); + useConfiguration("--collect_code_coverage", + "--compilation_mode=" + compilationModeFlag(mode)); + break; + case LLVMCOV: + allExpectedCoptsBuilder.addAll(CompilationSupport.CLANG_LLVM_COVERAGE_FLAGS); + useConfiguration("--collect_code_coverage", "--experimental_use_llvm_covmap", + "--compilation_mode=" + compilationModeFlag(mode)); + break; + } + scratch.file("x/a.m"); + ruleType.scratchTarget(scratch, + "srcs", "['a.m']"); + + CommandAction compileActionA = compileAction("//x:x", "a.o"); + + assertThat(compileActionA.getArguments()) + .containsAllIn(allExpectedCoptsBuilder.build()); + } + + protected void checkClangCoptsForDebugModeWithoutGlib(RuleType ruleType) throws Exception { + ImmutableList.Builder<String> allExpectedCoptsBuilder = ImmutableList.<String>builder() + .addAll(CompilationSupport.DEFAULT_COMPILER_FLAGS) + .addAll(ObjcConfiguration.DBG_COPTS); + + useConfiguration("--compilation_mode=dbg", "--objc_debug_with_GLIBCXX=false"); + scratch.file("x/a.m"); + ruleType.scratchTarget(scratch, + "srcs", "['a.m']"); + + CommandAction compileActionA = compileAction("//x:x", "a.o"); + + assertThat(compileActionA.getArguments()) + .containsAllIn(allExpectedCoptsBuilder.build()).inOrder(); + + } + + protected void checkLinkopts(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, "linkopts", "['foo', 'bar']"); + + CommandAction linkAction = linkAction("//x:x"); + String linkArgs = Joiner.on(" ").join(linkAction.getArguments()); + assertThat(linkArgs).contains("-Wl,foo"); + assertThat(linkArgs).contains("-Wl,bar"); + } + + protected void checkMergesPartialInfoplists(RuleType ruleType) throws Exception { + scratch.file("x/primary-Info.plist"); + ruleType.scratchTarget(scratch, + INFOPLIST_ATTR, "'primary-Info.plist'", + "asset_catalogs", "['foo.xcassets/bar']"); + + String targetName = "//x:x"; + ConfiguredTarget target = getConfiguredTarget(targetName); + + PlMergeProtos.Control control = plMergeControl(targetName); + Artifact actoolPartial = getBinArtifact("x.actool-PartialInfo.plist", "//x:x"); + Artifact versionInfoplist = getBinArtifact("plists/x-version.plist", target); + Artifact environmentInfoplist = getBinArtifact("plists/x-environment.plist", target); + Artifact automaticInfoplist = getBinArtifact("plists/x-automatic.plist", target); + + assertPlistMergeControlUsesSourceFiles( + control, + ImmutableList.<String>of( + "x/primary-Info.plist", + versionInfoplist.getExecPathString(), + environmentInfoplist.getExecPathString(), + automaticInfoplist.getExecPathString(), + actoolPartial.getExecPathString())); + } + + protected void checkMergesPartialInfoplists(BinaryRuleTypePair ruleTypePair) throws Exception { + scratch.file("x/primary-Info.plist"); + ruleTypePair.scratchTargets(scratch, + "asset_catalogs", "['foo.xcassets/bar']", + INFOPLIST_ATTR, "'primary-Info.plist'"); + + String targetName = "//x:x"; + ConfiguredTarget target = getConfiguredTarget(targetName); + PlMergeProtos.Control control = plMergeControl(targetName); + + + Artifact merged = getBinArtifact("x-MergedInfo.plist", target); + Artifact actoolPartial = getBinArtifact("x.actool-PartialInfo.plist", "//x:x"); + + Artifact versionInfoplist = getBinArtifact("plists/x-version.plist", target); + Artifact environmentInfoplist = getBinArtifact("plists/x-environment.plist", target); + Artifact automaticInfoplist = getBinArtifact("plists/x-automatic.plist", target); + + assertPlistMergeControlUsesSourceFiles( + control, + ImmutableList.<String>of( + "x/primary-Info.plist", + versionInfoplist.getExecPathString(), + environmentInfoplist.getExecPathString(), + automaticInfoplist.getExecPathString(), + actoolPartial.getExecPathString())); + assertThat(control.getOutFile()).isEqualTo(merged.getExecPathString()); + assertThat(control.getVariableSubstitutionMapMap()) + .containsExactlyEntriesIn(getVariableSubstitutionArguments(ruleTypePair)); + assertThat(control.getFallbackBundleId()).isEqualTo("example.x"); + } + + private void addTransitiveDefinesUsage(RuleType topLevelRuleType) throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m") + .setList("defines", "A=foo", "B") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m") + .setList("deps", "//lib1:lib1") + .setList("defines", "C=bar", "D") + .write(); + + topLevelRuleType.scratchTarget(scratch, + "srcs", "['a.m']", + "non_arc_srcs", "['b.m']", + "deps", "['//lib2:lib2']", + "defines", "['E=baz']", + "copts", "['explicit_copt']"); + } + + protected void checkReceivesTransitivelyPropagatedDefines(RuleType ruleType) throws Exception { + addTransitiveDefinesUsage(ruleType); + assertContainsSublist(compileAction("//x:x", "a.o").getArguments(), + ImmutableList.of("-DA=foo", "-DB", "-DC=bar", "-DD", "-DE=baz", "explicit_copt")); + assertContainsSublist(compileAction("//x:x", "b.o").getArguments(), + ImmutableList.of("-DA=foo", "-DB", "-DC=bar", "-DD", "-DE=baz", "explicit_copt")); + } + + protected void checkDefinesFromCcLibraryDep(RuleType ruleType) throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + ScratchAttributeWriter.fromLabelString(this, "cc_library", "//dep:lib") + .setList("srcs", "a.cc") + .setList("defines", "foo", "bar") + .write(); + + ScratchAttributeWriter.fromLabelString(this, ruleType.getRuleTypeName(), "//objc:x") + .setList("srcs", "a.m") + .setList("deps", "//dep:lib") + .write(); + + CommandAction compileAction = compileAction("//objc:x", "a.o"); + assertThat(compileAction.getArguments()).containsAllOf("-Dfoo", "-Dbar"); + } + + protected void checkSdkIncludesUsedInCompileAction(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "sdk_includes", "['foo', 'bar/baz']", + "srcs", "['a.m', 'b.m']"); + String sdkIncludeDir = AppleToolchain.sdkDir() + "/usr/include"; + assertThat(compileAction("//x:x", "a.o").getArguments()) + .containsAllOf( + "-I", sdkIncludeDir + "/foo", + "-I", sdkIncludeDir + "/bar/baz") + .inOrder(); + assertThat(compileAction("//x:x", "b.o").getArguments()) + .containsAllOf( + "-I", sdkIncludeDir + "/foo", + "-I", sdkIncludeDir + "/bar/baz") + .inOrder(); + } + + protected void checkSdkIncludesUsedInCompileActionsOfDependers(RuleType ruleType) + throws Exception { + ruleType.scratchTarget(scratch, "sdk_includes", "['foo', 'bar/baz']"); + // Add some dependers (including transitive depender //bin:bin) and make sure they use the flags + // as well. + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m") + .setList("deps", "//x:x") + .setList("sdk_includes", "from_lib") + .write(); + createBinaryTargetWriter("//bin:bin") + .setAndCreateFiles("srcs", "b.m") + .setList("deps", "//lib:lib") + .setList("sdk_includes", "from_bin") + .write(); + String sdkIncludeDir = AppleToolchain.sdkDir() + "/usr/include"; + assertThat(compileAction("//lib:lib", "a.o").getArguments()) + .containsAllOf( + "-I", sdkIncludeDir + "/from_lib", + "-I", sdkIncludeDir + "/foo", + "-I", sdkIncludeDir + "/bar/baz") + .inOrder(); + assertThat(compileAction("//bin:bin", "b.o").getArguments()) + .containsAllOf( + "-I", sdkIncludeDir + "/from_bin", + "-I", sdkIncludeDir + "/from_lib", + "-I", sdkIncludeDir + "/foo", + "-I", sdkIncludeDir + "/bar/baz") + .inOrder(); + } + + protected void checkCompileXibActions( + BinaryRuleTypePair ruleTypePair, DottedVersion minimumOsVersion, + String platformType) throws Exception { + ruleTypePair.scratchTargets(scratch, "xibs", "['foo.xib', 'es.lproj/bar.xib']"); + checkCompileXibActions(minimumOsVersion, platformType); + } + + protected void checkCompileXibActions(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, "xibs", "['foo.xib', 'es.lproj/bar.xib']"); + checkCompileXibActions(DEFAULT_IOS_SDK_VERSION, "iphone"); + } + + private void checkCompileXibActions(DottedVersion minimumOsVersion, + String platformType) throws Exception { + scratch.file("x/foo.xib"); + scratch.file("x/es.lproj/bar.xib"); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + Artifact fooNibZip = getBinArtifact("x/x/foo.nib.zip", target); + Artifact barNibZip = getBinArtifact("x/x/es.lproj/bar.nib.zip", target); + CommandAction fooCompile = (CommandAction) getGeneratingAction(fooNibZip); + CommandAction barCompile = (CommandAction) getGeneratingAction(barNibZip); + + assertThat(Artifact.toExecPaths(fooCompile.getInputs())) + .containsExactly(MOCK_IBTOOLWRAPPER_PATH, "x/foo.xib"); + assertThat(Artifact.toExecPaths(barCompile.getInputs())) + .containsExactly(MOCK_IBTOOLWRAPPER_PATH, "x/es.lproj/bar.xib"); + + assertThat(fooCompile.getArguments()) + .containsExactly( + MOCK_IBTOOLWRAPPER_PATH, + fooNibZip.getExecPathString(), + "foo.nib", // archive root + "--minimum-deployment-target", minimumOsVersion.toString(), + "--module", "x", + "--target-device", platformType, + "x/foo.xib") + .inOrder(); + assertThat(barCompile.getArguments()) + .containsExactly( + MOCK_IBTOOLWRAPPER_PATH, + barNibZip.getExecPathString(), + "es.lproj/bar.nib", // archive root + "--minimum-deployment-target", minimumOsVersion.toString(), + "--module", "x", + "--target-device", platformType, + "x/es.lproj/bar.xib") + .inOrder(); + } + + protected void checkNibZipsMergedIntoBundle(RuleType ruleType) throws Exception { + scratch.file("lib/a.m"); + scratch.file("lib/lib.xib"); + scratch.file("lib/BUILD", + "objc_library(", + " name = 'lib',", + " srcs = ['a.m'],", + " xibs = ['lib.xib'],", + ")"); + scratch.file("x/foo.xib"); + scratch.file("x/es.lproj/x.xib"); + ruleType.scratchTarget(scratch, + "xibs", "['foo.xib', 'es.lproj/x.xib']", + "deps", "['//lib:lib']"); + + ConfiguredTarget xTarget = getConfiguredTarget("//x:x"); + List<Artifact> nibZips = ImmutableList.of( + getBinArtifact("x/lib/lib.nib.zip", xTarget), + getBinArtifact("x/x/foo.nib.zip", xTarget), + getBinArtifact("x/x/es.lproj/x.nib.zip", xTarget)); + List<BundleMergeProtos.MergeZip> mergeZips = new ArrayList<>(); + for (Artifact nibZip : nibZips) { + mergeZips.add(BundleMergeProtos.MergeZip.newBuilder() + .setEntryNamePrefix(getBundlePathInsideIpa(ruleType) + "/") + .setSourcePath(nibZip.getExecPathString()) + .build()); + } + + assertThat(bundleMergeAction("//x:x").getInputs()).containsAllIn(nibZips); + assertThat(bundleMergeControl("//x:x").getMergeZipList()) + .containsAllIn(mergeZips); + } + + protected void checkNibZipsMergedIntoBundle(BinaryRuleTypePair ruleTypePair) throws Exception { + scratch.file("lib/a.m"); + scratch.file("lib/lib.xib"); + scratch.file("lib/BUILD", + "objc_library(", + " name = 'lib',", + " srcs = ['a.m'],", + " xibs = ['lib.xib'],", + ")"); + scratch.file("x/foo.xib"); + scratch.file("x/es.lproj/bar.xib"); + ruleTypePair.scratchTargets(scratch, + "xibs", "['foo.xib', 'es.lproj/bar.xib']", + "deps", "['//lib:lib']"); + + ConfiguredTarget bundlingTarget = getConfiguredTarget("//x:x"); + List<Artifact> nibZips = ImmutableList.of( + getBinArtifact("x/lib/lib.nib.zip", bundlingTarget), + getBinArtifact("x/x/foo.nib.zip", bundlingTarget), + getBinArtifact("x/x/es.lproj/bar.nib.zip", bundlingTarget)); + List<BundleMergeProtos.MergeZip> mergeZips = new ArrayList<>(); + for (Artifact nibZip : nibZips) { + mergeZips.add(BundleMergeProtos.MergeZip.newBuilder() + .setEntryNamePrefix(ruleTypePair.getBundleDir() + "/") + .setSourcePath(nibZip.getExecPathString()) + .build()); + } + + assertThat(bundleMergeAction("//x:x").getInputs()).containsAllIn(nibZips); + assertThat(bundleMergeControl("//x:x").getMergeZipList()) + .containsAllIn(mergeZips); + } + + protected void checkBinaryLipoActionMultiCpu( + BinaryRuleTypePair ruleTypePair, ConfigurationDistinguisher configurationDistinguisher) + throws Exception { + useConfiguration("--ios_multi_cpus=i386,x86_64", "--cpu=ios_armv7"); + ruleTypePair.scratchTargets(scratch); + + CommandAction action = (CommandAction) getGeneratingAction( + getBinArtifact("x_lipobin", getConfiguredTarget("//x:x"))); + + String i386Prefix = configurationBin("i386", configurationDistinguisher); + String x8664Prefix = configurationBin("x86_64", configurationDistinguisher); + assertThat(Artifact.toExecPaths(action.getInputs())) + .containsExactly( + i386Prefix + "x/bin_bin", + x8664Prefix + "x/bin_bin", + MOCK_XCRUNWRAPPER_PATH); + + assertThat(action.getArguments()) + .containsExactly(MOCK_XCRUNWRAPPER_PATH, LIPO, + "-create", i386Prefix + "x/bin_bin", x8664Prefix + "x/bin_bin", + "-o", execPathEndingWith(action.getOutputs(), "x_lipobin")) + .inOrder(); + } + + protected void checkBinaryActionMultiPlatform_fails(BinaryRuleTypePair ruleTypePair) + throws Exception { + useConfiguration( + "--ios_multi_cpus=i386,x86_64,armv7,arm64", "--watchos_cpus=armv7k", "--cpu=ios_armv7"); + ruleTypePair.scratchTargets(scratch); + + try { + getConfiguredTarget("//x:x"); + fail("Multiplatform binary should have failed"); + } catch (AssertionError expected) { + assertThat(expected) + .hasMessageThat() + .contains( + "--ios_multi_cpus does not currently allow values for both simulator and device " + + "builds."); + } + } + + protected void checkMultiCpuResourceInheritance(BinaryRuleTypePair ruleTypePair) + throws Exception { + useConfiguration("--ios_multi_cpus=i386,x86_64"); + ruleTypePair.scratchTargets(scratch, "resources", "['foo.png']"); + + assertThat(Artifact.toRootRelativePaths(bundleMergeAction("//x:x").getInputs())) + .containsExactly( + "x/foo.png", + "x/x_lipobin", + "tools/objc/bundlemerge", + "x/x.ipa-control", + "x/x-MergedInfo.plist"); + } + + public void checkAllowVariousNonBlacklistedTypesInHeaders(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, "hdrs", "['foo.foo', 'NoExtension', 'bar.inc', 'baz.hpp']"); + assertThat(view.hasErrors(getConfiguredTarget("//x:x"))).isFalse(); + } + + public void checkWarningForBlacklistedTypesInHeaders(RuleType ruleType) throws Exception { + checkWarning("x1", "x1", + "file 'foo.a' from target '//x1:foo.a' is not allowed in hdrs", + ruleType.target(scratch, "x1", "x1", "hdrs", "['foo.a']")); + checkWarning("x2", "x2", + "file 'bar.o' from target '//x2:bar.o' is not allowed in hdrs", + ruleType.target(scratch, "x2", "x2", "hdrs", "['bar.o']")); + } + + public void checkCppSourceCompilesWithCppFlags(RuleType ruleType) throws Exception { + useConfiguration("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); + + ruleType.scratchTarget( + scratch, "srcs", "['a.mm', 'b.cc', 'c.mm', 'd.cxx', 'e.c', 'f.m', 'g.C']"); + assertThat(compileAction("//x:x", "a.o").getArguments()).contains("-stdlib=libc++"); + assertThat(compileAction("//x:x", "b.o").getArguments()).contains("-stdlib=libc++"); + assertThat(compileAction("//x:x", "c.o").getArguments()).contains("-stdlib=libc++"); + assertThat(compileAction("//x:x", "d.o").getArguments()).contains("-stdlib=libc++"); + assertThat(compileAction("//x:x", "e.o").getArguments()).doesNotContain("-stdlib=libc++"); + assertThat(compileAction("//x:x", "f.o").getArguments()).doesNotContain("-stdlib=libc++"); + assertThat(compileAction("//x:x", "g.o").getArguments()).contains("-stdlib=libc++"); + + // Also test that --std=gnu++11 is provided whenever -stdlib=libc++ is. + assertThat(compileAction("//x:x", "a.o").getArguments()).contains("-std=gnu++11"); + assertThat(compileAction("//x:x", "b.o").getArguments()).contains("-std=gnu++11"); + assertThat(compileAction("//x:x", "c.o").getArguments()).contains("-std=gnu++11"); + assertThat(compileAction("//x:x", "d.o").getArguments()).contains("-std=gnu++11"); + assertThat(compileAction("//x:x", "e.o").getArguments()).doesNotContain("-std=gnu++11"); + assertThat(compileAction("//x:x", "f.o").getArguments()).doesNotContain("-std=gnu++11"); + assertThat(compileAction("//x:x", "g.o").getArguments()).contains("-std=gnu++11"); + } + + public void checkBundleIdPassedAsFallbackId(RuleType ruleType) throws Exception { + scratch.file("bin/a.m"); + scratch.file("bin/Info.plist"); + + ruleType.scratchTarget(scratch, + INFOPLIST_ATTR, "'Info.plist'"); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + + assertThat(control.hasPrimaryBundleIdentifier()).isFalse(); + assertThat(control.getFallbackBundleIdentifier()).isEqualTo("example.x"); + } + + public void checkBundleIdPassedAsPrimaryId(RuleType ruleType) throws Exception { + scratch.file("bin/a.m"); + scratch.file("bin/Info.plist"); + + ruleType.scratchTarget(scratch, + INFOPLIST_ATTR, "'Info.plist'", + BUNDLE_ID_ATTR, "'com.bundle.id'"); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + + assertThat(control.getPrimaryBundleIdentifier()).isEqualTo("com.bundle.id"); + assertThat(control.hasFallbackBundleIdentifier()).isFalse(); + } + + protected void checkPrimaryBundleIdInMergedPlist(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + INFOPLIST_ATTR, "'Info.plist'", + BUNDLE_ID_ATTR, "'com.bundle.id'"); + scratch.file("bin/Info.plist"); + checkBundleIdFlagsInPlistMergeAction( + Optional.of("com.bundle.id"), getVariableSubstitutionArgumentsDefaultFormat(ruleType)); + } + + protected void checkPrimaryBundleIdInMergedPlist(BinaryRuleTypePair ruleTypePair) + throws Exception { + ruleTypePair.scratchTargets(scratch, + INFOPLIST_ATTR, "'Info.plist'", + BUNDLE_ID_ATTR, "'com.bundle.id'"); + scratch.file("bin/Info.plist"); + checkBundleIdFlagsInPlistMergeAction( + Optional.of("com.bundle.id"), getVariableSubstitutionArguments(ruleTypePair)); + } + + protected void checkFallbackBundleIdInMergedPlist(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + INFOPLIST_ATTR, "'Info.plist'"); + scratch.file("bin/Info.plist"); + + checkBundleIdFlagsInPlistMergeAction( + Optional.<String>absent(), getVariableSubstitutionArgumentsDefaultFormat(ruleType)); + } + + protected void checkFallbackBundleIdInMergedPlist(BinaryRuleTypePair ruleTypePair) + throws Exception { + ruleTypePair.scratchTargets(scratch, + INFOPLIST_ATTR, "'Info.plist'"); + scratch.file("bin/Info.plist"); + + checkBundleIdFlagsInPlistMergeAction( + Optional.<String>absent(), getVariableSubstitutionArguments(ruleTypePair)); + } + + protected void checkBundleIdFlagsInPlistMergeAction( + Optional<String> specifiedBundleId, Map<String, String> variableSubstitutions) + throws Exception { + checkBundleIdFlagsInPlistMergeAction(specifiedBundleId, variableSubstitutions, + "example.x"); + } + protected void checkBundleIdFlagsInPlistMergeAction( + Optional<String> specifiedBundleId, Map<String, String> variableSubstitutions, + String defaultBundleId) throws Exception { + String targetName = "//x:x"; + PlMergeProtos.Control control = plMergeControl(targetName); + ConfiguredTarget target = getConfiguredTarget(targetName); + Artifact mergedPlist = getMergedInfoPlist(target); + String bundleIdToCheck = specifiedBundleId.or(defaultBundleId); + Artifact versionInfoplist = getBinArtifact("plists/x" + artifactName("-version.plist"), target); + Artifact environmentInfoplist = getBinArtifact("plists/x" + artifactName("-environment.plist"), + target); + Artifact automaticInfoplist = getBinArtifact("plists/x" + artifactName("-automatic.plist"), + target); + + assertPlistMergeControlUsesSourceFiles( + control, + ImmutableList.<String>of( + "x/Info.plist", + versionInfoplist.getExecPathString(), + environmentInfoplist.getExecPathString(), + automaticInfoplist.getExecPathString())); + assertThat(control.getOutFile()).isEqualTo(mergedPlist.getExecPathString()); + assertThat(control.getVariableSubstitutionMapMap()) + .containsExactlyEntriesIn(variableSubstitutions); + + if (specifiedBundleId.isPresent()) { + assertThat(control.hasPrimaryBundleId()).isTrue(); + assertThat(control.getPrimaryBundleId()).isEqualTo(bundleIdToCheck); + } else { + assertThat(control.hasFallbackBundleId()).isTrue(); + assertThat(control.getFallbackBundleId()).isEqualTo(bundleIdToCheck); + } + } + + protected void checkSigningSimulatorBuild(BinaryRuleTypePair ruleTypePair, boolean useMultiCpu) + throws Exception { + if (useMultiCpu) { + useConfiguration("--ios_multi_cpus=i386,x86_64", "--cpu=ios_i386"); + } else { + useConfiguration("--cpu=ios_i386"); + } + + ruleTypePair.scratchTargets(scratch); + + SpawnAction ipaGeneratingAction = (SpawnAction) ipaGeneratingAction(); + assertThat(ActionsTestUtil.baseArtifactNames(ipaGeneratingAction.getInputs())) + .containsExactly("x.unprocessed.ipa"); + + assertThat(normalizeBashArgs(ipaGeneratingAction.getArguments())) + .containsAllOf("/usr/bin/codesign", "--sign", "\"-\"") + .inOrder(); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + assertThat(mobileProvisionProfiles(control)).isEmpty(); + } + + protected Action ipaGeneratingAction() throws Exception { + return getGeneratingActionForLabel("//x:x.ipa"); + } + + protected void checkProvisioningProfileDeviceBuild( + BinaryRuleTypePair ruleTypePair, boolean useMultiCpu) throws Exception { + if (useMultiCpu) { + useConfiguration("--ios_multi_cpus=armv7,arm64", "--cpu=ios_i386", "--watchos_cpus=armv7k"); + } else { + useConfiguration("--cpu=ios_armv7", "--watchos_cpus=armv7k"); + } + + ruleTypePair.scratchTargets(scratch); + + Artifact provisioningProfile = + getFileConfiguredTarget("//tools/objc:foo.mobileprovision").getArtifact(); + assertThat(ipaGeneratingAction().getInputs()).contains(provisioningProfile); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + Map<String, String> profiles = mobileProvisionProfiles(control); + ImmutableMap<String, String> expectedProfiles = ImmutableMap.of( + provisioningProfile.getExecPathString(), + ReleaseBundlingSupport.PROVISIONING_PROFILE_BUNDLE_FILE); + assertThat(profiles).isEqualTo(expectedProfiles); + } + + protected void addCustomProvisioningProfile(BinaryRuleTypePair ruleTypePair, + String provisioningProfileAttribute) throws Exception { + scratch.file("custom/BUILD", "exports_files(['pp.mobileprovision'])"); + scratch.file("custom/pp.mobileprovision"); + ruleTypePair.scratchTargets( + scratch, provisioningProfileAttribute, "'//custom:pp.mobileprovision'"); + } + + protected void checkProvisioningProfileUserSpecified( + BinaryRuleTypePair ruleTypePair, boolean useMultiCpu) throws Exception { + checkProvisioningProfileUserSpecified(ruleTypePair, useMultiCpu, PROVISIONING_PROFILE_ATTR); + } + protected void checkProvisioningProfileUserSpecified( + BinaryRuleTypePair ruleTypePair, boolean useMultiCpu, + String provisioningProfileAttribute) throws Exception { + if (useMultiCpu) { + useConfiguration("--ios_multi_cpus=armv7,arm64", "--cpu=ios_i386", "--watchos_cpus=armv7k"); + } else { + useConfiguration("--cpu=ios_armv7", "--watchos_cpus=armv7k"); + } + addCustomProvisioningProfile(ruleTypePair, provisioningProfileAttribute); + + Artifact defaultProvisioningProfile = + getFileConfiguredTarget("//tools/objc:foo.mobileprovision").getArtifact(); + Artifact customProvisioningProfile = + getFileConfiguredTarget("//custom:pp.mobileprovision").getArtifact(); + Action signingAction = ipaGeneratingAction(); + assertThat(signingAction.getInputs()).contains(customProvisioningProfile); + assertThat(signingAction.getInputs()).doesNotContain(defaultProvisioningProfile); + + BundleMergeProtos.Control control = bundleMergeControl("//x:x"); + Map<String, String> profiles = mobileProvisionProfiles(control); + Map<String, String> expectedProfiles = ImmutableMap.of( + customProvisioningProfile.getExecPathString(), + ReleaseBundlingSupport.PROVISIONING_PROFILE_BUNDLE_FILE); + assertThat(profiles).isEqualTo(expectedProfiles); + } + + protected void checkMergeBundleAction(BinaryRuleTypePair ruleTypePair) throws Exception { + ruleTypePair.scratchTargets(scratch, + INFOPLIST_ATTR, "'Info.plist'"); + SpawnAction action = bundleMergeAction("//x:x"); + assertThat(Artifact.toRootRelativePaths(action.getInputs())) + .containsExactly( + MOCK_BUNDLEMERGE_PATH, + "x/x_lipobin", + "x/x.ipa-control", + "x/x-MergedInfo.plist"); + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("x/x.unprocessed.ipa"); + assertNotRequiresDarwin(action); + assertThat(action.getEnvironment()).isEmpty(); + assertThat(action.getArguments()) + .containsExactly( + MOCK_BUNDLEMERGE_PATH, + execPathEndingWith(action.getInputs(), "x.ipa-control")) + .inOrder(); + } + + protected void checkCollectsAssetCatalogsTransitively(BinaryRuleTypePair ruleTypePair) + throws Exception { + scratch.file("lib/ac.xcassets/foo"); + scratch.file("lib/ac.xcassets/bar"); + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .set("asset_catalogs", "glob(['ac.xcassets/**'])") + .write(); + + scratch.file("x/ac.xcassets/baz"); + scratch.file("x/ac.xcassets/42"); + ruleTypePair.scratchTargets(scratch, + "deps", "['//lib:lib']", + "asset_catalogs", "glob(['ac.xcassets/**'])"); + + // Test that the actoolzip Action has arguments and inputs obtained from dependencies. + SpawnAction actoolZipAction = actoolZipActionForIpa("//x:x"); + assertThat(Artifact.toExecPaths(actoolZipAction.getInputs())).containsExactly( + "lib/ac.xcassets/foo", "lib/ac.xcassets/bar", "x/ac.xcassets/baz", "x/ac.xcassets/42", + MOCK_ACTOOLWRAPPER_PATH); + assertContainsSublist(actoolZipAction.getArguments(), + ImmutableList.of("lib/ac.xcassets", "x/ac.xcassets")); + } + + protected void checkMergeActionsWithAssetCatalog(BinaryRuleTypePair ruleTypePair) + throws Exception { + Artifact actoolZipOut = getBinArtifact("x.actool.zip", "//x:x"); + assertThat(bundleMergeAction("//x:x").getInputs()).contains(actoolZipOut); + + BundleMergeProtos.Control mergeControl = bundleMergeControl("//x:x"); + assertThat(mergeControl.getMergeZipList()) + .containsExactly(MergeZip.newBuilder() + .setEntryNamePrefix(ruleTypePair.getBundleDir() + "/") + .setSourcePath(actoolZipOut.getExecPathString()) + .build()); + } + + protected void checkBundleablesAreMerged( + String bundlingTarget, ListMultimap<String, String> artifactAndBundlePaths) throws Exception { + BundleMergeProtos.Control control = bundleMergeControl(bundlingTarget); + Action mergeBundleAction = bundleMergeAction(bundlingTarget); + List<BundleFile> expectedBundleFiles = new ArrayList<>(); + for (Map.Entry<String, String> path : artifactAndBundlePaths.entries()) { + Artifact artifact = getFirstArtifactEndingWith(mergeBundleAction.getInputs(), path.getKey()); + expectedBundleFiles.add(BundleFile.newBuilder() + .setBundlePath(path.getValue()) + .setSourceFile(artifact.getExecPathString()) + .setExternalFileAttribute(BundleableFile.DEFAULT_EXTERNAL_FILE_ATTRIBUTE) + .build()); + } + assertThat(control.getBundleFileList()).containsAllIn(expectedBundleFiles); + } + + protected void checkNestedBundleInformationPropagatedToDependers(RuleType ruleType) + throws Exception { + scratch.file("bndl/bndl-Info.plist"); + scratch.file("bndl/bndl.png"); + scratch.file("bndl/BUILD", + "objc_bundle_library(", + " name = 'bndl',", + " infoplist = 'bndl-Info.plist',", + " resources = ['bndl.png'],", + ")"); + + ruleType.scratchTarget(scratch, "bundles", "['//bndl:bndl']"); + + scratch.file("bin/bin.m"); + scratch.file("bin/BUILD", + "objc_binary(", + " name = 'bin',", + " srcs = ['bin.m'],", + " deps = ['//x:x'],", + ")"); + + assertThat(bundleMergeAction("//bin:bin").getInputs()) + .containsAllOf( + getSourceArtifact("bndl/bndl-Info.plist"), getSourceArtifact("bndl/bndl.png")); + + BundleMergeProtos.Control binControl = bundleMergeControl("//bin:bin"); + BundleMergeProtos.Control bundleControl = + Iterables.getOnlyElement(binControl.getNestedBundleList()); + + assertThat(bundleControl.getBundleInfoPlistFile()).isEqualTo("bndl/bndl-Info.plist"); + + assertThat(bundleControl.getBundleFileList()) + .containsExactly(BundleMergeProtos.BundleFile.newBuilder() + .setBundlePath("bndl.png") + .setSourceFile("bndl/bndl.png") + .setExternalFileAttribute(BundleableFile.DEFAULT_EXTERNAL_FILE_ATTRIBUTE) + .build()); + } + + protected void checkConvertStringsAction(BinaryRuleTypePair ruleTypePair) throws Exception { + scratch.file("lib/foo.strings"); + scratch.file("lib/es.lproj/bar.strings"); + ruleTypePair.scratchTargets(scratch, "strings", "['foo.strings', 'es.lproj/bar.strings']"); + + ConfiguredTarget target = getConfiguredTarget("//x:x"); + Artifact binaryFoo = getBinArtifact("x/foo.strings.binary", target); + Artifact binaryBar = getBinArtifact("x/es.lproj/bar.strings.binary", target); + + CommandAction fooAction = (CommandAction) getGeneratingAction(binaryFoo); + CommandAction barAction = (CommandAction) getGeneratingAction(binaryBar); + + assertThat(fooAction.getOutputs()) + .containsExactly(binaryFoo); + assertThat(Artifact.toExecPaths(fooAction.getInputs())) + .containsExactly("x/foo.strings", MOCK_XCRUNWRAPPER_PATH); + + assertThat(barAction.getOutputs()) + .containsExactly(binaryBar); + assertThat(Artifact.toExecPaths(barAction.getInputs())) + .containsExactly("x/es.lproj/bar.strings", MOCK_XCRUNWRAPPER_PATH); + } + + protected void checkMomczipActions( + BinaryRuleTypePair ruleTypePair, DottedVersion minimumOsVersion) throws Exception { + createLibraryTargetWriter("//lib:lib") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setList("datamodels", "foo.xcdatamodel/1") + .write(); + ruleTypePair.scratchTargets(scratch, + "deps", "['//lib:lib']", + "datamodels", "['bar.xcdatamodeld/barx.xcdatamodel/2']"); + + AppleConfiguration configuration = + getTargetConfiguration().getFragment(AppleConfiguration.class); + + Action mergeBundleAction = bundleMergeAction("//x:x"); + Artifact fooMomZip = getFirstArtifactEndingWith(mergeBundleAction.getInputs(), "x/foo.zip"); + CommandAction fooMomczipAction = (CommandAction) getGeneratingAction(fooMomZip); + Artifact barMomZip = getFirstArtifactEndingWith(mergeBundleAction.getInputs(), "x/bar.zip"); + CommandAction barMomczipAction = (CommandAction) getGeneratingAction(barMomZip); + + assertThat(Artifact.toExecPaths(fooMomczipAction.getInputs())) + .containsExactly("lib/foo.xcdatamodel/1", MOCK_MOMCWRAPPER_PATH); + assertThat(fooMomczipAction.getOutputs()).containsExactly(fooMomZip); + assertThat(Artifact.toExecPaths(barMomczipAction.getInputs())) + .containsExactly("x/bar.xcdatamodeld/barx.xcdatamodel/2", MOCK_MOMCWRAPPER_PATH); + assertThat(barMomczipAction.getOutputs()).containsExactly(barMomZip); + + ImmutableList<String> commonMomcZipArguments = ImmutableList.of( + "-XD_MOMC_SDKROOT=" + AppleToolchain.sdkDir(), + "-XD_MOMC_IOS_TARGET_VERSION=" + minimumOsVersion, + "-MOMC_PLATFORMS", + configuration.getMultiArchPlatform(PlatformType.IOS).getLowerCaseNameInPlist(), + "-XD_MOMC_TARGET_VERSION=10.6"); + + assertThat(fooMomczipAction.getArguments()) + .isEqualTo( + new ImmutableList.Builder<String>() + .add(MOCK_MOMCWRAPPER_PATH) + .add(fooMomZip.getExecPathString()) + .add("foo.mom") + .addAll(commonMomcZipArguments) + .add("lib/foo.xcdatamodel") + .build()); + assertThat(barMomczipAction.getArguments()) + .isEqualTo( + new ImmutableList.Builder<String>() + .add(MOCK_MOMCWRAPPER_PATH) + .add(barMomZip.getExecPathString()) + .add("bar.momd") + .addAll(commonMomcZipArguments) + .add("x/bar.xcdatamodeld") + .build()); + } + + protected void addCommonResources(BinaryRuleTypePair ruleTypePair) throws Exception { + scratch.file("x/Model.xcdatamodeld/Model-1.0.xcdatamodel/contents"); + ruleTypePair.scratchTargets(scratch, + "strings", "['foo.strings']", + "xibs", "['bar.xib']", + "storyboards", "['baz.storyboard']", + "datamodels", "glob(['Model.xcdatamodeld/**'])"); + } + + protected void checkMultiCpuCompiledResources(BinaryRuleTypePair ruleTypePair) throws Exception { + useConfiguration("--ios_multi_cpus=armv7,arm64", "--watchos_cpus=armv7k"); + addCommonResources(ruleTypePair); + + BundleMergeProtos.Control topControl = bundleMergeControl("//x:x"); + ImmutableList.Builder<String> bundlePaths = ImmutableList.builder(); + for (BundleMergeProtos.BundleFile file : topControl.getBundleFileList()) { + bundlePaths.add(file.getBundlePath()); + } + assertThat(bundlePaths.build()).containsNoDuplicates(); + + ImmutableList.Builder<String> mergeZipNames = ImmutableList.builder(); + for (BundleMergeProtos.MergeZip zip : topControl.getMergeZipList()) { + mergeZipNames.add(Iterables.getLast(Splitter.on('/').split(zip.getSourcePath()))); + } + assertThat(mergeZipNames.build()).containsNoDuplicates(); + } + + protected void checkMultiCpuCompiledResourcesFromGenrule(BinaryRuleTypePair ruleTypePair) + throws Exception { + useConfiguration("--ios_multi_cpus=armv7,arm64", "--watchos_cpus=armv7k"); + + String targets = + ruleTypePair.targets(scratch, "x", "strings", "['Resources/en.lproj/foo.strings']"); + scratch.file("x/foo.strings"); + scratch.file("x/BUILD", + "genrule(", + " name = 'gen',", + " srcs = ['foo.strings'],", + " outs = ['Resources/en.lproj/foo.strings'],", + " cmd = 'cp $(location foo.strings) $(location Resources/en.lproj/foo.strings)'", + ")", + targets); + + BundleMergeProtos.Control topControl = bundleMergeControl("//x:x"); + ImmutableList.Builder<String> bundlePaths = ImmutableList.builder(); + for (BundleMergeProtos.BundleFile file : topControl.getBundleFileList()) { + bundlePaths.add(file.getBundlePath()); + } + assertThat(bundlePaths.build()).containsNoDuplicates(); + } + + protected void checkMultiCpuGeneratedResourcesFromGenrule(BinaryRuleTypePair ruleTypePair) + throws Exception { + useConfiguration("--ios_multi_cpus=armv7,arm64", "--watchos_cpus=armv7k"); + + String targets = ruleTypePair.targets(scratch, "x", "resources", "[':gen']"); + scratch.file( + "x/BUILD", + "genrule(", + " name = 'gen',", + " srcs = ['foo'],", + " outs = ['foo.res'],", + " cmd = 'cp $(location foo) $(location foo.res)'", + ")", + targets); + + BundleMergeProtos.Control topControl = bundleMergeControl("//x:x"); + ImmutableList.Builder<String> bundlePaths = ImmutableList.builder(); + for (BundleMergeProtos.BundleFile file : topControl.getBundleFileList()) { + bundlePaths.add(file.getBundlePath()); + } + assertThat(bundlePaths.build()).containsNoDuplicates(); + } + + protected void checkTwoStringsOneBundlePath(BinaryRuleTypePair ruleTypePair, String errorTarget) + throws Exception { + String targets = ruleTypePair.targets(scratch, "x", + "strings", "['Resources/en.lproj/foo.strings', 'FooBar/en.lproj/foo.strings']"); + checkTwoStringsOneBundlePath(targets, errorTarget); + } + + protected void checkTwoStringsOneBundlePath(RuleType ruleType) throws Exception { + String targets = ruleType.target(scratch, "x", "bndl", + "strings", "['Resources/en.lproj/foo.strings', 'FooBar/en.lproj/foo.strings']"); + checkTwoStringsOneBundlePath(targets, "bndl"); + } + + private void checkTwoStringsOneBundlePath(String targets, String errorTarget) throws Exception { + checkError( + "x", + errorTarget, + "Two files map to the same path [en.lproj/foo.strings] in this bundle but come from " + + "different locations: //x:Resources/en.lproj/foo.strings and " + + "//x:FooBar/en.lproj/foo.strings", + targets); + } + + protected void checkTwoResourcesOneBundlePath(RuleType ruleType) throws Exception { + String targets = ruleType.target(scratch, "x", "bndl", "resources", "['baz/foo', 'bar/foo']"); + checkTwoResourcesOneBundlePath(targets, "bndl"); + } + + protected void checkTwoResourcesOneBundlePath(BinaryRuleTypePair ruleTypePair, String errorTarget) + throws Exception { + String targets = ruleTypePair.targets(scratch, "x", "resources", "['baz/foo', 'bar/foo']"); + checkTwoResourcesOneBundlePath(targets, errorTarget); + } + + private void checkTwoResourcesOneBundlePath(String targets, String errorTarget) throws Exception { + checkError( + "x", + errorTarget, + "Two files map to the same path [foo] in this bundle but come from " + + "different locations: //x:baz/foo and //x:bar/foo", + targets); + } + + protected void checkSameStringsTwice(RuleType ruleType) throws Exception { + String targets = + ruleType.target( + scratch, + "x", + "bndl", + "resources", + "['Resources/en.lproj/foo.strings']", + "strings", + "['Resources/en.lproj/foo.strings']"); + checkSameStringsTwice(targets, "bndl"); + } + + protected void checkSameStringsTwice(BinaryRuleTypePair ruleTypePair, String errorTarget) + throws Exception { + String targets = + ruleTypePair.targets( + scratch, + "x", + "resources", + "['Resources/en.lproj/foo.strings']", + "strings", + "['Resources/en.lproj/foo.strings']"); + checkSameStringsTwice(targets, errorTarget); + } + + private void checkSameStringsTwice(String targets, String errorTarget) throws Exception { + checkError( + "x", + errorTarget, + "The same file was included multiple times in this rule: x/Resources/en.lproj/foo.strings", + targets); + } + + protected Artifact getMergedInfoPlist(ConfiguredTarget target) { + return getBinArtifact(target.getLabel().getName() + artifactName("-MergedInfo.plist"), + target); + } + + protected void checkTargetHasDebugSymbols(RuleType ruleType) throws Exception { + useConfiguration("--apple_generate_dsym"); + ruleType.scratchTarget(scratch); + + Iterable<Artifact> filesToBuild = + getConfiguredTarget("//x:x").getProvider(FileProvider.class).getFilesToBuild(); + assertThat(filesToBuild) + .containsAllOf( + getBinArtifact("x.app.dSYM/Contents/Resources/DWARF/x_bin", "//x:x"), + getBinArtifact("x.app.dSYM/Contents/Info.plist", "//x:x")); + } + + protected void checkTargetHasCpuSpecificDsymFiles(RuleType ruleType) throws Exception { + useConfiguration("--ios_multi_cpus=armv7,arm64", "--apple_generate_dsym"); + ruleType.scratchTarget(scratch); + + List<Artifact> debugArtifacts = new ArrayList<>(); + debugArtifacts.add(getBinArtifact("x.app.dSYM/Contents/Resources/DWARF/x_armv7", "//x:x")); + debugArtifacts.add(getBinArtifact("x.app.dSYM/Contents/Resources/DWARF/x_arm64", "//x:x")); + + Iterable<Artifact> filesToBuild = + getConfiguredTarget("//x:x").getProvider(FileProvider.class).getFilesToBuild(); + assertThat(filesToBuild).containsAllIn(debugArtifacts); + } + + protected void checkTargetHasDsymPlist(RuleType ruleType) throws Exception { + useConfiguration("--ios_multi_cpus=armv7,arm64", "--apple_generate_dsym"); + ruleType.scratchTarget(scratch); + + Iterable<Artifact> filesToBuild = + getConfiguredTarget("//x:x").getProvider(FileProvider.class).getFilesToBuild(); + assertThat(filesToBuild).contains(getBinArtifact("x.app.dSYM/Contents/Info.plist", "//x:x")); + } + + protected void checkCcDependency(BinaryRuleTypePair ruleTypePair, + ConfigurationDistinguisher configurationDistinguisher) throws Exception { + ruleTypePair.scratchTargets(scratch, "deps", "['//lib:cclib']"); + checkCcDependency(configurationDistinguisher, "bin"); + } + + /** + * @param extraAttrs pairs of key-value strings (must be an even number) which will be added as + * extra attributes to the target generated for this test + */ + protected void checkCcDependency(RuleType ruleType, String... extraAttrs) throws Exception { + List<String> attrs = + ImmutableList.<String>builder() + .add("deps") + .add("['//lib:cclib']") + .add(extraAttrs) + .build(); + ruleType.scratchTarget(scratch, attrs.toArray(new String[0])); + checkCcDependency(ConfigurationDistinguisher.UNKNOWN, "x"); + } + + private void checkCcDependency( + ConfigurationDistinguisher configurationDistinguisher, String targetName) throws Exception { + useConfiguration( + "--experimental_disable_go", + "--experimental_disable_jvm", + "--cpu=ios_i386", + "--crosstool_top=//tools/osx/crosstool:crosstool"); + + scratch.file("lib/BUILD", + "cc_library(", + " name = 'cclib',", + " srcs = ['dep.c'],", + ")"); + + Action appLipoAction = lipoBinAction("//x:x"); + + CommandAction binBinAction = (CommandAction) getGeneratingAction( + getFirstArtifactEndingWith(appLipoAction.getInputs(), targetName + "_bin")); + + verifyObjlist( + binBinAction, String.format("%s-linker.objlist", targetName), + "lib/libcclib.a", String.format("x/lib%s.a", targetName)); + + assertThat(Artifact.toExecPaths(binBinAction.getInputs())) + .containsAllOf( + iosConfigurationCcDepsBin("i386", configurationDistinguisher) + "lib/libcclib.a", + iosConfigurationCcDepsBin("i386", configurationDistinguisher) + + String.format("x/lib%s.a", targetName), + iosConfigurationCcDepsBin("i386", configurationDistinguisher) + + String.format("x/%s-linker.objlist", targetName)); + } + + protected void checkCcDependencyMultiArch(BinaryRuleTypePair ruleTypePair, + ConfigurationDistinguisher configurationDistinguisher) throws Exception { + useConfiguration("--experimental_disable_go", "--experimental_disable_jvm", + "--ios_multi_cpus=armv7,arm64", "--crosstool_top=//tools/osx/crosstool:crosstool"); + + scratch.file("lib/BUILD", + "cc_library(", + " name = 'cclib',", + " srcs = ['dep.c'],", + ")"); + ruleTypePair.scratchTargets(scratch, "deps", "['//lib:cclib']"); + + CommandAction appLipoAction = (CommandAction) getGeneratingAction( + getBinArtifact("x_lipobin", getConfiguredTarget("//x:x", targetConfig))); + + assertThat(Artifact.toExecPaths(appLipoAction.getInputs())) + .containsAllOf( + configurationBin("armv7", configurationDistinguisher) + "x/bin_bin", + configurationBin("arm64", configurationDistinguisher) + "x/bin_bin"); + + ImmutableSet.Builder<Artifact> binInputs = ImmutableSet.builder(); + for (Artifact bin : appLipoAction.getInputs()) { + CommandAction binAction = (CommandAction) getGeneratingAction(bin); + if (binAction != null) { + binInputs.addAll(binAction.getInputs()); + verifyObjlist(binAction, "x/bin-linker.objlist", + "x/libbin.a", "lib/libcclib.a"); + } + } + + assertThat(Artifact.toExecPaths(binInputs.build())) + .containsAllOf( + configurationBin("armv7", configurationDistinguisher) + "x/libbin.a", + configurationBin("arm64", configurationDistinguisher) + "x/libbin.a", + configurationBin("armv7", configurationDistinguisher) + + "lib/libcclib.a", + configurationBin("arm64", configurationDistinguisher) + + "lib/libcclib.a", + configurationBin("armv7", configurationDistinguisher) + + "x/bin-linker.objlist", + configurationBin("arm64", configurationDistinguisher) + + "x/bin-linker.objlist"); + } + + protected void checkGenruleDependency(BinaryRuleTypePair ruleTypePair) throws Exception { + checkGenruleDependency(ruleTypePair.targets(scratch, "x", "srcs", "['gen.m']")); + } + + protected void checkGenruleDependency(RuleType ruleType) throws Exception { + checkGenruleDependency(ruleType.target(scratch, "x", "bin", "srcs", "['gen.m']")); + } + + private void checkGenruleDependency(String targets) throws Exception { + scratch.file("x/BUILD", + "genrule(", + " name = 'gen',", + " srcs = [],", + " outs = ['gen.m'],", + " cmd = '\\'\\' > $(location gen.m)'", + ")", + targets); + + CommandAction binBinAction = (CommandAction) + getGeneratingAction(getConfiguredTarget("//x:bin"), "x/bin_bin"); + Artifact libBin = getFirstArtifactEndingWith(binBinAction.getInputs(), "libbin.a"); + Action libBinAction = getGeneratingAction(libBin); + Action genOAction = + getGeneratingAction(Iterables.getOnlyElement(inputsEndingWith(libBinAction, ".o"))); + + assertThat(Artifact.toExecPaths(genOAction.getInputs())) + .containsExactly( + configurationGenfiles( + "x86_64", + ConfigurationDistinguisher.UNKNOWN, + defaultMinimumOs(ConfigurationDistinguisher.UNKNOWN)) + + "/x/gen.m", + MOCK_XCRUNWRAPPER_PATH); + } + + protected void checkGenruleDependencyMultiArch(BinaryRuleTypePair ruleTypePair, + ConfigurationDistinguisher configurationDistinguisher) throws Exception { + useConfiguration("--ios_multi_cpus=armv7,arm64"); + String targets = ruleTypePair.targets(scratch, "x", "srcs", "['gen.m']"); + scratch.file("x/BUILD", + "genrule(", + " name = 'gen',", + " srcs = [],", + " outs = ['gen.m'],", + " cmd = '\\'\\' > $(location gen.m)'", + ")", + targets); + + CommandAction appLipoAction = (CommandAction) getGeneratingAction( + getBinArtifact("x_lipobin", getConfiguredTarget("//x:x", targetConfig))); + + assertThat(Artifact.toExecPaths(appLipoAction.getInputs())) + .containsExactly( + configurationBin("armv7", configurationDistinguisher) + "x/bin_bin", + configurationBin("arm64", configurationDistinguisher) + "x/bin_bin", + MOCK_XCRUNWRAPPER_PATH); + } + + protected void checkGenruleWithoutJavaCcDependency(BinaryRuleTypePair ruleTypePair) + throws Exception { + useConfiguration("--experimental_disable_go", "--experimental_disable_jvm", + "--ios_multi_cpus=armv7,arm64", "--crosstool_top=//tools/osx/crosstool:crosstool"); + + String targets = ruleTypePair.targets(scratch, "x", "srcs", "['gen.m']"); + scratch.file("x/BUILD", + "genrule(", + " name = 'gen',", + " srcs = [],", + " outs = ['gen.m'],", + " cmd = 'echo \"\" > $(location gen.m)'", + ")", + targets); + + CommandAction appLipoAction = (CommandAction) getGeneratingAction( + getBinArtifact("x_lipobin", getConfiguredTarget("//x:x", targetConfig))); + + for (Artifact binBin : appLipoAction.getInputs()) { + CommandAction binBinAction = (CommandAction) getGeneratingAction(binBin); + if (binBinAction == null) { + continue; + } + Action libBinAction = + getGeneratingAction(getFirstArtifactEndingWith(binBinAction.getInputs(), "libbin.a")); + Action genOAction = + getGeneratingAction(Iterables.getOnlyElement(inputsEndingWith(libBinAction, ".o"))); + Action genMAction = + getGeneratingAction(getFirstArtifactEndingWith(genOAction.getInputs(), "gen.m")); + assertThat(genMAction).isNotInstanceOf(FailAction.class); + } + } + + protected void checkCcDependencyWithProtoDependency(BinaryRuleTypePair ruleTypePair, + ConfigurationDistinguisher configurationDistinguisher) throws Exception { + MockProtoSupport.setup(mockToolsConfig); + useConfiguration( + "--experimental_disable_go", + "--experimental_disable_jvm", + "--cpu=ios_i386", + "--crosstool_top=//tools/osx/crosstool:crosstool"); + scratch.file("lib/BUILD", + "proto_library(", + " name = 'protolib',", + " srcs = ['foo.proto'],", + " cc_api_version = 1,", + ")", + "", + "cc_library(", + " name = 'cclib',", + " srcs = ['dep.c'],", + " deps = [':protolib'],", + ")"); + ruleTypePair.scratchTargets(scratch, "deps", "['//lib:cclib']"); + + Action appLipoAction = lipoBinAction("//x:x"); + + CommandAction binBinAction = (CommandAction) getGeneratingAction( + getFirstArtifactEndingWith(appLipoAction.getInputs(), "bin_bin")); + + String i386Prefix = iosConfigurationCcDepsBin("i386", configurationDistinguisher); + ImmutableList<String> archiveFilenames = ImmutableList.of( + i386Prefix + "lib/libcclib.a", + i386Prefix + "x/libbin.a", + i386Prefix + "lib/libprotolib.a", + i386Prefix + "net/proto/libproto.a"); + + verifyObjlist(binBinAction, "x/bin-linker.objlist", + archiveFilenames.toArray(new String[archiveFilenames.size()])); + + assertThat(Artifact.toExecPaths(binBinAction.getInputs())) + .containsAllIn( + ImmutableList.builder() + .addAll(archiveFilenames) + .add(i386Prefix + "x/bin-linker.objlist") + .build()); + } + + protected void checkCcDependencyAndJ2objcDependency(BinaryRuleTypePair ruleTypePair, + ConfigurationDistinguisher configurationDistinguisher) throws Exception { + MockProtoSupport.setup(mockToolsConfig); + MockJ2ObjcSupport.setup(mockToolsConfig); + useConfiguration( + "--experimental_disable_go", + "--cpu=ios_i386", + "--crosstool_top=//tools/osx/crosstool:crosstool"); + + scratch.file("lib/BUILD", + "java_library(", + " name = 'javalib',", + " srcs = ['foo.java'],", + ")", + "", + "j2objc_library(", + " name = 'j2objclib',", + " deps = [':javalib'],", + ")", + "", + "cc_library(", + " name = 'cclib',", + " srcs = ['dep.c'],", + ")"); + ruleTypePair.scratchTargets(scratch, "deps", "['//lib:cclib', '//lib:j2objclib']"); + + Action appLipoAction = getGeneratingAction( + getBinArtifact("x_lipobin", getConfiguredTarget("//x:x", targetConfig))); + + CommandAction binBinAction = (CommandAction) getGeneratingAction( + getFirstArtifactEndingWith(appLipoAction.getInputs(), "bin_bin")); + + String i386Prefix = iosConfigurationCcDepsBin("i386", configurationDistinguisher); + ImmutableList<String> archiveFilenames = ImmutableList.of( + i386Prefix + "lib/libcclib.a", + i386Prefix + "x/libbin.a", + i386Prefix + "lib/libjavalib_j2objc.a", + i386Prefix + "third_party/java/j2objc/libjre_core_lib.a"); + + verifyObjlist(binBinAction, "x/bin-linker.objlist", + archiveFilenames.toArray(new String[archiveFilenames.size()])); + + assertThat(Artifact.toExecPaths(binBinAction.getInputs())) + .containsAllIn( + ImmutableList.builder() + .addAll(archiveFilenames) + .add(i386Prefix + "x/bin-linker.objlist") + .build()); + } + + protected void checkCcDependencyWithProtoDependencyMultiArch(BinaryRuleTypePair ruleTypePair, + ConfigurationDistinguisher configurationDistinguisher) throws Exception { + MockProtoSupport.setup(mockToolsConfig); + useConfiguration("--experimental_disable_go", "--experimental_disable_jvm", + "--ios_multi_cpus=armv7,arm64", "--crosstool_top=//tools/osx/crosstool:crosstool"); + scratch.file("lib/BUILD", + "proto_library(", + " name = 'protolib',", + " srcs = ['foo.proto'],", + " cc_api_version = 1,", + ")", + "", + "cc_library(", + " name = 'cclib',", + " srcs = ['dep.c'],", + " deps = [':protolib'],", + ")"); + ruleTypePair.scratchTargets(scratch, "deps", "['//lib:cclib']"); + + Action appLipoAction = lipoBinAction("//x:x"); + + assertThat(Artifact.toExecPaths(appLipoAction.getInputs())) + .containsExactly( + configurationBin("armv7", configurationDistinguisher) + "x/bin_bin", + configurationBin("arm64", configurationDistinguisher) + "x/bin_bin", + MOCK_XCRUNWRAPPER_PATH); + } + + protected void checkBinaryStripAction(RuleType ruleType, String... extraItems) throws Exception { + ruleType.scratchTarget(scratch); + + useConfiguration("--compilation_mode=opt", "--objc_enable_binary_stripping"); + ConfiguredTarget binaryTarget = getConfiguredTarget("//x:x"); + Artifact strippedBinary = getBinArtifact("x_bin", binaryTarget); + Artifact unstrippedBinary = getBinArtifact("x_bin_unstripped", binaryTarget); + CommandAction symbolStripAction = (CommandAction) getGeneratingAction(strippedBinary); + boolean isTestRule = ruleType.getRuleTypeName().endsWith("_test"); + + ImmutableList.Builder<String> expectedSymbolStripArgs = ImmutableList.<String>builder() + .add(MOCK_XCRUNWRAPPER_PATH) + .add(STRIP); + + expectedSymbolStripArgs.add(extraItems); + + expectedSymbolStripArgs.add( + "-o", strippedBinary.getExecPathString(), unstrippedBinary.getExecPathString()); + + assertThat(symbolStripAction.getArguments()) + .containsExactlyElementsIn(expectedSymbolStripArgs.build()) + .inOrder(); + + CommandAction linkAction = (CommandAction) getGeneratingAction(unstrippedBinary); + + String args = Joiner.on(" ").join(linkAction.getArguments()); + if (isTestRule) { + assertThat(args).doesNotContain("-dead_strip"); + assertThat(args).doesNotContain("-no_dead_strip_inits_and_terms"); + } else { + assertThat(args).contains("-dead_strip"); + assertThat(args).contains("-no_dead_strip_inits_and_terms"); + } + + assertThat(compileAction("//x:x", "a.o").getArguments()).contains("-g"); + } + + protected void checkLaunchStoryboardIncluded(BinaryRuleTypePair ruleTypePair) throws Exception { + useConfiguration("--ios_minimum_os=8.1"); + ruleTypePair.scratchTargets(scratch, "launch_storyboard", "'launch.storyboard'"); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + + Artifact storyboardZip = getBinArtifact("x/launch.storyboard.zip", target); + CommandAction storyboardCompile = (CommandAction) getGeneratingAction(storyboardZip); + + assertThat(storyboardCompile.getInputs()) + .containsExactly( + getSourceArtifact(MOCK_IBTOOLWRAPPER_PATH), + getSourceArtifact("x/launch.storyboard")); + + assertThat(storyboardCompile.getArguments()) + .isEqualTo( + new CustomCommandLine.Builder() + .add(MOCK_IBTOOLWRAPPER_PATH) + .add(storyboardZip.getExecPathString()) + .add("launch.storyboardc") + .add("--minimum-deployment-target") + .add("8.1") + .add("--module") + .add("x") + .add("--target-device") + .add("iphone") + .add("x/launch.storyboard") + .build() + .arguments()); + + assertGeneratesLaunchStoryboardPlist(target, "launch"); + assertUsesLaunchStoryboardPlist(target); + assertMergesLaunchStoryboard(ruleTypePair, storyboardZip); + } + + protected void checkLaunchStoryboardXib(BinaryRuleTypePair ruleTypePair) throws Exception { + useConfiguration("--ios_minimum_os=8.1"); + ruleTypePair.scratchTargets(scratch, "launch_storyboard", "'launch.xib'"); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + + Artifact nibZip = getBinArtifact("x/x/launch.nib.zip", target); + CommandAction nibCompile = (CommandAction) getGeneratingAction(nibZip); + + assertThat(Artifact.toExecPaths(nibCompile.getInputs())) + .containsExactly(MOCK_IBTOOLWRAPPER_PATH, "x/launch.xib"); + + assertThat(nibCompile.getArguments()) + .containsExactly( + MOCK_IBTOOLWRAPPER_PATH, + nibZip.getExecPathString(), + "launch.nib", + "--minimum-deployment-target", "8.1", + "--module", "x", + "--target-device", "iphone", + "x/launch.xib") + .inOrder(); + + assertGeneratesLaunchStoryboardPlist(target, "launch"); + assertUsesLaunchStoryboardPlist(target); + assertMergesLaunchStoryboard(ruleTypePair, nibZip); + } + + private void assertGeneratesLaunchStoryboardPlist(ConfiguredTarget target, String baseName) + throws Exception { + Artifact storyboardPlist = + getBinArtifact("plists/" + target.getLabel().getName() + "-launchstoryboard.plist", target); + + FileWriteAction plistAction = (FileWriteAction) getGeneratingAction(storyboardPlist); + + assertThat(plistAction.getFileContents()) + .contains("\"UILaunchStoryboardName\" = \"" + baseName + "\""); + } + + private void assertUsesLaunchStoryboardPlist(ConfiguredTarget target) throws Exception { + Artifact storyboardPlist = + getBinArtifact("plists/" + target.getLabel().getName() + "-launchstoryboard.plist", target); + PlMergeProtos.Control plMergeControl = plMergeControl(target.getLabel().getCanonicalForm()); + + assertThat(plMergeControl.getSourceFileList()).contains(storyboardPlist.getExecPathString()); + } + + private void assertMergesLaunchStoryboard(BinaryRuleTypePair ruleTypePair, Artifact storyboardZip) + throws Exception { + assertThat(bundleMergeAction("//x:x").getInputs()).contains(storyboardZip); + assertThat(bundleMergeControl("//x:x").getMergeZipList()) + .contains( + BundleMergeProtos.MergeZip.newBuilder() + .setEntryNamePrefix(ruleTypePair.getBundleDir() + "/") + .setSourcePath(storyboardZip.getExecPathString()) + .build()); + } + + protected void checkLaunchStoryboardLproj(BinaryRuleTypePair ruleTypePair) throws Exception { + useConfiguration("--ios_minimum_os=8.1"); + ruleTypePair.scratchTargets( + scratch, "launch_storyboard", "'superfluous_dir/en.lproj/launch.storyboard'"); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + + Artifact storyboardZip = getBinArtifact("x/en.lproj/launch.storyboard.zip", target); + CommandAction storyboardCompile = (CommandAction) getGeneratingAction(storyboardZip); + + assertThat(storyboardCompile.getInputs()) + .contains(getSourceArtifact("x/superfluous_dir/en.lproj/launch.storyboard")); + + assertThat(storyboardCompile.getArguments()) + .containsAllOf( + "en.lproj/launch.storyboardc", "x/superfluous_dir/en.lproj/launch.storyboard"); + + assertGeneratesLaunchStoryboardPlist(target, "launch"); + assertMergesLaunchStoryboard(ruleTypePair, storyboardZip); + } + + protected void checkAutomaticPlistEntries(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, INFOPLIST_ATTR, RuleType.OMIT_REQUIRED_ATTR); + + ConfiguredTarget target = getConfiguredTarget("//x:x"); + Artifact automaticInfoplist = getBinArtifact("plists/x-automatic.plist", target); + FileWriteAction automaticInfoplistAction = + (FileWriteAction) getGeneratingAction(automaticInfoplist); + + NSDictionary foundAutomaticEntries = + (NSDictionary) + PropertyListParser.parse( + automaticInfoplistAction.getFileContents().getBytes(Charset.defaultCharset())); + + assertThat(foundAutomaticEntries.keySet()) + .containsExactly( + "UIDeviceFamily", + "DTPlatformName", + "DTSDKName", + "CFBundleSupportedPlatforms", + "MinimumOSVersion"); + } + + protected void checkMultipleInfoPlists(RuleType ruleType) throws Exception { + scratch.file("x/a.plist"); + scratch.file("x/b.plist"); + ruleType.scratchTarget(scratch, "infoplists", "['a.plist', 'b.plist']"); + + String targetName = "//x:x"; + PlMergeProtos.Control control = plMergeControl(targetName); + + assertThat(control.getSourceFileList()) + .contains(getSourceArtifact("x/a.plist").getExecPathString()); + assertThat(control.getSourceFileList()) + .contains(getSourceArtifact("x/b.plist").getExecPathString()); + } + + protected void checkInfoplistAndInfoplistsTogether(RuleType ruleType) throws Exception { + scratch.file("x/a.plist"); + scratch.file("x/b.plist"); + scratch.file("x/c.plist"); + ruleType.scratchTarget(scratch, "infoplists", "['a.plist', 'b.plist']", INFOPLIST_ATTR, + "'c.plist'"); + + String targetName = "//x:x"; + PlMergeProtos.Control control = plMergeControl(targetName); + + assertThat(control.getSourceFileList()) + .contains(getSourceArtifact("x/a.plist").getExecPathString()); + assertThat(control.getSourceFileList()) + .contains(getSourceArtifact("x/b.plist").getExecPathString()); + assertThat(control.getSourceFileList()) + .contains(getSourceArtifact("x/c.plist").getExecPathString()); + } + + protected void checkBundleMergeInputContainsPlMergeOutput(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, INFOPLIST_ATTR, RuleType.OMIT_REQUIRED_ATTR); + + Artifact mergedPlist = getMergedInfoPlist(getConfiguredTarget("//x:x")); + CommandAction mergeAction = (CommandAction) getGeneratingAction(mergedPlist); + + assertThat(bundleMergeAction("//x:x").getInputs()).containsAllIn(mergeAction.getOutputs()); + } + + /** + * Computes expected variable substitutions for "x" with full bundle name. + */ + protected Map<String, String> getVariableSubstitutionArgumentsDefaultFormat(RuleType ruleType) { + return constructVariableSubstitutions("x", getBundlePathInsideIpa(ruleType)); + } + + /** + * Return the expected name for the bundle. + */ + protected String getBundleNameWithExtension(RuleType ruleType) { + return "x." + ruleType.bundleExtension(); + } + + /** + * Return the expected bundle path inside an Ipa. + */ + protected String getBundlePathInsideIpa(RuleType ruleType) { + return "Payload/" + getBundleNameWithExtension(ruleType); + } + + /** + * Computes expected variable substitutions from a ruleTypePair + */ + protected Map<String, String> getVariableSubstitutionArguments(BinaryRuleTypePair ruleTypePair) { + return constructVariableSubstitutions( + ruleTypePair.getBundleName(), ruleTypePair.getBundleDir()); + } + + private Map<String, String> constructVariableSubstitutions(String bundleName, String bundleDir) { + return new ImmutableMap.Builder<String, String>() + .put("EXECUTABLE_NAME", bundleName) + .put("BUNDLE_NAME", bundleDir.split("/")[1]) + .put("PRODUCT_NAME", bundleName) + .build(); + } + + protected void assertPlistMergeControlUsesSourceFiles( + PlMergeProtos.Control control, Iterable<String> sourceFilePaths) throws Exception { + Iterable<String> allSourceFiles = + Iterables.concat(control.getSourceFileList(), control.getImmutableSourceFileList()); + assertThat(allSourceFiles).containsAllIn(sourceFilePaths); + } + + private BinaryFileWriteAction plMergeAction(String binaryLabelString) throws Exception { + Label binaryLabel = Label.parseAbsolute(binaryLabelString); + ConfiguredTarget binary = getConfiguredTarget(binaryLabelString); + return (BinaryFileWriteAction) + getGeneratingAction(getBinArtifact(binaryLabel.getName() + + artifactName(".plmerge-control"), binary)); + } + + protected PlMergeProtos.Control plMergeControl(String binaryLabelString) throws Exception { + InputStream in = plMergeAction(binaryLabelString).getSource().openStream(); + return PlMergeProtos.Control.parseFrom(in); + } + + protected void setArtifactPrefix(String artifactPrefix) { + this.artifactPrefix = artifactPrefix; + } + + private String artifactName(String artifactName) { + if (artifactPrefix != null) { + return String.format("-%s%s", artifactPrefix, artifactName); + } + return artifactName; + } + + /** + * Normalizes arguments to a bash action into a space-separated list. + * + * <p>Bash actions' arguments have two parts, the bash invocation ({@code "/bin/bash", "-c"}) and + * the command executed in the bash shell, as a single string. This method merges all these + * arguments and splits them on {@code ' '}. + */ + protected List<String> normalizeBashArgs(List<String> args) { + return Splitter.on(' ').splitToList(Joiner.on(' ').join(args)); + } + + /** Returns the directory where objc modules will be cached. */ + protected String getModulesCachePath() throws InterruptedException { + return getAppleCrosstoolConfiguration().getGenfilesFragment() + + "/" + + CompilationSupport.OBJC_MODULE_CACHE_DIR_NAME; + } + + /** + * Verifies that the given rule supports the minimum_os attribute, and adds compile and link + * args to set the minimum os appropriately, including compile args for dependencies. + * + * @param ruleType the rule to test + */ + protected void checkMinimumOsLinkAndCompileArg(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "deps", "['//package:objcLib']", + "minimum_os_version", "'5.4'"); + scratch.file("package/BUILD", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ])"); + useConfiguration("--xcode_version=5.8"); + + CommandAction linkAction = linkAction("//x:x"); + CommandAction objcLibArchiveAction = (CommandAction) getGeneratingAction( + getFirstArtifactEndingWith(linkAction.getInputs(), "libobjcLib.a")); + CommandAction objcLibCompileAction = (CommandAction) getGeneratingAction( + getFirstArtifactEndingWith(objcLibArchiveAction.getInputs(), "b.o")); + + String linkArgs = Joiner.on(" ").join(linkAction.getArguments()); + String compileArgs = Joiner.on(" ").join(objcLibCompileAction.getArguments()); + assertThat(linkArgs).contains("-mios-simulator-version-min=5.4"); + assertThat(compileArgs).contains("-mios-simulator-version-min=5.4"); + } + + /** + * Verifies that the given rule supports the minimum_os attribute under the watchOS platform + * type, and adds compile and link args to set the minimum os appropriately for watchos, + * including compile args for dependencies. + * + * @param ruleType the rule to test + */ + protected void checkMinimumOsLinkAndCompileArg_watchos(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "deps", "['//package:objcLib']", + "platform_type", "'watchos'", + "minimum_os_version", "'5.4'"); + scratch.file("package/BUILD", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ])"); + useConfiguration("--xcode_version=5.8"); + + CommandAction linkAction = linkAction("//x:x"); + CommandAction objcLibArchiveAction = (CommandAction) getGeneratingAction( + getFirstArtifactEndingWith(linkAction.getInputs(), "libobjcLib.a")); + CommandAction objcLibCompileAction = (CommandAction) getGeneratingAction( + getFirstArtifactEndingWith(objcLibArchiveAction.getInputs(), "b.o")); + + String linkArgs = Joiner.on(" ").join(linkAction.getArguments()); + String compileArgs = Joiner.on(" ").join(objcLibCompileAction.getArguments()); + assertThat(linkArgs).contains("-mwatchos-simulator-version-min=5.4"); + assertThat(compileArgs).contains("-mwatchos-simulator-version-min=5.4"); + } + + /** + * Verifies that the given rule throws a sensible error if the minimum_os attribute has a bad + * value. + */ + protected void checkMinimumOs_invalid_nonVersion(RuleType ruleType) throws Exception { + checkError("x", "x", + String.format( + MultiArchSplitTransitionProvider.INVALID_VERSION_STRING_ERROR_FORMAT, + "foobar"), + ruleType.target(scratch, "x", "x", "minimum_os_version", "'foobar'")); + } + + /** + * Verifies that the given rule throws a sensible error if the minimum_os attribute has a bad + * value. + */ + protected void checkMinimumOs_invalid_containsAlphabetic(RuleType ruleType) throws Exception { + checkError("x", "x", + String.format( + MultiArchSplitTransitionProvider.INVALID_VERSION_STRING_ERROR_FORMAT, + "4.3alpha"), + ruleType.target(scratch, "x", "x", "minimum_os_version", "'4.3alpha'")); + } + + /** + * Verifies that the given rule throws a sensible error if the minimum_os attribute has a bad + * value. + */ + protected void checkMinimumOs_invalid_tooManyComponents(RuleType ruleType) throws Exception { + checkError("x", "x", + String.format( + MultiArchSplitTransitionProvider.INVALID_VERSION_STRING_ERROR_FORMAT, + "4.3.1"), + ruleType.target(scratch, "x", "x", "minimum_os_version", "'4.3.1'")); + } + + protected void checkDylibDependencies(RuleType ruleType, + ExtraLinkArgs extraLinkArgs) throws Exception { + ruleType.scratchTarget(scratch, + "srcs", "['a.m']", + "dylibs", "['//fx:framework_import']"); + + scratch.file("fx/MyFramework.framework/MyFramework"); + scratch.file("fx/BUILD", + "objc_framework(", + " name = 'framework_import',", + " framework_imports = glob(['MyFramework.framework/*']),", + " is_dynamic = 1,", + ")"); + useConfiguration("--ios_multi_cpus=i386,x86_64", "--experimental_disable_go", + "--experimental_disable_jvm", "--crosstool_top=//tools/osx/crosstool:crosstool"); + + Action lipobinAction = lipoBinAction("//x:x"); + + String i386Bin = + configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_IOS) + + "x/x_bin"; + String i386Filelist = + configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_IOS) + + "x/x-linker.objlist"; + String x8664Bin = + configurationBin("x86_64", ConfigurationDistinguisher.APPLEBIN_IOS) + + "x/x_bin"; + String x8664Filelist = + configurationBin("x86_64", ConfigurationDistinguisher.APPLEBIN_IOS) + + "x/x-linker.objlist"; + + Artifact i386BinArtifact = getFirstArtifactEndingWith(lipobinAction.getInputs(), i386Bin); + Artifact i386FilelistArtifact = + getFirstArtifactEndingWith(getGeneratingAction(i386BinArtifact).getInputs(), i386Filelist); + Artifact x8664BinArtifact = getFirstArtifactEndingWith(lipobinAction.getInputs(), x8664Bin); + Artifact x8664FilelistArtifact = + getFirstArtifactEndingWith(getGeneratingAction(x8664BinArtifact).getInputs(), + x8664Filelist); + + ImmutableList<String> archiveNames = + ImmutableList.of("x/libx.a", "lib1/liblib1.a", "lib2/liblib2.a"); + verifyLinkAction(i386BinArtifact, i386FilelistArtifact, "i386", archiveNames, + ImmutableList.of(PathFragment.create("fx/MyFramework")), extraLinkArgs); + verifyLinkAction(x8664BinArtifact, x8664FilelistArtifact, + "x86_64", archiveNames, ImmutableList.of(PathFragment.create("fx/MyFramework")), + extraLinkArgs); + } + + protected void checkLipoBinaryAction(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "srcs", "['a.m']"); + + useConfiguration("--ios_multi_cpus=i386,x86_64"); + + CommandAction action = (CommandAction) lipoBinAction("//x:x"); + String i386Bin = + configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_IOS) + "x/x_bin"; + String x8664Bin = + configurationBin("x86_64", ConfigurationDistinguisher.APPLEBIN_IOS) + "x/x_bin"; + + assertThat(Artifact.toExecPaths(action.getInputs())) + .containsExactly(i386Bin, x8664Bin, MOCK_XCRUNWRAPPER_PATH); + + assertThat(action.getArguments()) + .containsExactly(MOCK_XCRUNWRAPPER_PATH, LIPO, + "-create", i386Bin, x8664Bin, + "-o", execPathEndingWith(action.getOutputs(), "x_lipobin")) + .inOrder(); + + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("x/x_lipobin"); + assertRequiresDarwin(action); + } + + protected void checkMultiarchCcDep(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "srcs", "['a.m']", "deps", "['//package:cclib']"); + scratch.file("package/BUILD", + "cc_library(name = 'cclib', srcs = ['dep.c'])"); + + useConfiguration("--ios_multi_cpus=i386,x86_64", "--experimental_disable_go", + "--experimental_disable_jvm", "--crosstool_top=//tools/osx/crosstool:crosstool"); + + Action appLipoAction = actionProducingArtifact("//x:x", "_lipobin"); + String i386Prefix = + configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_IOS); + String x8664Prefix = + configurationBin("x86_64", ConfigurationDistinguisher.APPLEBIN_IOS); + + CommandAction i386BinAction = (CommandAction) getGeneratingAction( + getFirstArtifactEndingWith(appLipoAction.getInputs(), i386Prefix + "x/x_bin")); + + CommandAction x8664BinAction = (CommandAction) getGeneratingAction( + getFirstArtifactEndingWith(appLipoAction.getInputs(), x8664Prefix + "x/x_bin")); + + verifyObjlist( + i386BinAction, "x/x-linker.objlist", + "x/libx.a", "package/libcclib.a"); + verifyObjlist( + x8664BinAction, "x/x-linker.objlist", + "x/libx.a", "package/libcclib.a"); + + assertThat(Artifact.toExecPaths(i386BinAction.getInputs())) + .containsAllOf( + i386Prefix + "x/libx.a", + i386Prefix + "package/libcclib.a", + i386Prefix + "x/x-linker.objlist"); + assertThat(Artifact.toExecPaths(x8664BinAction.getInputs())) + .containsAllOf( + x8664Prefix + "x/libx.a", + x8664Prefix + "package/libcclib.a", + x8664Prefix + "x/x-linker.objlist"); + } + + protected void checkLinkActionsWithSrcs(RuleType ruleType, + ExtraLinkArgs extraLinkArgs) throws Exception { + createLibraryTargetWriter("//lib1:lib1") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + createLibraryTargetWriter("//lib2:lib2") + .setAndCreateFiles("srcs", "a.m", "b.m", "private.h") + .setAndCreateFiles("hdrs", "hdr.h") + .write(); + ruleType.scratchTarget(scratch, + "srcs", "['a.m']", + "deps", "['//lib1:lib1', '//lib2:lib2']"); + useConfiguration("--ios_multi_cpus=i386,x86_64", "--experimental_disable_go", + "--experimental_disable_jvm", "--crosstool_top=//tools/osx/crosstool:crosstool"); + + Action lipobinAction = lipoBinAction("//x:x"); + + String i386Bin = + configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_IOS) + + "x/x_bin"; + String i386Filelist = + configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_IOS) + + "x/x-linker.objlist"; + String x8664Bin = + configurationBin("x86_64", ConfigurationDistinguisher.APPLEBIN_IOS) + + "x/x_bin"; + String x8664Filelist = + configurationBin("x86_64", ConfigurationDistinguisher.APPLEBIN_IOS) + + "x/x-linker.objlist"; + + Artifact i386BinArtifact = getFirstArtifactEndingWith(lipobinAction.getInputs(), i386Bin); + Artifact i386FilelistArtifact = + getFirstArtifactEndingWith(getGeneratingAction(i386BinArtifact).getInputs(), i386Filelist); + Artifact x8664BinArtifact = getFirstArtifactEndingWith(lipobinAction.getInputs(), x8664Bin); + Artifact x8664FilelistArtifact = + getFirstArtifactEndingWith(getGeneratingAction(x8664BinArtifact).getInputs(), + x8664Filelist); + + ImmutableList<String> archiveNames = + ImmutableList.of("x/libx.a", "lib1/liblib1.a", "lib2/liblib2.a"); + verifyLinkAction(i386BinArtifact, i386FilelistArtifact, "i386", archiveNames, + ImmutableList.<PathFragment>of(), extraLinkArgs); + verifyLinkAction(x8664BinArtifact, x8664FilelistArtifact, + "x86_64", archiveNames, ImmutableList.<PathFragment>of(), extraLinkArgs); + } + + // Regression test for b/32310268. + protected void checkAliasedLinkoptsThroughObjcLibrary(RuleType ruleType) throws Exception { + useConfiguration("--experimental_disable_go", "--experimental_disable_jvm", + "--cpu=ios_i386", "--crosstool_top=//tools/osx/crosstool:crosstool"); + + scratch.file("bin/BUILD", + "objc_library(", + " name = 'objclib',", + " srcs = ['objcdep.c'],", + " deps = ['cclib'],", + ")", + "alias(", + " name = 'cclib',", + " actual = 'cclib_real',", + ")", + "cc_library(", + " name = 'cclib_real',", + " srcs = ['ccdep.c'],", + " linkopts = ['-somelinkopt'],", + ")"); + + ruleType.scratchTarget(scratch, + "srcs", "['main.m']", + "deps", "['//bin:objclib']"); + + // Frameworks should get placed together with no duplicates. + assertThat(Joiner.on(" ").join(linkAction("//x").getArguments())) + .contains("-somelinkopt"); + } + + protected void checkCcDependencyLinkoptsArePropagatedToLinkAction( + RuleType ruleType) throws Exception { + useConfiguration("--experimental_disable_go", "--experimental_disable_jvm", + "--cpu=ios_i386", "--crosstool_top=//tools/osx/crosstool:crosstool"); + + scratch.file("bin/BUILD", + "cc_library(", + " name = 'cclib1',", + " srcs = ['dep1.c'],", + " linkopts = ['-framework F1', '-framework F2', '-Wl,--other-opt'],", + ")", + "cc_library(", + " name = 'cclib2',", + " srcs = ['dep2.c'],", + " linkopts = ['-another-opt', '-framework F2'],", + " deps = ['cclib1'],", + ")", + "cc_library(", + " name = 'cclib3',", + " srcs = ['dep2.c'],", + " linkopts = ['-one-more-opt', '-framework UIKit'],", + " deps = ['cclib1'],", + ")"); + + ruleType.scratchTarget(scratch, + "srcs", "['main.m']", + "deps", "['//bin:cclib2', '//bin:cclib3']"); + + // Frameworks from the CROSSTOOL "apply_implicit_frameworks" feature should be present. + assertThat(Joiner.on(" ").join(linkAction("//x").getArguments())) + .contains("-framework Foundation -framework UIKit"); + // Frameworks included in linkopts by the user should get placed together with no duplicates. + // (They may duplicate the ones inserted by the CROSSTOOL feature, but we don't test that here.) + assertThat(Joiner.on(" ").join(linkAction("//x").getArguments())) + .contains("-framework F2 -framework F1"); + // Linkopts should also be grouped together. + assertThat(Joiner.on(" ").join(linkAction("//x").getArguments())) + .contains("-another-opt -Wl,--other-opt -one-more-opt"); + } + + protected void checkAppleSdkVersionEnv(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch); + + CommandAction action = linkAction("//x:x"); + + assertAppleSdkVersionEnv(action); + } + + protected void checkNonDefaultAppleSdkVersionEnv(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch); + useConfiguration("--ios_sdk_version=8.1"); + + CommandAction action = linkAction("//x:x"); + + assertAppleSdkVersionEnv(action, "8.1"); + } + + protected void checkAppleSdkDefaultPlatformEnv(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch); + CommandAction action = linkAction("//x:x"); + + assertAppleSdkPlatformEnv(action, "iPhoneSimulator"); + } + + protected void checkAppleSdkIphoneosPlatformEnv(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch); + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_disable_go", + "--cpu=ios_arm64"); + + CommandAction action = linkAction("//x:x"); + + assertAppleSdkPlatformEnv(action, "iPhoneOS"); + } + + protected void checkAppleSdkWatchsimulatorPlatformEnv(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "platform_type", "'watchos'"); + useConfiguration("--watchos_cpus=i386"); + + Action lipoAction = actionProducingArtifact("//x:x", "_lipobin"); + + String i386Bin = configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_WATCHOS) + + "x/x_bin"; + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), i386Bin); + CommandAction linkAction = (CommandAction) getGeneratingAction(binArtifact); + + assertAppleSdkPlatformEnv(linkAction, "WatchSimulator"); + } + + protected void checkAppleSdkWatchosPlatformEnv(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "platform_type", "'watchos'"); + useConfiguration("--watchos_cpus=armv7k"); + + Action lipoAction = actionProducingArtifact("//x:x", "_lipobin"); + + String armv7kBin = + configurationBin("armv7k", ConfigurationDistinguisher.APPLEBIN_WATCHOS) + + "x/x_bin"; + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), armv7kBin); + CommandAction linkAction = (CommandAction) getGeneratingAction(binArtifact); + + assertAppleSdkPlatformEnv(linkAction, "WatchOS"); + } + + protected void checkAppleSdkTvsimulatorPlatformEnv(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "platform_type", "'tvos'"); + useConfiguration("--tvos_cpus=x86_64"); + + CommandAction linkAction = linkAction("//x:x"); + + assertAppleSdkPlatformEnv(linkAction, "AppleTVSimulator"); + } + + protected void checkAppleSdkTvosPlatformEnv(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "platform_type", "'tvos'"); + useConfiguration("--tvos_cpus=arm64"); + + CommandAction linkAction = linkAction("//x:x"); + + assertAppleSdkPlatformEnv(linkAction, "AppleTVOS"); + } + + protected void checkLinkMinimumOSVersion(ConfigurationDistinguisher distinguisher, String arch, + String minOSVersionOption) throws Exception { + CommandAction linkAction = linkAction("//x:x"); + + assertThat(Joiner.on(" ").join(linkAction.getArguments())).contains(minOSVersionOption); + } + + protected void checkWatchSimulatorDepCompile(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "deps", "['//package:objcLib']", + "platform_type", "'watchos'"); + scratch.file("package/BUILD", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ])"); + + Action lipoAction = actionProducingArtifact("//x:x", "_lipobin"); + + String i386Bin = configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_WATCHOS) + + "x/x_bin"; + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), i386Bin); + CommandAction linkAction = (CommandAction) getGeneratingAction(binArtifact); + CommandAction objcLibCompileAction = (CommandAction) getGeneratingAction( + getFirstArtifactEndingWith(linkAction.getInputs(), "libobjcLib.a")); + + assertAppleSdkPlatformEnv(objcLibCompileAction, "WatchSimulator"); + assertThat(objcLibCompileAction.getArguments()).containsAllOf("-arch_only", "i386").inOrder(); + } + + protected void checkWatchSimulatorLinkAction(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "deps", "['//package:objcLib']", + "platform_type", "'watchos'"); + scratch.file("package/BUILD", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ])"); + + // Tests that ios_multi_cpus and cpu are completely ignored. + useConfiguration("--ios_multi_cpus=x86_64", "--cpu=ios_x86_64", "--watchos_cpus=i386"); + + Action lipoAction = actionProducingArtifact("//x:x", "_lipobin"); + + String i386Bin = configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_WATCHOS) + + "x/x_bin"; + Artifact binArtifact = getFirstArtifactEndingWith(lipoAction.getInputs(), i386Bin); + CommandAction linkAction = (CommandAction) getGeneratingAction(binArtifact); + + assertAppleSdkPlatformEnv(linkAction, "WatchSimulator"); + assertThat(normalizeBashArgs(linkAction.getArguments())) + .containsAllOf("-arch", "i386").inOrder(); + } + + protected void checkWatchSimulatorLipoAction(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "platform_type", "'watchos'"); + + // Tests that ios_multi_cpus and cpu are completely ignored. + useConfiguration("--ios_multi_cpus=x86_64", "--cpu=ios_x86_64", "--watchos_cpus=i386,armv7k"); + + CommandAction action = (CommandAction) lipoBinAction("//x:x"); + String i386Bin = configurationBin("i386", ConfigurationDistinguisher.APPLEBIN_WATCHOS) + + "x/x_bin"; + String armv7kBin = configurationBin("armv7k", ConfigurationDistinguisher.APPLEBIN_WATCHOS) + + "x/x_bin"; + + assertThat(Artifact.toExecPaths(action.getInputs())) + .containsExactly(i386Bin, armv7kBin, MOCK_XCRUNWRAPPER_PATH); + + assertContainsSublist(action.getArguments(), ImmutableList.of( + MOCK_XCRUNWRAPPER_PATH, LIPO, "-create")); + assertThat(action.getArguments()).containsAllOf(armv7kBin, i386Bin); + assertContainsSublist(action.getArguments(), ImmutableList.of( + "-o", execPathEndingWith(action.getOutputs(), "x_lipobin"))); + + assertThat(Artifact.toRootRelativePaths(action.getOutputs())) + .containsExactly("x/x_lipobin"); + assertAppleSdkPlatformEnv(action, "WatchOS"); + assertRequiresDarwin(action); + } + + protected void checkXcodeVersionEnv(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch); + useConfiguration("--xcode_version=5.8"); + + CommandAction action = linkAction("//x:x"); + + assertXcodeVersionEnv(action, "5.8"); + } + + protected void checkNoSrcs(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "deps", "['//package:objcLib']"); + scratch.file("package/BUILD", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ])"); + useConfiguration("--xcode_version=5.8"); + + CommandAction action = linkAction("//x:x"); + assertThat(Artifact.toRootRelativePaths(action.getInputs())).containsAllOf( + "x/libx.a", "package/libobjcLib.a", "x/x-linker.objlist"); + } + + public void checkLinkingRuleCanUseCrosstool(RuleType ruleType) throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all"); + ruleType.scratchTarget(scratch, "srcs", "['a.m']"); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + + // If bin is indeed using the c++ backend, then its archive action should be a CppLinkAction. + Action action = + getGeneratingAction(getBinArtifact("lib" + target.getLabel().getName() + ".a", target)); + assertThat(action).isInstanceOf(CppLinkAction.class); + } + + public void checkLinkingRuleCanUseCrosstool_singleArch(RuleType ruleType) throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all"); + ruleType.scratchTarget(scratch, "srcs", "['a.m']"); + + // If bin is indeed using the c++ backend, then its archive action should be a CppLinkAction. + Action lipobinAction = lipoBinAction("//x:x"); + Artifact bin = getFirstArtifactEndingWith(lipobinAction.getInputs(), "_bin"); + Action linkAction = getGeneratingAction(bin); + Artifact archive = getFirstArtifactEndingWith(linkAction.getInputs(), ".a"); + Action archiveAction = getGeneratingAction(archive); + assertThat(archiveAction).isInstanceOf(CppLinkAction.class); + } + + public void checkLinkingRuleCanUseCrosstool_multiArch(RuleType ruleType) throws Exception { + useConfiguration( + "--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL, + "--experimental_objc_crosstool=all", + "--ios_multi_cpus=i386,x86_64"); + ruleType.scratchTarget(scratch, "srcs", "['a.m']"); + + // If bin is indeed using the c++ backend, then its archive action should be a CppLinkAction. + Action lipobinAction = lipoBinAction("//x:x"); + Artifact bin = getFirstArtifactEndingWith(lipobinAction.getInputs(), "_bin"); + Action linkAction = getGeneratingAction(bin); + Artifact archive = getFirstArtifactEndingWith(linkAction.getInputs(), ".a"); + Action archiveAction = getGeneratingAction(archive); + assertThat(archiveAction).isInstanceOf(CppLinkAction.class); + } + + protected void scratchFrameworkSkylarkStub(String bzlPath) throws Exception { + PathFragment pathFragment = PathFragment.create(bzlPath); + scratch.file(pathFragment.getParentDirectory() + "/BUILD"); + scratch.file( + bzlPath, + "def framework_stub_impl(ctx):", + " bin_provider = ctx.attr.binary[apple_common.AppleDylibBinary]", + " my_provider = apple_common.new_dynamic_framework_provider(", + " objc = bin_provider.objc,", + " binary = bin_provider.binary,", + " framework_files = depset([bin_provider.binary]),", + " framework_dirs = depset(['_frameworks/stubframework.framework']))", + " return struct(providers = [my_provider])", + "framework_stub_rule = rule(", + " framework_stub_impl,", + // Both 'binary' and 'deps' are needed because ObjcProtoAspect is applied transitively + // along attribute 'deps' only. + " attrs = {'binary': attr.label(mandatory=True,", + " providers=[apple_common.AppleDylibBinary]),", + " 'deps': attr.label_list(providers=[apple_common.AppleDylibBinary])},", + " fragments = ['apple', 'objc'],", + ")"); + } + + private void assertAvoidDepsObjects(RuleType ruleType) throws Exception { + /* + * The target tree for ease of understanding: + * x depends on "avoidLib" as a dylib and "objcLib" as a static dependency. + * + * ( objcLib ) + * / \ + * ( avoidLib ) ( baseLib ) + * / / \ + * (avoidLibDep) / (baseLibDep) + * \ / + * ( avoidLibDepTwo ) + * + * All libraries prefixed with "avoid" shouldn't be statically linked in the top level target. + */ + ruleType.scratchTarget(scratch, + "deps", "['//package:objcLib']", + "dylibs", "['//package:avoidLib']"); + scratchFrameworkSkylarkStub("frameworkstub/framework_stub.bzl"); + scratch.file("package/BUILD", + "load('//frameworkstub:framework_stub.bzl', 'framework_stub_rule')", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ],", + " deps = [':avoidLibDep', ':baseLib'])", + "objc_library(name = 'baseLib', srcs = [ 'base.m' ],", + " deps = [':baseLibDep', ':avoidLibDepTwo'])", + "objc_library(name = 'baseLibDep', srcs = [ 'basedep.m' ],", + " sdk_frameworks = ['BaseSDK'], resources = [':base.png'])", + "framework_stub_rule(name = 'avoidLib', binary = ':avoidLibBinary')", + "apple_binary(name = 'avoidLibBinary', binary_type = 'dylib', srcs = [ 'c.m' ],", + " platform_type = 'ios',", + " deps = [':avoidLibDep'])", + "objc_library(name = 'avoidLibDep', srcs = [ 'd.m' ], deps = [':avoidLibDepTwo'])", + "objc_library(name = 'avoidLibDepTwo', srcs = [ 'e.m' ],", + " sdk_frameworks = ['AvoidSDK'], resources = [':avoid.png'])"); + + Action lipobinAction = lipoBinAction("//x:x"); + Artifact binArtifact = getFirstArtifactEndingWith(lipobinAction.getInputs(), "x/x_bin"); + + Action action = getGeneratingAction(binArtifact); + + assertThat(getFirstArtifactEndingWith(action.getInputs(), "x/libx.a")).isNotNull(); + assertThat(getFirstArtifactEndingWith(action.getInputs(), "package/libobjcLib.a")).isNotNull(); + assertThat(getFirstArtifactEndingWith(action.getInputs(), "package/libbaseLib.a")).isNotNull(); + assertThat(getFirstArtifactEndingWith(action.getInputs(), "package/libbaseLibDep.a")) + .isNotNull(); + assertThat(getFirstArtifactEndingWith(action.getInputs(), "package/libavoidLib.a")).isNull(); + assertThat(getFirstArtifactEndingWith(action.getInputs(), "package/libavoidLibDepTwo.a")) + .isNull(); + assertThat(getFirstArtifactEndingWith(action.getInputs(), "package/libavoidLibDep.a")).isNull(); + } + + public void checkAvoidDepsObjectsWithCrosstool(RuleType ruleType) throws Exception { + useConfiguration(ObjcCrosstoolMode.ALL, "--ios_multi_cpus=i386,x86_64"); + assertAvoidDepsObjects(ruleType); + } + + public void checkAvoidDepsObjects(RuleType ruleType) throws Exception { + useConfiguration("--ios_multi_cpus=i386,x86_64"); + assertAvoidDepsObjects(ruleType); + } + + /** + * Verifies that if apple_binary A depends on a dylib B1 which then depends on a dylib B2, + * that the symbols from B2 are not present in A. + */ + public void checkAvoidDepsThroughDylib(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "deps", "['//package:ObjcLib']", + "dylibs", "['//package:dylib1']"); + scratchFrameworkSkylarkStub("frameworkstub/framework_stub.bzl"); + scratch.file("package/BUILD", + "load('//frameworkstub:framework_stub.bzl', 'framework_stub_rule')", + "objc_library(name = 'ObjcLib', srcs = [ 'ObjcLib.m' ],", + " deps = [':Dylib1Lib', ':Dylib2Lib'])", + "objc_library(name = 'Dylib1Lib', srcs = [ 'Dylib1Lib.m' ])", + "objc_library(name = 'Dylib2Lib', srcs = [ 'Dylib2Lib.m' ])", + "framework_stub_rule(name = 'dylib1', binary = ':dylib1Binary')", + "apple_binary(name = 'dylib1Binary', binary_type = 'dylib', srcs = [ 'Dylib1Bin.m' ],", + " platform_type = 'ios',", + " deps = [':Dylib1Lib'], dylibs = ['//package:dylib2'])", + "framework_stub_rule(name = 'dylib2', binary = ':dylib2Binary')", + "apple_binary(name = 'dylib2Binary', binary_type = 'dylib', srcs = [ 'Dylib2Bin.m' ],", + " platform_type = 'ios',", + " deps = [':Dylib2Lib'])", + "apple_binary(name = 'alternate', srcs = [ 'alternate.m' ],", + " platform_type = 'ios',", + " deps = ['//package:ObjcLib'])"); + + Action lipobinAction = lipoBinAction("//x:x"); + Artifact binArtifact = getFirstArtifactEndingWith(lipobinAction.getInputs(), "x/x_bin"); + + Action linkAction = getGeneratingAction(binArtifact); + + assertThat(getFirstArtifactEndingWith(linkAction.getInputs(), + "package/libObjcLib.a")).isNotNull(); + assertThat(getFirstArtifactEndingWith(linkAction.getInputs(), + "package/libDylib1Lib.a")).isNull(); + assertThat(getFirstArtifactEndingWith(linkAction.getInputs(), + "package/libDylib2Lib.a")).isNull(); + + // Sanity check that the identical binary without dylibs would be fully linked. + Action alternateLipobinAction = lipoBinAction("//package:alternate"); + Artifact alternateBinArtifact = getFirstArtifactEndingWith(alternateLipobinAction.getInputs(), + "package/alternate_bin"); + Action alternateLinkAction = getGeneratingAction(alternateBinArtifact); + + assertThat(getFirstArtifactEndingWith(alternateLinkAction.getInputs(), + "package/libObjcLib.a")).isNotNull(); + assertThat(getFirstArtifactEndingWith(alternateLinkAction.getInputs(), + "package/libDylib1Lib.a")).isNotNull(); + assertThat(getFirstArtifactEndingWith(alternateLinkAction.getInputs(), + "package/libDylib2Lib.a")).isNotNull(); + } + + /** + * Tests that direct cc_library dependencies of a dylib (and their dependencies) are correctly + * removed from the main binary. + */ + // transitively avoided, even if it is not present in deps. + public void checkAvoidDepsObjects_avoidViaCcLibrary(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, + "deps", "['//package:objcLib']", + "dylibs", "['//package:avoidLib']"); + scratchFrameworkSkylarkStub("frameworkstub/framework_stub.bzl"); + scratch.file("package/BUILD", + "load('//frameworkstub:framework_stub.bzl', 'framework_stub_rule')", + "framework_stub_rule(name = 'avoidLib', binary = ':avoidLibBinary')", + "apple_binary(name = 'avoidLibBinary', binary_type = 'dylib', srcs = [ 'c.m' ],", + " platform_type = 'ios',", + " deps = [':avoidCclib'])", + "cc_library(name = 'avoidCclib', srcs = ['cclib.c'], deps = [':avoidObjcLib'])", + "objc_library(name = 'objcLib', srcs = [ 'b.m' ], deps = [':avoidObjcLib'])", + "objc_library(name = 'avoidObjcLib', srcs = [ 'c.m' ])"); + + Action lipobinAction = actionProducingArtifact("//x:x", "_lipobin"); + Artifact binArtifact = getFirstArtifactEndingWith(lipobinAction.getInputs(), "x/x_bin"); + + Action action = getGeneratingAction(binArtifact); + + assertThat(getFirstArtifactEndingWith(action.getInputs(), "package/libobjcLib.a")).isNotNull(); + assertThat(getFirstArtifactEndingWith(action.getInputs(), "package/libavoidObjcLib.a")) + .isNull(); + } + + public void checkFilesToCompileOutputGroup(RuleType ruleType) throws Exception { + ruleType.scratchTarget(scratch, "srcs", "['a.m']"); + ConfiguredTarget target = getConfiguredTarget("//x:x"); + assertThat( + ActionsTestUtil.baseNamesOf( + getOutputGroup(target, OutputGroupProvider.FILES_TO_COMPILE))) + .isEqualTo("a.o"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcSkylarkTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcSkylarkTest.java new file mode 100644 index 0000000000..4dea91f381 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcSkylarkTest.java @@ -0,0 +1,1234 @@ +// 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 static com.google.devtools.build.lib.rules.objc.BundleableFile.BUNDLED_FIELD; +import static com.google.devtools.build.lib.rules.objc.BundleableFile.BUNDLE_PATH_FIELD; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ObjectArrays; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.util.ActionsTestUtil; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.rules.apple.AppleToolchain; +import com.google.devtools.build.lib.rules.apple.DottedVersion; +import com.google.devtools.build.lib.rules.objc.ObjcCommandLineOptions.ObjcCrosstoolMode; +import com.google.devtools.build.lib.syntax.SkylarkDict; +import com.google.devtools.build.lib.syntax.SkylarkNestedSet; +import com.google.devtools.build.lib.vfs.PathFragment; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for Skylark interaction with the objc_* rules. + */ +@RunWith(JUnit4.class) +public class ObjcSkylarkTest extends ObjcRuleTestCase { + + @Override + protected void useConfiguration(String... args) throws Exception { + // Do not test crosstool for skylark tests. + useConfiguration(ObjcCrosstoolMode.OFF, args); + } + + @Test + public void testSkylarkRuleCanDependOnNativeAppleRule() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def my_rule_impl(ctx):", + " dep = ctx.attr.deps[0]", + " return struct(", + " found_libs = dep.objc.library,", + " found_hdrs = dep.objc.header,", + " )", + "my_rule = rule(implementation = my_rule_impl,", + " attrs = {", + " 'deps': attr.label_list(allow_files = False, mandatory = False, providers = ['objc']),", + "})"); + scratch.file("examples/apple_skylark/a.m"); + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'my_rule')", + "my_rule(", + " name = 'my_target',", + " deps = [':lib'],", + ")", + "objc_library(", + " name = 'lib',", + " srcs = ['a.m'],", + " hdrs = ['b.h']", + ")"); + + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + SkylarkNestedSet skylarkLibraries = + (SkylarkNestedSet) skylarkTarget.get("found_libs"); + SkylarkNestedSet skylarkHdrs = + (SkylarkNestedSet) skylarkTarget.get("found_hdrs"); + + assertThat(ActionsTestUtil.baseArtifactNames(skylarkLibraries.getSet(Artifact.class))) + .contains("liblib.a"); + assertThat(ActionsTestUtil.baseArtifactNames(skylarkHdrs.getSet(Artifact.class))) + .contains("b.h"); + } + + @Test + public void testSkylarkProviderRetrievalNoneIfNoProvider() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def my_rule_impl(ctx):", + " dep = ctx.attr.deps[0]", + " objc_provider = dep.objc", + " return struct()", + "my_rule = rule(implementation = my_rule_impl,", + " attrs = {", + " 'deps': attr.label_list(allow_files = False, mandatory = False),", + "})"); + scratch.file("examples/apple_skylark/a.cc"); + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'my_rule')", + "my_rule(", + " name = 'my_target',", + " deps = [':lib'],", + ")", + "cc_library(", + " name = 'lib',", + " srcs = ['a.cc'],", + " hdrs = ['b.h']", + ")"); + try { + getConfiguredTarget("//examples/apple_skylark:my_target"); + fail("Should throw assertion error"); + } catch (AssertionError e) { + assertThat(e) + .hasMessageThat() + .contains("File \"/workspace/examples/apple_skylark/BUILD\", line 3"); + assertThat(e).hasMessageThat().contains("my_rule(name = 'my_target')"); + assertThat(e) + .hasMessageThat() + .contains("File \"/workspace/examples/rule/apple_rules.bzl\", line 3, in my_rule_impl"); + assertThat(e).hasMessageThat().contains("dep.objc"); + assertThat(e) + .hasMessageThat() + .contains("target (rule class of 'cc_library') doesn't have provider 'objc'."); + } + } + + @Test + public void testSkylarkProviderCanCheckForExistanceOfObjcProvider() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def my_rule_impl(ctx):", + " cc_has_provider = hasattr(ctx.attr.deps[0], 'objc')", + " objc_has_provider = hasattr(ctx.attr.deps[1], 'objc')", + " return struct(cc_has_provider=cc_has_provider, objc_has_provider=objc_has_provider)", + "my_rule = rule(implementation = my_rule_impl,", + " attrs = {", + " 'deps': attr.label_list(allow_files = False, mandatory = False),", + "})"); + scratch.file("examples/apple_skylark/a.cc"); + scratch.file("examples/apple_skylark/a.m"); + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'my_rule')", + "my_rule(", + " name = 'my_target',", + " deps = [':cc_lib', ':objc_lib'],", + ")", + "objc_library(", + " name = 'objc_lib',", + " srcs = ['a.m'],", + ")", + "cc_library(", + " name = 'cc_lib',", + " srcs = ['a.cc'],", + ")"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + boolean ccResult = + (boolean) skylarkTarget.get("cc_has_provider"); + boolean objcResult = + (boolean) skylarkTarget.get("objc_has_provider"); + assertThat(ccResult).isFalse(); + assertThat(objcResult).isTrue(); + } + + @Test + public void testSkylarkExportsObjcProviderToNativeRule() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def my_rule_impl(ctx):", + " dep = ctx.attr.deps[0]", + " objc_provider = dep.objc", + " return struct(objc=objc_provider)", + "swift_library = rule(implementation = my_rule_impl,", + " attrs = {", + " 'deps': attr.label_list(allow_files = False, mandatory = False, providers = ['objc'])", + "})"); + + scratch.file("examples/apple_skylark/a.m"); + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'swift_library')", + "swift_library(", + " name='my_target',", + " deps=[':lib'],", + ")", + "objc_library(", + " name = 'lib',", + " srcs = ['a.m'],", + " defines = ['mock_define']", + ")", + "objc_binary(", + " name = 'bin',", + " deps = [':my_target']", + ")"); + + ConfiguredTarget binaryTarget = getConfiguredTarget("//examples/apple_skylark:bin"); + ObjcProvider objcProvider = binaryTarget.getProvider(ObjcProvider.class); + + assertThat(Artifact.toRootRelativePaths(objcProvider.get(ObjcProvider.LIBRARY))) + .contains("examples/apple_skylark/liblib.a"); + assertThat(objcProvider.get(ObjcProvider.DEFINE)).contains("mock_define"); + } + + @Test + public void testObjcRuleCanDependOnArbitrarySkylarkRuleThatProvidesObjc() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def my_rule_impl(ctx):", + " objc_provider = apple_common.new_objc_provider(define=depset(['mock_define']))", + " return struct(objc=objc_provider)", + "my_rule = rule(implementation = my_rule_impl,", + " attrs = {})"); + + scratch.file("examples/apple_skylark/a.m"); + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'my_rule')", + "my_rule(", + " name='my_target'", + ")", + "objc_library(", + " name = 'lib',", + " srcs = ['a.m'],", + " deps = [':my_target']", + ")", + "objc_binary(", + " name = 'bin',", + " deps = [':lib']", + ")"); + + ConfiguredTarget binaryTarget = getConfiguredTarget("//examples/apple_skylark:bin"); + ObjcProvider objcProvider = binaryTarget.getProvider(ObjcProvider.class); + + assertThat(objcProvider.get(ObjcProvider.DEFINE)).contains("mock_define"); + } + + @Test + public void testSkylarkCanAccessAppleConfiguration() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def swift_binary_impl(ctx):", + " cpu = ctx.fragments.apple.ios_cpu()", + " platform = ctx.fragments.apple.ios_cpu_platform()", + " env = ctx.fragments.apple.target_apple_env(platform)", + " xcode_version = ctx.fragments.apple.xcode_version()", + " sdk_version = ctx.fragments.apple.sdk_version_for_platform(platform)", + " single_arch_platform = ctx.fragments.apple.single_arch_platform", + " single_arch_cpu = ctx.fragments.apple.single_arch_cpu", + " platform_type = single_arch_platform.platform_type", + " bitcode_mode = ctx.fragments.apple.bitcode_mode", + " return struct(", + " cpu=cpu,", + " env=env,", + " xcode_version=str(xcode_version),", + " sdk_version=str(sdk_version),", + " single_arch_platform=str(single_arch_platform),", + " single_arch_cpu=str(single_arch_cpu),", + " platform_type=str(platform_type),", + " bitcode_mode=str(bitcode_mode)", + " )", + "swift_binary = rule(", + "implementation = swift_binary_impl,", + "fragments = ['apple']", + ")"); + + scratch.file("examples/apple_skylark/a.m"); + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'swift_binary')", + "swift_binary(", + " name='my_target',", + ")"); + + useConfiguration("--cpu=ios_i386", "--xcode_version=7.1"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + + Object iosCpu = skylarkTarget.get("cpu"); + @SuppressWarnings("unchecked") + SkylarkDict<String, String> env = + (SkylarkDict<String, String>) skylarkTarget.get("env"); + Object sdkVersion = skylarkTarget.get("sdk_version"); + + assertThat(iosCpu).isEqualTo("i386"); + assertThat(env).containsEntry("APPLE_SDK_PLATFORM", "iPhoneSimulator"); + assertThat(env).containsEntry("APPLE_SDK_VERSION_OVERRIDE", "8.4"); + assertThat(sdkVersion).isEqualTo("8.4"); + assertThat(skylarkTarget.get("xcode_version")).isEqualTo("7.1"); + assertThat(skylarkTarget.get("single_arch_platform")).isEqualTo("IOS_SIMULATOR"); + assertThat(skylarkTarget.get("single_arch_cpu")).isEqualTo("i386"); + assertThat(skylarkTarget.get("platform_type")).isEqualTo("ios"); + assertThat(skylarkTarget.get("bitcode_mode")).isEqualTo("none"); + } + + @Test + public void testSkylarkCanAccessApplePlatformNames() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _test_rule_impl(ctx):", + " platform = ctx.fragments.apple.ios_cpu_platform()", + " return struct(", + " name=platform.name_in_plist,", + " )", + "test_rule = rule(", + "implementation = _test_rule_impl,", + "fragments = ['apple']", + ")"); + + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'test_rule')", + "test_rule(", + " name='my_target',", + ")"); + + useConfiguration("--cpu=ios_i386"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + Object name = skylarkTarget.get("name"); + assertThat(name).isEqualTo("iPhoneSimulator"); + } + + @Test + public void testSkylarkCanAccessAppleToolchain() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def swift_binary_impl(ctx):", + " apple_toolchain = apple_common.apple_toolchain()", + " sdk_dir = apple_toolchain.sdk_dir()", + " platform_developer_framework_dir = \\", + " apple_toolchain.platform_developer_framework_dir(ctx.fragments.apple)", + " return struct(", + " platform_developer_framework_dir=platform_developer_framework_dir,", + " sdk_dir=sdk_dir,", + " )", + "swift_binary = rule(", + "implementation = swift_binary_impl,", + "fragments = ['apple']", + ")"); + + scratch.file("examples/apple_skylark/a.m"); + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'swift_binary')", + "swift_binary(", + " name='my_target',", + ")"); + + useConfiguration("--cpu=ios_i386"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + String platformDevFrameworksDir = + (String) + skylarkTarget.get("platform_developer_framework_dir"); + String sdkDir = (String) skylarkTarget.get("sdk_dir"); + + assertThat(platformDevFrameworksDir) + .isEqualTo( + AppleToolchain.developerDir() + + "/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks"); + assertThat(sdkDir).isEqualTo(AppleToolchain.sdkDir()); + } + + @Test + public void testSkylarkCanAccessSdkAndMinimumOs() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def swift_binary_impl(ctx):", + " ios_sdk_version = ctx.fragments.apple.sdk_version_for_platform\\", + "(apple_common.platform.ios_device)", + " watchos_sdk_version = ctx.fragments.apple.sdk_version_for_platform\\", + "(apple_common.platform.watchos_device)", + " tvos_sdk_version = ctx.fragments.apple.sdk_version_for_platform\\", + "(apple_common.platform.tvos_device)", + " macos_sdk_version = ctx.fragments.apple.sdk_version_for_platform\\", + "(apple_common.platform.macos)", + " ios_minimum_os = ctx.fragments.apple.minimum_os_for_platform_type\\", + "(apple_common.platform_type.ios)", + " watchos_minimum_os = ctx.fragments.apple.minimum_os_for_platform_type\\", + "(apple_common.platform_type.watchos)", + " tvos_minimum_os = ctx.fragments.apple.minimum_os_for_platform_type\\", + "(apple_common.platform_type.tvos)", + " return struct(", + " ios_sdk_version=str(ios_sdk_version),", + " watchos_sdk_version=str(watchos_sdk_version),", + " tvos_sdk_version=str(tvos_sdk_version),", + " macos_sdk_version=str(macos_sdk_version),", + " ios_minimum_os=str(ios_minimum_os),", + " watchos_minimum_os=str(watchos_minimum_os),", + " tvos_minimum_os=str(tvos_minimum_os)", + " )", + "swift_binary = rule(", + "implementation = swift_binary_impl,", + "fragments = ['apple']", + ")"); + + scratch.file("examples/apple_skylark/a.m"); + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'swift_binary')", + "swift_binary(", + " name='my_target',", + ")"); + + useConfiguration("--ios_sdk_version=1.1", "--ios_minimum_os=1.0", + "--watchos_sdk_version=2.1", "--watchos_minimum_os=2.0", + "--tvos_sdk_version=3.1", "--tvos_minimum_os=3.0", + "--macos_sdk_version=4.1"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + + assertThat(skylarkTarget.get("ios_sdk_version")).isEqualTo("1.1"); + assertThat(skylarkTarget.get("ios_minimum_os")).isEqualTo("1.0"); + assertThat(skylarkTarget.get("watchos_sdk_version")).isEqualTo("2.1"); + assertThat(skylarkTarget.get("watchos_minimum_os")).isEqualTo("2.0"); + assertThat(skylarkTarget.get("tvos_sdk_version")).isEqualTo("3.1"); + assertThat(skylarkTarget.get("tvos_minimum_os")).isEqualTo("3.0"); + assertThat(skylarkTarget.get("macos_sdk_version")).isEqualTo("4.1"); + + useConfiguration("--ios_sdk_version=1.1", + "--watchos_sdk_version=2.1", + "--tvos_sdk_version=3.1", + "--macos_sdk_version=4.1"); + skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + assertThat(skylarkTarget.get("ios_sdk_version")).isEqualTo("1.1"); + assertThat(skylarkTarget.get("ios_minimum_os")).isEqualTo("1.1"); + assertThat(skylarkTarget.get("watchos_sdk_version")).isEqualTo("2.1"); + assertThat(skylarkTarget.get("watchos_minimum_os")).isEqualTo("2.1"); + assertThat(skylarkTarget.get("tvos_sdk_version")).isEqualTo("3.1"); + assertThat(skylarkTarget.get("tvos_minimum_os")).isEqualTo("3.1"); + assertThat(skylarkTarget.get("macos_sdk_version")).isEqualTo("4.1"); + } + + @Test + public void testSkylarkCanAccessObjcConfiguration() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/objc_rules.bzl", + "def swift_binary_impl(ctx):", + " copts = ctx.fragments.objc.copts", + " compilation_mode_copts = ctx.fragments.objc.copts_for_current_compilation_mode", + " ios_simulator_device = ctx.fragments.objc.ios_simulator_device", + " ios_simulator_version = ctx.fragments.objc.ios_simulator_version", + " signing_certificate_name = ctx.fragments.objc.signing_certificate_name", + " generate_dsym = ctx.fragments.objc.generate_dsym", + " return struct(", + " copts=copts,", + " compilation_mode_copts=compilation_mode_copts,", + " ios_simulator_device=ios_simulator_device,", + " ios_simulator_version=str(ios_simulator_version),", + " signing_certificate_name=signing_certificate_name,", + " generate_dsym=generate_dsym,", + " )", + "swift_binary = rule(", + "implementation = swift_binary_impl,", + "fragments = ['objc']", + ")"); + + scratch.file("examples/objc_skylark/a.m"); + scratch.file( + "examples/objc_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/objc_rules', 'swift_binary')", + "swift_binary(", + " name='my_target',", + ")"); + + useConfiguration( + "--compilation_mode=opt", + "--objccopt=-DTestObjcCopt", + "--ios_simulator_device='iPhone 6'", + "--ios_simulator_version=8.4", + "--ios_signing_cert_name='Apple Developer'", + "--apple_generate_dsym"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/objc_skylark:my_target"); + + @SuppressWarnings("unchecked") + List<String> copts = (List<String>) skylarkTarget.get("copts"); + @SuppressWarnings("unchecked") + List<String> compilationModeCopts = (List<String>) skylarkTarget.get("compilation_mode_copts"); + Object iosSimulatorDevice = skylarkTarget.get("ios_simulator_device"); + Object iosSimulatorVersion = skylarkTarget.get("ios_simulator_version"); + Object signingCertificateName = skylarkTarget.get("signing_certificate_name"); + Boolean generateDsym = (Boolean) skylarkTarget.get("generate_dsym"); + + assertThat(copts).contains("-DTestObjcCopt"); + assertThat(compilationModeCopts).containsExactlyElementsIn(ObjcConfiguration.OPT_COPTS); + assertThat(iosSimulatorDevice).isEqualTo("'iPhone 6'"); + assertThat(iosSimulatorVersion).isEqualTo("8.4"); + assertThat(signingCertificateName).isEqualTo("'Apple Developer'"); + assertThat(generateDsym).isTrue(); + } + + @Test + public void testSigningCertificateNameCanReturnNone() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/objc_rules.bzl", + "def my_rule_impl(ctx):", + " signing_certificate_name = ctx.fragments.objc.signing_certificate_name", + " return struct(", + " signing_certificate_name=str(signing_certificate_name),", + " )", + "my_rule = rule(", + "implementation = my_rule_impl,", + "fragments = ['objc']", + ")"); + + scratch.file("examples/objc_skylark/a.m"); + scratch.file( + "examples/objc_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/objc_rules', 'my_rule')", + "my_rule(", + " name='my_target',", + ")"); + + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/objc_skylark:my_target"); + + Object signingCertificateName = skylarkTarget.get("signing_certificate_name"); + assertThat(signingCertificateName).isEqualTo("None"); + } + + @Test + public void testUsesDebugEntitlementsIsTrueIfCompilationModeIsNotOpt() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/objc_rules.bzl", + "def test_rule_impl(ctx):", + " uses_device_debug_entitlements = ctx.fragments.objc.uses_device_debug_entitlements", + " return struct(", + " uses_device_debug_entitlements=uses_device_debug_entitlements,", + " )", + "test_rule = rule(", + "implementation = test_rule_impl,", + "fragments = ['objc']", + ")"); + + scratch.file("examples/objc_skylark/a.m"); + scratch.file( + "examples/objc_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/objc_rules', 'test_rule')", + "test_rule(", + " name='my_target',", + ")"); + + useConfiguration("--compilation_mode=dbg"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/objc_skylark:my_target"); + + boolean usesDeviceDebugEntitlements = (boolean) skylarkTarget + .get("uses_device_debug_entitlements"); + assertThat(usesDeviceDebugEntitlements).isTrue(); + } + + @Test + public void testUsesDebugEntitlementsIsFalseIfFlagIsExplicitlyFalse() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/objc_rules.bzl", + "def test_rule_impl(ctx):", + " uses_device_debug_entitlements = ctx.fragments.objc.uses_device_debug_entitlements", + " return struct(", + " uses_device_debug_entitlements=uses_device_debug_entitlements,", + " )", + "test_rule = rule(", + "implementation = test_rule_impl,", + "fragments = ['objc']", + ")"); + + scratch.file("examples/objc_skylark/a.m"); + scratch.file( + "examples/objc_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/objc_rules', 'test_rule')", + "test_rule(", + " name='my_target',", + ")"); + + useConfiguration( + "--compilation_mode=dbg", + "--nodevice_debug_entitlements"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/objc_skylark:my_target"); + + boolean usesDeviceDebugEntitlements = (boolean) skylarkTarget + .get("uses_device_debug_entitlements"); + assertThat(usesDeviceDebugEntitlements).isFalse(); + } + + @Test + public void testUsesDebugEntitlementsIsFalseIfCompilationModeIsOpt() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/objc_rules.bzl", + "def test_rule_impl(ctx):", + " uses_device_debug_entitlements = ctx.fragments.objc.uses_device_debug_entitlements", + " return struct(", + " uses_device_debug_entitlements=uses_device_debug_entitlements,", + " )", + "test_rule = rule(", + "implementation = test_rule_impl,", + "fragments = ['objc']", + ")"); + + scratch.file("examples/objc_skylark/a.m"); + scratch.file( + "examples/objc_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/objc_rules', 'test_rule')", + "test_rule(", + " name='my_target',", + ")"); + + useConfiguration("--compilation_mode=opt"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/objc_skylark:my_target"); + + boolean usesDeviceDebugEntitlements = (boolean) skylarkTarget + .get("uses_device_debug_entitlements"); + assertThat(usesDeviceDebugEntitlements).isFalse(); + } + + private ConfiguredTarget createObjcProviderSkylarkTarget(String... implLines) throws Exception { + String[] impl = + ObjectArrays.concat( + ObjectArrays.concat("def swift_binary_impl(ctx):", implLines), + new String[] { + "swift_binary = rule(", + "implementation = swift_binary_impl,", + "attrs = {", + " 'deps': attr.label_list(", + "allow_files = False, mandatory = False, providers = ['objc'])", + "})" + }, + String.class); + + scratch.file("examples/rule/BUILD"); + scratch.file("examples/rule/objc_rules.bzl", impl); + scratch.file( + "examples/objc_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/objc_rules', 'swift_binary')", + "swift_binary(", + " name='my_target',", + " deps=[':lib'],", + ")", + "objc_library(", + " name = 'lib',", + " srcs = ['a.m'],", + " defines = ['define_from_dep']", + ")"); + + return getConfiguredTarget("//examples/objc_skylark:my_target"); + } + + @Test + public void testSkylarkCanCreateObjcProviderFromScratch() throws Exception { + ConfiguredTarget skylarkTarget = + createObjcProviderSkylarkTarget( + " defines = depset(['define1', 'define2'])", + " linkopts = depset(['somelinkopt'])", + " created_provider = apple_common.new_objc_provider\\", + "(define=defines, linkopt=linkopts)", + " return struct(objc=created_provider)"); + + Iterable<String> foundLinkopts = + skylarkTarget.getProvider(ObjcProvider.class).get(ObjcProvider.LINKOPT); + Iterable<String> foundDefines = + skylarkTarget.getProvider(ObjcProvider.class).get(ObjcProvider.DEFINE); + boolean usesSwift = + skylarkTarget.getProvider(ObjcProvider.class).is(ObjcProvider.Flag.USES_SWIFT); + + assertThat(foundLinkopts).containsExactly("somelinkopt"); + assertThat(foundDefines).containsExactly("define1", "define2"); + assertThat(usesSwift).isFalse(); + } + + @Test + public void testSkylarkCanPassLinkInputsInObjcProvider() throws Exception { + ConfiguredTarget skylarkTarget = + createObjcProviderSkylarkTarget( + " file = ctx.actions.declare_file('foo.ast')", + " ctx.actions.run_shell(outputs=[file], command='echo')", + " link_inputs = depset([file])", + " created_provider = apple_common.new_objc_provider\\", + "(link_inputs=link_inputs)", + " return struct(objc=created_provider)"); + + Iterable<Artifact> foundLinkInputs = + skylarkTarget.getProvider(ObjcProvider.class).get(ObjcProvider.LINK_INPUTS); + assertThat(ActionsTestUtil.baseArtifactNames(foundLinkInputs)).contains("foo.ast"); + } + + @Test + public void testSkylarkCanPassUsesSwiftFlag() throws Exception { + ConfiguredTarget skylarkTarget = + createObjcProviderSkylarkTarget( + " created_provider = apple_common.new_objc_provider(uses_swift=True)", + " return struct(objc=created_provider)"); + + boolean usesSwift = + skylarkTarget.getProvider(ObjcProvider.class).is(ObjcProvider.Flag.USES_SWIFT); + + assertThat(usesSwift).isTrue(); + } + + @Test + public void testSkylarkCanCreateObjcProviderWithPathFragments() throws Exception { + ConfiguredTarget skylarkTarget = + createObjcProviderSkylarkTarget( + " includes = depset(['path1', 'path_dir/path2', 'path_dir1/path_dir2/path3'])", + " created_provider = apple_common.new_objc_provider\\", + "(include=includes)", + " return struct(objc=created_provider)"); + + Iterable<PathFragment> foundIncludes = + skylarkTarget.getProvider(ObjcProvider.class).get(ObjcProvider.INCLUDE); + + assertThat(foundIncludes) + .containsExactly( + PathFragment.create("path1"), + PathFragment.create("path_dir/path2"), + PathFragment.create("path_dir1/path_dir2/path3")); + } + + @Test + public void testSkylarkCanCreateObjcProviderWithStrictDeps() throws Exception { + ConfiguredTarget skylarkTarget = + createObjcProviderSkylarkTarget( + " strict_includes = depset(['path1'])", + " propagated_includes = depset(['path2'])", + " strict_provider = apple_common.new_objc_provider\\", + "(include=strict_includes)", + " created_provider = apple_common.new_objc_provider\\", + "(include=propagated_includes, direct_dep_providers=[strict_provider])", + " return struct(objc=created_provider)"); + + ObjcProvider skylarkProvider = skylarkTarget.getProvider(ObjcProvider.class); + ObjcProvider skylarkProviderDirectDepender = + new ObjcProvider.Builder().addTransitiveAndPropagate(skylarkProvider).build(); + ObjcProvider skylarkProviderIndirectDepender = + new ObjcProvider.Builder().addTransitiveAndPropagate(skylarkProviderDirectDepender).build(); + + assertThat(skylarkProvider.get(ObjcProvider.INCLUDE)) + .containsExactly(PathFragment.create("path1"), PathFragment.create("path2")); + assertThat(skylarkProviderDirectDepender.get(ObjcProvider.INCLUDE)) + .containsExactly(PathFragment.create("path1"), PathFragment.create("path2")); + assertThat(skylarkProviderIndirectDepender.get(ObjcProvider.INCLUDE)) + .containsExactly(PathFragment.create("path2")); + } + + @Test + public void testSkylarkCanCreateObjcProviderFromObjcProvider() throws Exception { + ConfiguredTarget skylarkTarget = + createObjcProviderSkylarkTarget( + " dep = ctx.attr.deps[0]", + " define = depset(['define_from_impl'])", + " created_provider = apple_common.new_objc_provider\\", + "(providers=[dep.objc], define=define)", + " return struct(objc=created_provider)"); + + Iterable<String> foundStrings = + skylarkTarget.getProvider(ObjcProvider.class).get(ObjcProvider.DEFINE); + + assertThat(foundStrings).containsExactly("define_from_dep", "define_from_impl"); + } + + @Test + public void testSkylarkErrorOnBadObjcProviderInputKey() throws Exception { + try { + createObjcProviderSkylarkTarget( + " created_provider = apple_common.new_objc_provider(foo=depset(['bar']))", + " return struct(objc=created_provider)"); + fail("Should throw AssertionError"); + } catch (AssertionError e) { + assertThat(e) + .hasMessageThat() + .contains(String.format(AppleSkylarkCommon.BAD_KEY_ERROR, "foo")); + } + } + + @Test + public void testSkylarkErrorOnNonSetObjcProviderInputValue() throws Exception { + try { + createObjcProviderSkylarkTarget( + " created_provider = apple_common.new_objc_provider(library='bar')", + " return struct(objc=created_provider)"); + fail("Should throw AssertionError"); + } catch (AssertionError e) { + assertThat(e) + .hasMessageThat() + .contains(String.format(AppleSkylarkCommon.NOT_SET_ERROR, "library", "string")); + } + } + + @Test + public void testSkylarkErrorOnObjcProviderInputValueWrongSetType() throws Exception { + try { + createObjcProviderSkylarkTarget( + " created_provider = apple_common.new_objc_provider(library=depset(['bar']))", + " return struct(objc=created_provider)"); + fail("Should throw AssertionError"); + } catch (AssertionError e) { + assertThat(e) + .hasMessageThat() + .contains( + String.format(AppleSkylarkCommon.BAD_SET_TYPE_ERROR, "library", "File", "string")); + } + } + + @Test + public void testSkylarkErrorOnNonIterableObjcProviderProviderValue() throws Exception { + try { + createObjcProviderSkylarkTarget( + " created_provider = apple_common.new_objc_provider(providers='bar')", + " return struct(objc=created_provider)"); + fail("Should throw AssertionError"); + } catch (AssertionError e) { + assertThat(e) + .hasMessageThat() + .contains(String.format(AppleSkylarkCommon.BAD_PROVIDERS_ITER_ERROR, "string")); + } + } + + @Test + public void testSkylarkErrorOnBadIterableObjcProviderProviderValue() throws Exception { + try { + createObjcProviderSkylarkTarget( + " created_provider = apple_common.new_objc_provider(providers=['bar'])", + " return struct(objc=created_provider)"); + fail("Should throw AssertionError"); + } catch (AssertionError e) { + assertThat(e) + .hasMessageThat() + .contains(String.format(AppleSkylarkCommon.BAD_PROVIDERS_ELEM_ERROR, "string")); + } + } + + @Test + public void testEmptyObjcProviderKeysArePresent() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def swift_binary_impl(ctx):", + " objc_provider = ctx.attr.deps[0].objc", + " return struct(", + " empty_value=objc_provider.include,", + " )", + "swift_binary = rule(", + "implementation = swift_binary_impl,", + "fragments = ['apple'],", + "attrs = {", + " 'deps': attr.label_list(allow_files = False, mandatory = False, providers = ['objc'])", + "})"); + + scratch.file("examples/apple_skylark/a.m"); + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'swift_binary')", + "swift_binary(", + " name='my_target',", + " deps=[':lib'],", + ")", + "objc_library(", + " name = 'lib',", + " srcs = ['a.m'],", + ")"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + SkylarkNestedSet emptyValue = + (SkylarkNestedSet) + skylarkTarget.get("empty_value"); + assertThat(emptyValue.toCollection()).isEmpty(); + } + + @Test + public void testSkylarkCanAccessProvidedBundleFiles() throws Exception { + // Since the collections of structs with Artifact values are extremely difficult to test with + // Truth, we fudge them in the Skylark side to return easily comparable dictionaries instead. + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _simplify_bundle_file(bf):", + " return {'file': bf.file.path, 'bundle_path': bf.bundle_path}", + "def _test_rule_impl(ctx):", + " dep = ctx.attr.deps[0]", + " objc_provider = dep.objc", + " bundle_file = [_simplify_bundle_file(bf) for bf in list(objc_provider.bundle_file)]", + " return struct(", + " bundle_file=bundle_file,", + " )", + "test_rule = rule(implementation = _test_rule_impl,", + " attrs = {", + " 'deps': attr.label_list(allow_files = False, mandatory = False, providers = ['objc'])", + "})"); + + scratch.file("examples/apple_skylark/a.m"); + scratch.file("examples/apple_skylark/flattened/a/a.txt"); + scratch.file("examples/apple_skylark/flattened/b.lproj/b.txt"); + scratch.file("examples/apple_skylark/structured/c/c.txt"); + scratch.file("examples/apple_skylark/structured/d/d.txt"); + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'test_rule')", + "objc_library(", + " name = 'lib',", + " srcs = ['a.m'],", + " resources = glob(['flattened/**']),", + " structured_resources = glob(['structured/**']),", + ")", + "test_rule(", + " name = 'my_target',", + " deps = [':lib'],", + ")"); + + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + Iterable<?> bundleFiles = (Iterable<?>) + skylarkTarget.get("bundle_file"); + assertThat(bundleFiles).containsAllOf(ImmutableMap.of( + BUNDLE_PATH_FIELD, "a.txt", + BUNDLED_FIELD, "examples/apple_skylark/flattened/a/a.txt" + ), ImmutableMap.of( + BUNDLE_PATH_FIELD, "b.lproj/b.txt", + BUNDLED_FIELD, "examples/apple_skylark/flattened/b.lproj/b.txt" + ), ImmutableMap.of( + BUNDLE_PATH_FIELD, "structured/c/c.txt", + BUNDLED_FIELD, "examples/apple_skylark/structured/c/c.txt" + ), ImmutableMap.of( + BUNDLE_PATH_FIELD, "structured/d/d.txt", + BUNDLED_FIELD, "examples/apple_skylark/structured/d/d.txt" + )); + } + + @Test + public void testSkylarkCanAccessSdkFrameworks() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _test_rule_impl(ctx):", + " dep = ctx.attr.deps[0]", + " objc_provider = dep.objc", + " return struct(", + " sdk_frameworks=objc_provider.sdk_framework,", + " )", + "test_rule = rule(implementation = _test_rule_impl,", + " attrs = {", + " 'deps': attr.label_list(allow_files = False, mandatory = False, providers = ['objc'])", + "})"); + + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'test_rule')", + "objc_library(", + " name = 'lib',", + " srcs = ['a.m'],", + " sdk_frameworks = ['Accelerate', 'GLKit'],", + ")", + "test_rule(", + " name = 'my_target',", + " deps = [':lib'],", + ")"); + + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + SkylarkNestedSet sdkFrameworks = (SkylarkNestedSet) + skylarkTarget.get("sdk_frameworks"); + assertThat(sdkFrameworks.toCollection()).containsAllOf("Accelerate", "GLKit"); + } + + @Test + public void testSkylarkCanAccessAndUseApplePlatformTypes() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _test_rule_impl(ctx):", + " apple = ctx.fragments.apple", + " ios_platform = apple.multi_arch_platform(apple_common.platform_type.ios)", + " watchos_platform = apple.multi_arch_platform(apple_common.platform_type.watchos)", + " tvos_platform = apple.multi_arch_platform(apple_common.platform_type.tvos)", + " return struct(", + " ios_platform=str(ios_platform),", + " watchos_platform=str(watchos_platform),", + " tvos_platform=str(tvos_platform),", + " )", + "test_rule = rule(implementation = _test_rule_impl,", + " fragments = ['apple'])"); + + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'test_rule')", + "test_rule(", + " name = 'my_target',", + ")"); + + useConfiguration( + "--ios_multi_cpus=arm64,armv7", + "--watchos_cpus=armv7k", + "--tvos_cpus=arm64"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + Object iosPlatform = + skylarkTarget.get("ios_platform"); + Object watchosPlatform = + skylarkTarget.get("watchos_platform"); + Object tvosPlatform = + skylarkTarget.get("tvos_platform"); + + assertThat(iosPlatform).isEqualTo("IOS_DEVICE"); + assertThat(watchosPlatform).isEqualTo("WATCHOS_DEVICE"); + assertThat(tvosPlatform).isEqualTo("TVOS_DEVICE"); + } + + @Test + public void testPlatformIsDeviceReturnsTrueForDevicePlatforms() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _test_rule_impl(ctx):", + " apple = ctx.fragments.apple", + " platform = apple.multi_arch_platform(apple_common.platform_type.ios)", + " return struct(", + " is_device=platform.is_device,", + " )", + "test_rule = rule(implementation = _test_rule_impl,", + " fragments = ['apple'])"); + + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'test_rule')", + "test_rule(", + " name = 'my_target',", + ")"); + + useConfiguration( + "--ios_multi_cpus=arm64,armv7"); + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + Boolean isDevice = + (Boolean) skylarkTarget.get("is_device"); + assertThat(isDevice).isTrue(); + } + + @Test + public void testPlatformIsDeviceReturnsFalseForSimulatorPlatforms() throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _test_rule_impl(ctx):", + " apple = ctx.fragments.apple", + " platform = apple.multi_arch_platform(apple_common.platform_type.ios)", + " return struct(", + " is_device=platform.is_device,", + " )", + "test_rule = rule(implementation = _test_rule_impl,", + " fragments = ['apple'])"); + + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'test_rule')", + "test_rule(", + " name = 'my_target',", + ")"); + + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + Boolean isDevice = + (Boolean) skylarkTarget.get("is_device"); + assertThat(isDevice).isFalse(); + } + + @Test + public void testXcTestAppProviderCanBeCreated() throws Exception { + scratch.file("examples/rule/BUILD", + "exports_files(['test_artifact'])"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _test_rule_impl(ctx):", + " artifact = list(ctx.attr.test_artifact.files)[0]", + " objc_provider = apple_common.new_objc_provider(define=depset(['TEST_DEFINE']))", + " xctest_app_provider = apple_common.new_xctest_app_provider(", + " bundle_loader=artifact, ipa=artifact, objc_provider=objc_provider)", + " return struct(", + " xctest_app=xctest_app_provider,", + " )", + "test_rule = rule(implementation = _test_rule_impl,", + " attrs = {", + " 'test_artifact': attr.label(", + " allow_single_file=True,", + " default=Label('//examples/rule:test_artifact')),", + " })"); + + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'test_rule')", + "test_rule(", + " name = 'my_target',", + ")"); + + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + XcTestAppProvider xcTestAppProvider = skylarkTarget.getProvider(XcTestAppProvider.class); + + assertThat(xcTestAppProvider.getBundleLoader().getRootRelativePathString()) + .isEqualTo("examples/rule/test_artifact"); + assertThat(xcTestAppProvider.getIpa().getRootRelativePathString()) + .isEqualTo("examples/rule/test_artifact"); + assertThat(xcTestAppProvider.getObjcProvider().get(ObjcProvider.DEFINE)) + .containsExactly("TEST_DEFINE"); + } + + @Test + public void testSkylarkWithRunMemleaksEnabled() throws Exception { + useConfiguration("--ios_memleaks"); + checkSkylarkRunMemleaksWithExpectedValue(true); + } + + @Test + public void testSkylarkWithRunMemleaksDisabled() throws Exception { + checkSkylarkRunMemleaksWithExpectedValue(false); + } + + @Test + public void testDottedVersion() throws Exception { + scratch.file("examples/rule/BUILD", + "exports_files(['test_artifact'])"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _test_rule_impl(ctx):", + " version = apple_common.dotted_version('5.4')", + " return struct(", + " version=version", + " )", + "test_rule = rule(implementation = _test_rule_impl)"); + + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'test_rule')", + "test_rule(", + " name = 'my_target',", + ")"); + + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + DottedVersion version = (DottedVersion) skylarkTarget.get("version"); + assertThat(version).isEqualTo(DottedVersion.fromString("5.4")); + } + + @Test + public void testDottedVersion_invalid() throws Exception { + scratch.file("examples/rule/BUILD", + "exports_files(['test_artifact'])"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _test_rule_impl(ctx):", + " version = apple_common.dotted_version('hello')", + " return struct(", + " version=version", + " )", + "test_rule = rule(implementation = _test_rule_impl)"); + + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'test_rule')", + "test_rule(", + " name = 'my_target',", + ")"); + + try { + getConfiguredTarget("//examples/apple_skylark:my_target"); + fail("Expected an error to be thrown for invalid dotted version string"); + } catch (AssertionError e) { + assertThat(e).hasMessageThat().contains("illegal argument in call to dotted_version"); + assertThat(e).hasMessageThat().contains("Dotted version components must all be of the form"); + } + } + + private void checkSkylarkRunMemleaksWithExpectedValue(boolean expectedValue) throws Exception { + scratch.file("examples/rule/BUILD"); + scratch.file( + "examples/rule/apple_rules.bzl", + "def _test_rule_impl(ctx):", + " return struct(run_memleaks = ctx.fragments.objc.run_memleaks)", + "test_rule = rule(implementation = _test_rule_impl,", + " fragments = ['objc'],", + " attrs = {},", + ")"); + + scratch.file( + "examples/apple_skylark/BUILD", + "package(default_visibility = ['//visibility:public'])", + "load('/examples/rule/apple_rules', 'test_rule')", + "test_rule(", + " name = 'my_target',", + ")"); + + ConfiguredTarget skylarkTarget = getConfiguredTarget("//examples/apple_skylark:my_target"); + + boolean runMemleaks = (boolean) skylarkTarget.get("run_memleaks"); + assertThat(runMemleaks).isEqualTo(expectedValue); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/OnlyNeedsSourcesRuleType.java b/src/test/java/com/google/devtools/build/lib/rules/objc/OnlyNeedsSourcesRuleType.java new file mode 100644 index 0000000000..043974d736 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/OnlyNeedsSourcesRuleType.java @@ -0,0 +1,42 @@ +// 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.collect.ImmutableList; +import com.google.devtools.build.lib.testutil.Scratch; +import java.io.IOException; +import java.util.Set; + +/** + * This exists for convenience for any rule type that requires only one file in {@code srcs} or + * {@code non_arc_srcs}, and no other attributes. + */ +final class OnlyNeedsSourcesRuleType extends RuleType { + OnlyNeedsSourcesRuleType(String ruleTypeName) { + super(ruleTypeName); + } + + @Override + Iterable<String> requiredAttributes( + Scratch scratch, String packageDir, Set<String> alreadyAdded) throws IOException { + ImmutableList.Builder<String> attributes = new ImmutableList.Builder<>(); + if (!alreadyAdded.contains("srcs") && !alreadyAdded.contains("non_arc_srcs")) { + scratch.file(packageDir + "/a.m"); + scratch.file(packageDir + "/private.h"); + attributes.add("srcs = ['a.m', 'private.h']"); + } + return attributes.build(); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/README.md b/src/test/java/com/google/devtools/build/lib/rules/objc/README.md new file mode 100644 index 0000000000..388dbc6e2b --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/README.md @@ -0,0 +1,13 @@ +# Objc rule tests +This package contains subclasses of ObjcRuleTestCase. These test cases use +--experimental_objc_crosstool=all by default, as per +ObjcRuleTestCase#useConfiguration. This is meant to test the "crosstool" +case. + +The "legacy" case (that is, --experimental_objc_crosstool=off) is also tested +in subclasses prefixed with the word "Legacy". Tests in the superclass, then, +are tested for both crosstool configurations, while tests in the subclass are +only tested for --experimental_objc_crosstool=off. + +As the crosstool case is developed, tests will moved up to superclasses. +Eventually, the legacy subclasses will be removed. diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/RuleType.java b/src/test/java/com/google/devtools/build/lib/rules/objc/RuleType.java new file mode 100644 index 0000000000..18875c680d --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/RuleType.java @@ -0,0 +1,145 @@ +// 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.collect.ImmutableMap; +import com.google.devtools.build.lib.testutil.Scratch; +import com.google.devtools.build.lib.util.Preconditions; +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 implementator 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 { + String target = target(scratch, "x", "x", checkSpecificAttrs); + scratch.file("x/BUILD", target); + return target; + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/TargetDeviceFamilyTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/TargetDeviceFamilyTest.java new file mode 100644 index 0000000000..7cc7b001e9 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/TargetDeviceFamilyTest.java @@ -0,0 +1,87 @@ +// 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 static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.lib.rules.objc.TargetDeviceFamily.InvalidFamilyNameException; +import com.google.devtools.build.lib.rules.objc.TargetDeviceFamily.RepeatedFamilyNameException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link TargetDeviceFamily}. + */ +@RunWith(JUnit4.class) +public class TargetDeviceFamilyTest { + @Test + public void uiDeviceFamilyValuesUndefinedForEmpty() { + assertThat(TargetDeviceFamily.UI_DEVICE_FAMILY_VALUES.keySet()) + .doesNotContain(ImmutableSet.<TargetDeviceFamily>of()); + } + + @Test + public void fromBuildSettings() { + assertThat(TargetDeviceFamily.fromBuildSetting("1,2")) + .isEqualTo(ImmutableSet.of(TargetDeviceFamily.IPAD, TargetDeviceFamily.IPHONE)); + assertThat(TargetDeviceFamily.fromBuildSetting(" 1, 2")) + .isEqualTo(ImmutableSet.of(TargetDeviceFamily.IPAD, TargetDeviceFamily.IPHONE)); + assertThat(TargetDeviceFamily.fromBuildSetting("1,2\n")) + .isEqualTo(ImmutableSet.of(TargetDeviceFamily.IPAD, TargetDeviceFamily.IPHONE)); + assertThat(TargetDeviceFamily.fromBuildSetting("1")) + .isEqualTo(ImmutableSet.of(TargetDeviceFamily.IPHONE)); + assertThat(TargetDeviceFamily.fromBuildSetting("2")) + .isEqualTo(ImmutableSet.of(TargetDeviceFamily.IPAD)); + } + + private void checkFromNamesInRuleThrows( + Class<? extends Exception> expectedClass, String... names) { + try { + TargetDeviceFamily.fromNamesInRule(ImmutableList.copyOf(names)); + fail("should have thrown"); + } catch (IllegalArgumentException expected) { + assertThat(expected.getClass()).isEqualTo(expectedClass); + } + } + + @Test + public void fromNamesInRule_errors() { + checkFromNamesInRuleThrows(InvalidFamilyNameException.class, "foo"); + checkFromNamesInRuleThrows(InvalidFamilyNameException.class, "foo", "bar"); + checkFromNamesInRuleThrows(InvalidFamilyNameException.class, "iphone", "ipad", "bar"); + checkFromNamesInRuleThrows(RepeatedFamilyNameException.class, "iphone", "iphone"); + checkFromNamesInRuleThrows(RepeatedFamilyNameException.class, "ipad", "ipad"); + } + + @Test + public void fromNamesInRule() { + assertThat(TargetDeviceFamily.fromNamesInRule(ImmutableList.<String>of())) + .isEmpty(); + assertThat(TargetDeviceFamily.fromNamesInRule(ImmutableList.of("iphone", "ipad"))) + .containsExactly(TargetDeviceFamily.IPAD, TargetDeviceFamily.IPHONE) + .inOrder(); + assertThat(TargetDeviceFamily.fromNamesInRule(ImmutableList.of("ipad", "iphone"))) + .containsExactly(TargetDeviceFamily.IPAD, TargetDeviceFamily.IPHONE) + .inOrder(); + assertThat(TargetDeviceFamily.fromNamesInRule(ImmutableList.of("iphone"))) + .containsExactly(TargetDeviceFamily.IPHONE); + assertThat(TargetDeviceFamily.fromNamesInRule(ImmutableList.of("ipad"))) + .containsExactly(TargetDeviceFamily.IPAD); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ValueTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ValueTest.java new file mode 100644 index 0000000000..dbb72843cd --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ValueTest.java @@ -0,0 +1,75 @@ +// 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 static org.junit.Assert.fail; + +import com.google.common.testing.EqualsTester; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@class Value}. + */ +@RunWith(JUnit4.class) +public class ValueTest { + private static final class PersonName extends Value<PersonName> { + public PersonName(String first, String last) { + super(first, last); + } + } + + private enum NameData { + JOHN_DOE, JANE_DOE, JOHN_SMITH, + } + + private PersonName make(NameData what) { + switch (what) { + case JOHN_DOE: + return new PersonName("John", "Doe"); + case JANE_DOE: + return new PersonName("Jane", "Doe"); + case JOHN_SMITH: + return new PersonName("John", "Smith"); + default: + throw new IllegalArgumentException("unknown: " + what); + } + } + + @Test + public void nullNotAllowedInMemberData() { + try { + new PersonName(null, "Doe"); + fail("should have thrown"); + } catch (NullPointerException expected) {} + } + + @Test + public void equality() { + EqualsTester tester = new EqualsTester(); + for (NameData what : NameData.values()) { + tester.addEqualityGroup(make(what), make(what)); + } + tester.testEquals(); + } + + @Test + public void testToString() { + assertThat(make(NameData.JOHN_DOE).toString()).contains("John"); + assertThat(make(NameData.JOHN_DOE).toString()).contains("Doe"); + } +} |