blob: 1d717689818101c0a2ca2cd0aa7b0981d05e267d [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.rules.objc;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.STORYBOARD;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.XCDATAMODEL;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.Runfiles;
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.analysis.RunfilesSupport;
import com.google.devtools.build.lib.analysis.actions.ExecutionRequirements;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
import com.google.devtools.build.lib.rules.apple.Platform.PlatformType;
import com.google.devtools.build.lib.rules.objc.CompilationSupport.ExtraLinkArgs;
import com.google.devtools.build.lib.rules.objc.ObjcCommon.ResourceAttributes;
import com.google.devtools.build.lib.rules.objc.ReleaseBundlingSupport.LinkedBinary;
import com.google.devtools.build.lib.rules.test.ExecutionInfoProvider;
import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
import com.google.devtools.build.lib.syntax.Type;
/**
* Implementation for {@code ios_test} rule in Bazel.
*/
public final class IosTest implements RuleConfiguredTargetFactory {
private static final ImmutableList<SdkFramework> AUTOMATIC_SDK_FRAMEWORKS_FOR_XCTEST =
ImmutableList.of(new SdkFramework("XCTest"));
// Attributes for IosTest rules.
// Documentation on usage is in {@link IosTestRule@}.
static final String OBJC_GCOV_ATTR = "$objc_gcov";
static final String DEVICE_ARG_ATTR = "ios_device_arg";
static final String IS_XCTEST_ATTR = "xctest";
static final String MEMLEAKS_DEP_ATTR = "$memleaks_dep";
static final String MEMLEAKS_PLUGIN_ATTR = "$memleaks_plugin";
static final String PLUGINS_ATTR = "plugins";
static final String TARGET_DEVICE = "target_device";
static final String TEST_RUNNER_ATTR = "$test_runner";
static final String TEST_TARGET_DEVICE_ATTR = "ios_test_target_device";
static final String TEST_TEMPLATE_ATTR = "$test_template";
static final String XCTEST_APP_ATTR = "xctest_app";
static final String MCOV_TOOL_ATTR = ":mcov";
@VisibleForTesting
public static final String REQUIRES_SOURCE_ERROR =
"ios_test requires at least one source file in srcs or non_arc_srcs";
@VisibleForTesting
public static final String NO_MULTI_CPUS_ERROR =
"ios_test cannot be built for multiple CPUs at the same time";
/**
* {@inheritDoc}
*
* Creates a target, including registering actions, just as {@link #create(RuleContext)} does.
* The difference between {@link #create(RuleContext)} and this method is that this method does
* only what is needed to support tests on the environment besides generate the Xcodeproj file
* and build the app and test {@code .ipa}s. The {@link #create(RuleContext)} method delegates
* to this method.
*/
@Override
public final ConfiguredTarget create(RuleContext ruleContext)
throws InterruptedException, RuleErrorException {
ProtobufSupport protoSupport =
new ProtobufSupport(ruleContext).registerGenerationActions().registerCompilationActions();
Optional<ObjcProvider> protosObjcProvider = protoSupport.getObjcProvider();
Optional<XcodeProvider> protosXcodeProvider = protoSupport.getXcodeProvider();
ObjcCommon common = common(ruleContext, protosObjcProvider);
if (!common.getCompilationArtifacts().get().getArchive().isPresent()) {
ruleContext.ruleError(REQUIRES_SOURCE_ERROR);
}
if (!ruleContext.getFragment(AppleConfiguration.class).getIosMultiCpus().isEmpty()) {
ruleContext.ruleError(NO_MULTI_CPUS_ERROR);
}
XcodeProvider.Builder xcodeProviderBuilder =
new XcodeProvider.Builder().addPropagatedDependencies(protosXcodeProvider.asSet());
NestedSetBuilder<Artifact> filesToBuild = NestedSetBuilder.stableOrder();
addResourceFilesToBuild(ruleContext, common.getObjcProvider(), filesToBuild);
XcodeProductType productType = getProductType(ruleContext);
ExtraLinkArgs extraLinkArgs;
Iterable<Artifact> extraLinkInputs;
String bundleFormat;
if (!isXcTest(ruleContext)) {
extraLinkArgs = new ExtraLinkArgs();
extraLinkInputs = ImmutableList.of();
bundleFormat = ReleaseBundlingSupport.APP_BUNDLE_DIR_FORMAT;
} else {
XcodeProvider appIpaXcodeProvider =
ruleContext.getPrerequisite(XCTEST_APP_ATTR, Mode.TARGET, XcodeProvider.class);
xcodeProviderBuilder
.setTestHost(appIpaXcodeProvider)
.setProductType(productType);
XcTestAppProvider testApp = xcTestAppProvider(ruleContext);
Artifact bundleLoader = testApp.getBundleLoader();
// -bundle causes this binary to be linked as a bundle and not require an entry point
// (i.e. main())
// -bundle_loader causes the code in this test to have access to the symbols in the test rig,
// or more specifically, the flag causes ld to consider the given binary when checking for
// missing symbols.
// -rpath @loader_path/Frameworks allows test bundles to load dylibs from the app's
// Frameworks directory.
extraLinkArgs = new ExtraLinkArgs(
"-bundle",
"-bundle_loader", bundleLoader.getExecPathString(),
"-Xlinker", "-rpath", "-Xlinker", "@loader_path/Frameworks");
extraLinkInputs = ImmutableList.of(bundleLoader);
bundleFormat = ReleaseBundlingSupport.XCTEST_BUNDLE_DIR_FORMAT;
filesToBuild.add(testApp.getIpa());
}
J2ObjcMappingFileProvider j2ObjcMappingFileProvider = J2ObjcMappingFileProvider.union(
ruleContext.getPrerequisites("deps", Mode.TARGET, J2ObjcMappingFileProvider.class));
J2ObjcEntryClassProvider j2ObjcEntryClassProvider = new J2ObjcEntryClassProvider.Builder()
.addTransitive(
ruleContext.getPrerequisites("deps", Mode.TARGET, J2ObjcEntryClassProvider.class))
.build();
new CompilationSupport(ruleContext)
.registerLinkActions(
common.getObjcProvider(),
j2ObjcMappingFileProvider,
j2ObjcEntryClassProvider,
extraLinkArgs,
extraLinkInputs,
DsymOutputType.TEST)
.registerCompileAndArchiveActions(common)
.registerFullyLinkAction(common.getObjcProvider())
.addXcodeSettings(xcodeProviderBuilder, common)
.validateAttributes();
ObjcConfiguration objcConfiguration = ObjcRuleClasses.objcConfiguration(ruleContext);
AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class);
new ReleaseBundlingSupport(
ruleContext,
common.getObjcProvider(),
LinkedBinary.LOCAL_AND_DEPENDENCIES,
bundleFormat,
objcConfiguration.getMinimumOs(),
appleConfiguration.getMultiArchPlatform(PlatformType.IOS))
.registerActions(DsymOutputType.TEST)
.addXcodeSettings(xcodeProviderBuilder)
.addFilesToBuild(filesToBuild, Optional.of(DsymOutputType.TEST))
.validateResources()
.validateAttributes();
new ResourceSupport(ruleContext).validateAttributes().addXcodeSettings(xcodeProviderBuilder);
new XcodeSupport(ruleContext)
.addXcodeSettings(xcodeProviderBuilder, common.getObjcProvider(), productType)
.addDependencies(xcodeProviderBuilder, new Attribute("bundles", Mode.TARGET))
.addDependencies(xcodeProviderBuilder, new Attribute("deps", Mode.TARGET))
.addNonPropagatedDependencies(
xcodeProviderBuilder, new Attribute("non_propagated_deps", Mode.TARGET))
.addFilesToBuild(filesToBuild)
.registerActions(xcodeProviderBuilder.build());
XcodeProvider xcodeProvider = xcodeProviderBuilder.build();
NestedSet<Artifact> filesToBuildSet = filesToBuild.build();
Runfiles.Builder runfilesBuilder = new Runfiles.Builder(
ruleContext.getWorkspaceName(),
ruleContext.getConfiguration().legacyExternalRunfiles())
.addRunfiles(ruleContext, RunfilesProvider.DEFAULT_RUNFILES);
NestedSetBuilder<Artifact> filesToBuildBuilder =
NestedSetBuilder.<Artifact>stableOrder().addTransitive(filesToBuildSet);
InstrumentedFilesProvider instrumentedFilesProvider =
new CompilationSupport(ruleContext).getInstrumentedFilesProvider(common);
TestSupport testSupport =
new TestSupport(ruleContext)
.registerTestRunnerActions()
.addRunfiles(runfilesBuilder, instrumentedFilesProvider)
.addFilesToBuild(filesToBuildBuilder);
Artifact executable = testSupport.generatedTestScript();
Runfiles runfiles = runfilesBuilder.build();
RunfilesSupport runfilesSupport =
RunfilesSupport.withExecutable(ruleContext, runfiles, executable);
return new RuleConfiguredTargetBuilder(ruleContext)
.setFilesToBuild(filesToBuildBuilder.build())
.addProvider(xcodeProvider)
.addProvider(RunfilesProvider.simple(runfiles))
.addProvider(
new ExecutionInfoProvider(ImmutableMap.of(ExecutionRequirements.REQUIRES_DARWIN, "")))
.addProvider(InstrumentedFilesProvider.class, instrumentedFilesProvider)
.addProviders(testSupport.getExtraProviders())
.setRunfilesSupport(runfilesSupport, executable)
.build();
}
private XcodeProductType getProductType(RuleContext ruleContext) {
if (isXcTest(ruleContext)) {
return XcodeProductType.UNIT_TEST;
} else {
return XcodeProductType.APPLICATION;
}
}
private void addResourceFilesToBuild(
RuleContext ruleContext, ObjcProvider objcProvider, NestedSetBuilder<Artifact> filesToBuild) {
IntermediateArtifacts intermediateArtifacts =
ObjcRuleClasses.intermediateArtifacts(ruleContext);
Iterable<Xcdatamodel> xcdatamodels =
Xcdatamodels.xcdatamodels(intermediateArtifacts, objcProvider.get(XCDATAMODEL));
filesToBuild.addAll(Xcdatamodel.outputZips(xcdatamodels));
for (Artifact storyboard : objcProvider.get(STORYBOARD)) {
filesToBuild.add(intermediateArtifacts.compiledStoryboardZip(storyboard));
}
}
/** Constructs an {@link ObjcCommon} instance based on the attributes. */
private ObjcCommon common(RuleContext ruleContext, Optional<ObjcProvider> protosObjcProvider) {
CompilationArtifacts compilationArtifacts =
CompilationSupport.compilationArtifacts(ruleContext);
ObjcCommon.Builder builder =
new ObjcCommon.Builder(ruleContext)
.setCompilationAttributes(
CompilationAttributes.Builder.fromRuleContext(ruleContext).build())
.setCompilationArtifacts(compilationArtifacts)
.setResourceAttributes(new ResourceAttributes(ruleContext))
.addDefines(ruleContext.getTokenizedStringListAttr("defines"))
.addDeps(ruleContext.getPrerequisites("deps", Mode.TARGET))
.addRuntimeDeps(ruleContext.getPrerequisites("runtime_deps", Mode.TARGET))
.addDeps(ruleContext.getPrerequisites("bundles", Mode.TARGET))
.addDepObjcProviders(protosObjcProvider.asSet())
.addNonPropagatedDepObjcProviders(
ruleContext.getPrerequisites(
"non_propagated_deps", Mode.TARGET, ObjcProvider.class))
.setIntermediateArtifacts(ObjcRuleClasses.intermediateArtifacts(ruleContext))
.setHasModuleMap();
if (isXcTest(ruleContext)) {
builder
.addExtraSdkFrameworks(AUTOMATIC_SDK_FRAMEWORKS_FOR_XCTEST)
.addDepObjcProviders(ImmutableList.of(xcTestAppProvider(ruleContext).getObjcProvider()));
}
// Add the memleaks library if the --ios_memleaks flag is true. The library pauses the test
// after all tests have been executed so that leaks can be run.
ObjcConfiguration config = ruleContext.getFragment(ObjcConfiguration.class);
if (config.runMemleaks()) {
builder.addDepObjcProviders(
ruleContext.getPrerequisites(MEMLEAKS_DEP_ATTR, Mode.TARGET, ObjcProvider.class));
}
return builder.build();
}
protected static boolean isXcTest(RuleContext ruleContext) {
return ruleContext.attributes().get(IS_XCTEST_ATTR, Type.BOOLEAN);
}
/** Returns the {@link XcTestAppProvider} of the {@code xctest_app} attribute. */
protected static XcTestAppProvider xcTestAppProvider(RuleContext ruleContext) {
return ruleContext.getPrerequisite(XCTEST_APP_ATTR, Mode.TARGET, XcTestAppProvider.class);
}
}