// 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'],",
" }),",
")");
}
}