// 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.CompilationSupport.AUTOMATIC_SDK_FRAMEWORKS; 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.MODULE_MAP; 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.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.CustomCommandLine.Builder; import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg; 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.cmdline.RepositoryName; 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.ApplePlatform; import com.google.devtools.build.lib.rules.apple.ApplePlatform.PlatformType; 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.cpp.CppLinkAction; import com.google.devtools.build.lib.rules.objc.CompilationSupport.ExtraLinkArgs; import com.google.devtools.build.lib.testutil.TestConstants; 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 javax.annotation.Nullable; import org.junit.Before; /** * Superclass for all Obj-C rule tests. * *

TODO(matvore): split this up into more helper classes, especially the check... methods, which * are many and not shared by all objc_ rules. *

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 = toolsRepoExecPath("tools/objc/actoolwrapper"); protected static final String MOCK_IBTOOLWRAPPER_PATH = toolsRepoExecPath("tools/objc/ibtoolwrapper"); protected static final String MOCK_BUNDLEMERGE_PATH = toolsRepoExecPath("tools/objc/bundlemerge"); protected static final String MOCK_MOMCWRAPPER_PATH = toolsRepoExecPath("tools/objc/momcwrapper"); protected static final String MOCK_SWIFTSTDLIBTOOLWRAPPER_PATH = toolsRepoExecPath("tools/objc/swiftstdlibtoolwrapper"); protected static final String MOCK_LIBTOOL_PATH = toolsRepoExecPath("tools/objc/libtool"); protected static final String MOCK_XCRUNWRAPPER_PATH = toolsRepoExecPath("tools/objc/xcrunwrapper"); protected static final ImmutableList FASTBUILD_COPTS = ImmutableList.of("-O0", "-DDEBUG"); 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) + getTargetConfiguration().getGenfilesDirectory(RepositoryName.MAIN) .getExecPath().getBaseName(); } private String configurationDir( String arch, ConfigurationDistinguisher configurationDistinguisher, DottedVersion minOsVersion) { String minOsSegment = minOsVersion == null ? "" : "-min" + minOsVersion; switch (configurationDistinguisher) { case UNKNOWN: return String.format("%s-out/ios_%s-fastbuild/", TestConstants.PRODUCT_NAME, arch); case APPLEBIN_IOS: return String.format( "%1$s-out/ios-%2$s%4$s-%3$s-ios_%2$s-fastbuild/", TestConstants.PRODUCT_NAME, arch, configurationDistinguisher.toString().toLowerCase(Locale.US), minOsSegment); case APPLEBIN_WATCHOS: return String.format( "%1$s-out/watchos-%2$s%4$s-%3$s-watchos_%2$s-fastbuild/", TestConstants.PRODUCT_NAME, arch, configurationDistinguisher.toString().toLowerCase(Locale.US), minOsSegment); 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, null); } /** * 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 APPLEBIN_IOS: return String.format( "%s-out/%s-ios_%s-fastbuild/bin/", TestConstants.PRODUCT_NAME, configurationDistinguisher.toString().toLowerCase(Locale.US), arch); case UNKNOWN: return String.format("%s-out/ios_%s-fastbuild/bin/", TestConstants.PRODUCT_NAME, arch); default: throw new AssertionError(); } } /** * Returns the genfiles dir for iOS builds in the root architecture. */ protected static String rootConfigurationGenfiles() { return TestConstants.PRODUCT_NAME + "-out/gcc-4.4.0-glibc-2.3.6-grte-k8-fastbuild/genfiles/"; } protected String execPathEndingWith(Iterable artifacts, String suffix) { return getFirstArtifactEndingWith(artifacts, suffix).getExecPathString(); } @Before public final void initializeMockToolsConfig() throws Exception { MockObjcSupport.setup(mockToolsConfig); MockProtoSupport.setup(mockToolsConfig); // Set flags required by objc builds. useConfiguration(); } protected static String frameworkDir(ConfiguredTarget target) { AppleConfiguration configuration = target.getConfiguration().getFragment(AppleConfiguration.class); return frameworkDir(configuration.getSingleArchPlatform()); } protected static String frameworkDir(ApplePlatform 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 apple_binary} target writer for the label indicated by the given String. */ protected ScratchAttributeWriter createBinaryTargetWriter(String labelString) { return ScratchAttributeWriter.fromLabelString(this, "apple_binary", labelString) .set("platform_type", "'ios'"); } 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 compilationModeCopts(CompilationMode mode) { switch (mode) { case DBG: return ImmutableList.builder() .addAll(ObjcConfiguration.DBG_COPTS) .build(); case OPT: return ObjcConfiguration.OPT_COPTS; case FASTBUILD: return FASTBUILD_COPTS; } throw new AssertionError(); } protected static String listAttribute(String name, Iterable 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(); } @Override protected void useConfiguration(String... args) throws Exception { ImmutableList.Builder extraArgsBuilder = ImmutableList.builder(); extraArgsBuilder.addAll(TestConstants.OSX_CROSSTOOL_FLAGS); // TODO(b/68751876): Set --apple_crosstool_top and --crosstool_top using the // AppleCrosstoolTransition extraArgsBuilder .add("--xcode_version_config=" + MockObjcSupport.XCODE_VERSION_CONFIG) .add("--apple_crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL) .add("--crosstool_top=" + MockObjcSupport.DEFAULT_OSX_CROSSTOOL); ImmutableList 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 srcs, List 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 srcs, List 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 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 getSplitConfigurations(BuildConfiguration configuration, SplitTransition splitTransition) throws InterruptedException { ImmutableList.Builder 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"); 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.of(), extraLinkArgs); } // Regression test for b/29094356. protected void checkLinkActionDuplicateInputs(RuleType ruleType, ExtraLinkArgs extraLinkArgs) throws Exception { useConfiguration("--cpu=ios_i386"); 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) throws Exception { Artifact filelistArtifact = getFirstArtifactEndingWith(originalAction.getInputs(), objlistName); ParameterFileWriteAction fileWriteAction = (ParameterFileWriteAction) getGeneratingAction(filelistArtifact); ImmutableList.Builder 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 inputArchives, List importedFrameworks, ExtraLinkArgs extraLinkArgs) throws Exception { 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 frameworkPathFragmentParents = ImmutableList.builder(); ImmutableList.Builder frameworkPathBaseNames = ImmutableList.builder(); for (PathFragment importedFramework : importedFrameworks) { frameworkPathFragmentParents.add(importedFramework.getParentDirectory().toString()); frameworkPathBaseNames.add(importedFramework.getBaseName()); } ImmutableList expectedCommandLineFragments = ImmutableList.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(ApplePlatform.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"); String commandLine = Joiner.on(" ").join(action.getArguments()); assertThat(commandLine).contains("-stdlib=libc++"); assertThat(commandLine).contains("-std=gnu++11"); } protected Map mobileProvisionProfiles(BundleMergeProtos.Control control) { Map 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 env) throws Exception { assertAppleSdkVersionEnv(env, DEFAULT_IOS_SDK_VERSION); } protected void assertAppleSdkVersionEnv(Map env, DottedVersion versionNumber) throws Exception { assertThat(env).containsEntry("APPLE_SDK_VERSION_OVERRIDE", versionNumber.toString()); } protected void assertAppleSdkPlatformEnv( Map 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"); Map substitutions = action .getSubstitutions() .stream() .collect(ImmutableMap.toImmutableMap(sub -> sub.getKey(), s -> s.getValue())); ; assertThat(substitutions.get("%ipa_file%")).isEqualTo("x/x.ipa"); assertThat(substitutions.get("%sim_device%")).isEqualTo("'iPhone X'"); assertThat(substitutions.get("%sdk_version%")).isEqualTo("3"); assertThat(substitutions.get("%app_name%")).isEqualTo("x"); assertThat(substitutions.get("%std_redirect_dylib_path%")) .endsWith("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(toolsRepoExecPath("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(toolsRepoExecPath("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(toolsRepoExecPath("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.getPossibleInputsForTesting())) .containsAllOf("x/a.m", "x/a.h", "x/private.h", "lib1/hdr.h", "lib2/hdr.h"); 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.getPossibleInputsForTesting())) .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"); // In the legacy rules, "-F" is followed by a space in framework includes. String linkActionArgsNoSpaces = linkActionArgs.replace(" ", ""); assertThat(linkActionArgsNoSpaces).contains("-Ffx"); 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 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).get(ObjcProvider.SKYLARK_CONSTRUCTOR); } protected CommandAction archiveAction(String label) throws Exception { ConfiguredTarget target = getConfiguredTarget(label); return (CommandAction) getGeneratingAction(getBinArtifact("lib" + target.getLabel().getName() + ".a", target)); } protected Iterable inputsEndingWith(Action action, final String suffix) { return Iterables.filter(action.getInputs(), new Predicate() { @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") .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. * *

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 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()) .get(ObjcProvider.SKYLARK_CONSTRUCTOR); 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").getPossibleInputsForTesting()) .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'],", " defines = ['SHOULDNOTBEINPROTOS'],", " copts = ['-ISHOULDNOTBEINPROTOS']", ")"); ruleType.scratchTarget( scratch, "deps", "['//libs:objc_lib']"); BuildConfiguration childConfig = Iterables.getOnlyElement( getSplitConfigurations( targetConfig, new MultiArchSplitTransitionProvider.AppleBinaryTransition( PlatformType.IOS, Optional.absent()))); ConfiguredTarget topTarget = getConfiguredTarget("//x:x", childConfig); assertObjcProtoProviderArtifactsArePropagated(topTarget); assertBundledGenerationActionsAreDifferent(topTarget); assertOnlyRequiredInputsArePresentForBundledGeneration(topTarget); assertOnlyRequiredInputsArePresentForBundledCompilation(topTarget); assertCoptsAndDefinesNotPropagatedToProtos(topTarget); assertBundledGroupsGetCreatedAndLinked(topTarget); } protected ImmutableList getAllObjectFilesLinkedInBin(Artifact bin) { ImmutableList.Builder 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"); } /** * Ensures that all middleman artifacts in the action input are expanded so that the real inputs * are also included. */ protected Iterable getExpandedActionInputs(Action action) { List containedArtifacts = new ArrayList<>(); for (Artifact input : action.getInputs()) { if (input.isMiddlemanArtifact()) { Action middlemanAction = getGeneratingAction(input); Iterables.addAll(containedArtifacts, getExpandedActionInputs(middlemanAction)); } containedArtifacts.add(input); } return containedArtifacts; } 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(getExpandedActionInputs(protoObjectActionA)) .containsNoneOf(protoHeaderB, protoHeaderC, protoHeaderD); assertThat(getExpandedActionInputs(protoObjectActionB)) .containsNoneOf(protoHeaderA, protoHeaderC, protoHeaderD); assertThat(getExpandedActionInputs(protoObjectActionC)) .containsNoneOf(protoHeaderA, protoHeaderB, protoHeaderD); assertThat(getExpandedActionInputs(protoObjectActionD)) .containsAllOf(protoHeaderA, protoHeaderC, protoHeaderD); assertThat(getExpandedActionInputs(protoObjectActionD)) .doesNotContain(protoHeaderB); } private void assertCoptsAndDefinesNotPropagatedToProtos(ConfiguredTarget topTarget) throws Exception { 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"); } 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, "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", "objc_library(", " name = 'lib',", " srcs = ['a.m'],", ")", "apple_binary(", " name = 'bin',", " deps = [':lib'],", " 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 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 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 binBundleMergeInputs, Collection libBundleMergeInputs, ImmutableSetMultimap> 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(); createTargetWithStoryboards(ruleType); ObjcProvider provider = providerForTarget("//x:x"); ImmutableList 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(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, ImmutableList targetDevices) throws Exception { Artifact storyboardZip = getBinArtifact("x/1.storyboard.zip", target); CommandAction compileAction = (CommandAction) getGeneratingAction(storyboardZip); assertThat(Artifact.toExecPaths(compileAction.getInputs())) .containsExactly(MOCK_IBTOOLWRAPPER_PATH, "x/1.storyboard"); String archiveRoot = targetDevices.contains("watch") ? "." : "1.storyboardc"; assertThat(compileAction.getOutputs()).containsExactly(storyboardZip); assertThat(compileAction.getArguments()) .containsExactlyElementsIn( new Builder() .addDynamicString(MOCK_IBTOOLWRAPPER_PATH) .addExecPath(storyboardZip) .addDynamicString(archiveRoot) // archive root .add("--minimum-deployment-target", minimumOsVersion.toString()) .add("--module") .add("x") .addAll(VectorArg.addBefore("--target-device").each(targetDevices)) .add("x/1.storyboard") .build() .arguments()) .inOrder(); storyboardZip = getBinArtifact("x/ja.lproj/loc.storyboard.zip", target); compileAction = (CommandAction) getGeneratingAction(storyboardZip); assertThat(Artifact.toExecPaths(compileAction.getInputs())) .containsExactly(MOCK_IBTOOLWRAPPER_PATH, "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 Builder() .addDynamicString(MOCK_IBTOOLWRAPPER_PATH) .addExecPath(storyboardZip) .addDynamicString(archiveRoot) // archive root .add("--minimum-deployment-target", minimumOsVersion.toString()) .add("--module") .add("x") .addAll(VectorArg.addBefore("--target-device").each(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(Artifact.toExecPaths(toolAction.getInputs())) .containsExactly(binary.getExecPathString(), MOCK_SWIFTSTDLIBTOOLWRAPPER_PATH); assertThat(toolAction.getOutputs()).containsExactly(swiftLibsZip); CustomCommandLine.Builder expectedCommandLine = CustomCommandLine.builder().addDynamicString(MOCK_SWIFTSTDLIBTOOLWRAPPER_PATH); if (toolchain != null) { expectedCommandLine.add("--toolchain", toolchain); } expectedCommandLine .addExecPath("--output_zip_path", swiftLibsZip) .add("--bundle_path", bundlePath) .add("--platform", platformName) .addExecPath("--scan-executable", binary); 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 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 rootedIncludePaths( BuildConfiguration configuration, String... unrootedPaths) { ImmutableList.Builder 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 forceLoadArchives = ImmutableList.of( execPathEndingWith(action.getInputs(), "imp1.a"), execPathEndingWith(action.getInputs(), "lib1.a")); List expectedArgs = new ImmutableList.Builder() .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(); String commandLine = Joiner.on(" ").join(action.getArguments()); for (String expectedArg : expectedArgs) { assertThat(commandLine).contains(expectedArg); } } protected void checkObjcCopts(RuleType ruleType) throws Exception { useConfiguration("--objccopt=-foo"); scratch.file("x/a.m"); ruleType.scratchTarget(scratch, "srcs", "['a.m']"); List 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 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 allExpectedCoptsBuilder = ImmutableList.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 allExpectedCoptsBuilder = ImmutableList.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.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.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); List expectedArgs = ImmutableList.of("-DA=foo", "-DB", "-DC=bar", "-DD", "-DE=baz", "explicit_copt"); List compileActionAArgs = compileAction("//x:x", "a.o").getArguments(); List compileActionBArgs = compileAction("//x:x", "b.o").getArguments(); for (String expectedArg : expectedArgs) { assertThat(compileActionAArgs).contains(expectedArg); assertThat(compileActionBArgs).contains(expectedArg); } } protected void checkDefinesFromCcLibraryDep(RuleType ruleType) throws Exception { useConfiguration(); 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"; // we remove spaces, since the legacy rules put a space after "-I" in include paths. String compileActionACommandLine = Joiner.on(" ").join(compileAction("//x:x", "a.o").getArguments()).replace(" ", ""); assertThat(compileActionACommandLine).contains("-I" + sdkIncludeDir + "/foo"); assertThat(compileActionACommandLine).contains("-I" + sdkIncludeDir + "/bar/baz"); String compileActionBCommandLine = Joiner.on(" ").join(compileAction("//x:x", "b.o").getArguments()).replace(" ", ""); assertThat(compileActionBCommandLine).contains("-I" + sdkIncludeDir + "/foo"); assertThat(compileActionBCommandLine).contains("-I" + sdkIncludeDir + "/bar/baz"); } 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(); createLibraryTargetWriter("//bin:main_lib") .setAndCreateFiles("srcs", "b.m") .setList("deps", "//lib:lib") .setList("sdk_includes", "from_bin") .write(); String sdkIncludeDir = AppleToolchain.sdkDir() + "/usr/include"; // We remove spaces because the crosstool case does not use spaces for include paths. String compileAArgs = Joiner.on("") .join(compileAction("//lib:lib", "a.o").getArguments()) .replace(" ", ""); assertThat(compileAArgs).contains("-I" + sdkIncludeDir + "/from_lib"); assertThat(compileAArgs).contains("-I" + sdkIncludeDir + "/foo"); assertThat(compileAArgs).contains("-I" + sdkIncludeDir + "/bar/baz"); String compileBArgs = Joiner.on("") .join(compileAction("//bin:main_lib", "b.o").getArguments()) .replace(" ", ""); assertThat(compileBArgs).contains("-I" + sdkIncludeDir + "/from_bin"); assertThat(compileBArgs).contains("-I" + sdkIncludeDir + "/from_lib"); assertThat(compileBArgs).contains("-I" + sdkIncludeDir + "/foo"); assertThat(compileBArgs).contains("-I" + sdkIncludeDir + "/bar/baz"); } 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 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 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 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 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", toolsRepoExecPath("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(); 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.absent(), getVariableSubstitutionArgumentsDefaultFormat(ruleType)); } protected void checkFallbackBundleIdInMergedPlist(BinaryRuleTypePair ruleTypePair) throws Exception { ruleTypePair.scratchTargets(scratch, INFOPLIST_ATTR, "'Info.plist'"); scratch.file("bin/Info.plist"); checkBundleIdFlagsInPlistMergeAction( Optional.absent(), getVariableSubstitutionArguments(ruleTypePair)); } protected void checkBundleIdFlagsInPlistMergeAction( Optional specifiedBundleId, Map variableSubstitutions) throws Exception { checkBundleIdFlagsInPlistMergeAction(specifiedBundleId, variableSubstitutions, "example.x"); } protected void checkBundleIdFlagsInPlistMergeAction( Optional specifiedBundleId, Map 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.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 profiles = mobileProvisionProfiles(control); ImmutableMap 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 profiles = mobileProvisionProfiles(control); Map 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 artifactAndBundlePaths) throws Exception { BundleMergeProtos.Control control = bundleMergeControl(bundlingTarget); Action mergeBundleAction = bundleMergeAction(bundlingTarget); List expectedBundleFiles = new ArrayList<>(); for (Map.Entry 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 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 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() .add(MOCK_MOMCWRAPPER_PATH) .add(fooMomZip.getExecPathString()) .add("foo.mom") .addAll(commonMomcZipArguments) .add("lib/foo.xcdatamodel") .build()); assertThat(barMomczipAction.getArguments()) .isEqualTo( new ImmutableList.Builder() .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 bundlePaths = ImmutableList.builder(); for (BundleMergeProtos.BundleFile file : topControl.getBundleFileList()) { bundlePaths.add(file.getBundlePath()); } assertThat(bundlePaths.build()).containsNoDuplicates(); ImmutableList.Builder 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 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 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 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 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 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 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 attrs = ImmutableList.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("--cpu=ios_i386"); 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("--ios_multi_cpus=armv7,arm64"); 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 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())) .contains( configurationGenfiles("x86_64", ConfigurationDistinguisher.UNKNOWN, null) + "/x/gen.m"); } 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("--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 = '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("--cpu=ios_i386"); 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 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("--cpu=ios_i386"); 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 archiveFilenames = ImmutableList.of( i386Prefix + "lib/libcclib.a", i386Prefix + "x/libbin.a", i386Prefix + "lib/libjavalib_j2objc.a", i386Prefix + toolsRepoExecPath("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("--ios_multi_cpus=armv7,arm64"); 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 expectedSymbolStripArgs = ImmutableList.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(Artifact.toExecPaths(storyboardCompile.getInputs())) .containsExactly(MOCK_IBTOOLWRAPPER_PATH, "x/launch.storyboard"); assertThat(storyboardCompile.getArguments()) .isEqualTo( new CustomCommandLine.Builder() .addDynamicString(MOCK_IBTOOLWRAPPER_PATH) .addExecPath(storyboardZip) .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 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 getVariableSubstitutionArguments(BinaryRuleTypePair ruleTypePair) { return constructVariableSubstitutions( ruleTypePair.getBundleName(), ruleTypePair.getBundleDir()); } private Map constructVariableSubstitutions(String bundleName, String bundleDir) { return new ImmutableMap.Builder() .put("EXECUTABLE_NAME", bundleName) .put("BUNDLE_NAME", bundleDir.split("/")[1]) .put("PRODUCT_NAME", bundleName) .build(); } protected void assertPlistMergeControlUsesSourceFiles( PlMergeProtos.Control control, Iterable sourceFilePaths) throws Exception { Iterable 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. * *

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 normalizeBashArgs(List 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, "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"); 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 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); 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, "deps", "['//package:cclib']"); scratch.file("package/BUILD", "cc_library(name = 'cclib', srcs = ['dep.c'])"); useConfiguration("--ios_multi_cpus=i386,x86_64"); 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", "package/libcclib.a"); verifyObjlist( x8664BinAction, "x/x-linker.objlist", "package/libcclib.a"); assertThat(Artifact.toExecPaths(i386BinAction.getInputs())) .containsAllOf( i386Prefix + "package/libcclib.a", i386Prefix + "x/x-linker.objlist"); assertThat(Artifact.toExecPaths(x8664BinAction.getInputs())) .containsAllOf( x8664Prefix + "package/libcclib.a", x8664Prefix + "x/x-linker.objlist"); } // Regression test for b/32310268. protected void checkAliasedLinkoptsThroughObjcLibrary(RuleType ruleType) throws Exception { useConfiguration("--cpu=ios_i386"); 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, "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("--cpu=ios_i386"); 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, "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 checkObjcProviderLinkInputsInLinkAction(RuleType ruleType) throws Exception { useConfiguration("--cpu=ios_i386"); scratch.file("bin/defs.bzl", "def _custom_rule_impl(ctx):", " return struct(objc=apple_common.new_objc_provider(", " link_inputs=depset(ctx.files.link_inputs)))", "custom_rule = rule(", " _custom_rule_impl,", " attrs={'link_inputs': attr.label_list(allow_files=True)},", ")"); scratch.file("bin/input.txt"); scratch.file("bin/BUILD", "load('//bin:defs.bzl', 'custom_rule')", "custom_rule(", " name = 'custom',", " link_inputs = ['input.txt'],", ")"); ruleType.scratchTarget(scratch, "deps", "['//bin:custom']"); Artifact inputFile = getSourceArtifact("bin/input.txt"); assertThat(linkAction("//x").getInputs()).contains(inputFile); } 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( "--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"); } public void checkLinkingRuleCanUseCrosstool(RuleType ruleType) throws Exception { 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 { ruleType.scratchTarget(scratch); // 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("--ios_multi_cpus=i386,x86_64"); ruleType.scratchTarget(scratch); // 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',", " 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(), "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("--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',", " platform_type = 'ios',", " deps = [':Dylib1Lib'], dylibs = ['//package:dylib2'])", "framework_stub_rule(name = 'dylib2', binary = ':dylib2Binary')", "apple_binary(name = 'dylib2Binary', binary_type = 'dylib',", " platform_type = 'ios',", " deps = [':Dylib2Lib'])", "apple_binary(name = 'alternate',", " 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',", " 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); ConfiguredTarget target = getConfiguredTarget("//x:x"); assertThat( ActionsTestUtil.baseNamesOf( getOutputGroup(target, OutputGroupProvider.FILES_TO_COMPILE))) .isEqualTo("a.o"); } protected void checkCustomModuleMap(RuleType ruleType) throws Exception { useConfiguration("--experimental_objc_enable_module_maps"); ruleType.scratchTarget(scratch, "deps", "['//z:testModuleMap']"); scratch.file("z/b.m"); scratch.file("z/b.h"); scratch.file("y/module.modulemap", "module my_module_b { export *\n header b.h }"); scratch.file("z/BUILD", "objc_library(", "name = 'testModuleMap',", "hdrs = ['b.h'],", "srcs = ['b.m'],", "module_map = '//y:mm'", ")"); scratch.file("y/BUILD", "filegroup(", "name = 'mm',", "srcs = ['module.modulemap']", ")"); CommandAction compileActionA = compileAction("//z:testModuleMap", "b.o"); assertThat(compileActionA.getArguments()).doesNotContain("-fmodule-maps"); assertThat(compileActionA.getArguments()).doesNotContain("-fmodule-name"); ObjcProvider provider = providerForTarget("//z:testModuleMap"); assertThat(Artifact.toExecPaths(provider.get(MODULE_MAP))) .containsExactly("y/module.modulemap"); provider = providerForTarget("//x:x"); assertThat(Artifact.toExecPaths(provider.get(MODULE_MAP))).contains("y/module.modulemap"); } /** * Verifies that the given rule supports different minimum_os attribute values for two targets * in the same build, and adds compile args to set the minimum os appropriately for * dependencies of each. * * @param ruleType the rule to test * @param multiArchArtifactSuffix the suffix of the artifact that the rule-under-test produces * @param singleArchArtifactSuffix the suffix of the single-architecture artifact that is an * input to the rule-under-test's generating action */ protected void checkMinimumOsDifferentTargets(RuleType ruleType, String multiArchArtifactSuffix, String singleArchArtifactSuffix) throws Exception { ruleType.scratchTarget("nine", "nine", scratch, "deps", "['//package:objcLib']", "minimum_os_version", "'9.0'"); ruleType.scratchTarget("eight", "eight", scratch, "deps", "['//package:objcLib']", "minimum_os_version", "'8.0'"); scratch.file("package/BUILD", "genrule(name = 'root', srcs = ['//nine:nine', '//eight:eight'], outs = ['genout'],", " cmd = 'touch genout')", "objc_library(name = 'objcLib', srcs = [ 'b.m' ])"); ConfiguredTarget rootTarget = getConfiguredTarget("//package:root"); Artifact rootArtifact = getGenfilesArtifact("genout", rootTarget); Action genruleAction = getGeneratingAction(rootArtifact); Action eightLipoAction = getGeneratingAction( getFirstArtifactEndingWith(genruleAction.getInputs(), "eight" + multiArchArtifactSuffix)); Action nineLipoAction = getGeneratingAction( getFirstArtifactEndingWith(genruleAction.getInputs(), "nine" + multiArchArtifactSuffix)); Artifact eightBin = getFirstArtifactEndingWith(eightLipoAction.getInputs(), singleArchArtifactSuffix); Artifact nineBin = getFirstArtifactEndingWith(nineLipoAction.getInputs(), singleArchArtifactSuffix); CommandAction eightLinkAction = (CommandAction) getGeneratingAction(eightBin); CommandAction nineLinkAction = (CommandAction) getGeneratingAction(nineBin); CommandAction eightObjcLibArchiveAction = (CommandAction) getGeneratingAction( getFirstArtifactEndingWith(eightLinkAction.getInputs(), "libobjcLib.a")); CommandAction eightObjcLibCompileAction = (CommandAction) getGeneratingAction( getFirstArtifactEndingWith(eightObjcLibArchiveAction.getInputs(), "b.o")); CommandAction nineObjcLibArchiveAction = (CommandAction) getGeneratingAction( getFirstArtifactEndingWith(nineLinkAction.getInputs(), "libobjcLib.a")); CommandAction nineObjcLibCompileAction = (CommandAction) getGeneratingAction( getFirstArtifactEndingWith(nineObjcLibArchiveAction.getInputs(), "b.o")); assertThat(Joiner.on(" ").join(eightObjcLibCompileAction.getArguments())) .contains("-mios-simulator-version-min=8.0"); assertThat(Joiner.on(" ").join(nineObjcLibCompileAction.getArguments())) .contains("-mios-simulator-version-min=9.0"); } protected void verifyDrops32BitArchitecture(RuleType ruleType) throws Exception { scratch.file("libs/BUILD", "objc_library(", " name = 'objc_lib',", " srcs = ['a.m'],", ")"); ruleType.scratchTarget( scratch, "deps", "['//libs:objc_lib']", "platform_type", "'ios'", "minimum_os_version", "'11.0'"); // Does not support 32-bit architectures. useConfiguration("--ios_multi_cpus=armv7,arm64,i386,x86_64"); Action lipoAction = actionProducingArtifact("//x:x", "_lipobin"); getSingleArchBinary(lipoAction, "arm64"); getSingleArchBinary(lipoAction, "x86_64"); assertThat(getSingleArchBinaryIfAvailable(lipoAction, "armv7")).isNull(); assertThat(getSingleArchBinaryIfAvailable(lipoAction, "i386")).isNull(); } /** Returns the full label string for labels within the main tools repository. */ protected static String toolsRepoLabel(String label) { return TestConstants.TOOLS_REPOSITORY + label; } /** * Returns the full exec path string for exec paths of targets within the main tools repository. */ protected static String toolsRepoExecPath(String execPath) { return TestConstants.TOOLS_REPOSITORY_PATH_PREFIX + execPath; } @Nullable protected Artifact getSingleArchBinaryIfAvailable(Action lipoAction, String arch) throws Exception { for (Artifact archBinary : lipoAction.getInputs()) { String execPath = archBinary.getExecPathString(); if (execPath.endsWith("_bin") && execPath.contains(arch)) { return archBinary; } } return null; } protected Artifact getSingleArchBinary(Action lipoAction, String arch) throws Exception { Artifact result = getSingleArchBinaryIfAvailable(lipoAction, arch); if (result != null) { return result; } else { throw new AssertionError("Lipo action does not contain an input binary from arch " + arch); } } protected void scratchFeatureFlagTestLib() throws Exception { scratch.file( "lib/BUILD", "config_feature_flag(", " name = 'flag1',", " allowed_values = ['on', 'off'],", " default_value = 'off',", ")", "config_setting(", " name = 'flag1@on',", " flag_values = {':flag1': 'on'},", ")", "config_feature_flag(", " name = 'flag2',", " allowed_values = ['on', 'off'],", " default_value = 'off',", ")", "config_setting(", " name = 'flag2@on',", " flag_values = {':flag2': 'on'},", ")", "objc_library(", " name = 'objcLib',", " srcs = select({", " ':flag1@on': ['flag1on.m'],", " '//conditions:default': ['flag1off.m'],", " }) + select({", " ':flag2@on': ['flag2on.m'],", " '//conditions:default': ['flag2off.m'],", " }),", " copts = select({", " ':flag1@on': ['-FLAG_1_ON'],", " '//conditions:default': ['-FLAG_1_OFF'],", " }) + select({", " ':flag2@on': ['-FLAG_2_ON'],", " '//conditions:default': ['-FLAG_2_OFF'],", " }),", ")"); } }