blob: 8a42747fb2a07b4a8924b6a6e13129972e21d1d9 [file] [log] [blame]
// Copyright 2016 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.packages.Type.STRING;
import static com.google.devtools.build.lib.rules.objc.AppleBinaryRule.BUNDLE_LOADER_ATTR_NAME;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.MULTI_ARCH_LINKED_BINARIES;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.DylibDependingRule.DYLIBS_ATTR_NAME;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.test.InstrumentedFilesCollector;
import com.google.devtools.build.lib.analysis.test.InstrumentedFilesInfo;
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.NativeInfo;
import com.google.devtools.build.lib.rules.apple.AppleCommandLineOptions.AppleBitcodeMode;
import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
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.cpp.CcToolchainProvider;
import com.google.devtools.build.lib.rules.objc.AppleDebugOutputsInfo.OutputType;
import com.google.devtools.build.lib.rules.objc.CompilationSupport.ExtraLinkArgs;
import com.google.devtools.build.lib.rules.objc.MultiArchBinarySupport.DependencySpecificConfiguration;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
import java.util.Map;
import java.util.TreeMap;
/** Implementation for the "apple_binary" rule. */
public class AppleBinary implements RuleConfiguredTargetFactory {
/** Type of linked binary that apple_binary may create. */
enum BinaryType {
/**
* Binaries that can be loaded by other binaries at runtime, and which can't be directly
* executed by the operating system. When linking, a bundle_loader binary may be passed which
* signals the linker on where to look for unimplemented symbols, basically declaring that the
* bundle should be loaded by that binary. Bundle binaries are usually found in Plugins, and one
* common use case is tests. Tests are bundled into an .xctest bundle which contains the test
* binary along with required resources. The test bundle is then loaded and run during test
* execution.
*/
LOADABLE_BUNDLE,
/**
* Binaries that can be run directly by the operating system. They implement the main method
* that is the entry point to the program. In Apple apps, they are usually distributed in .app
* bundles, which are directories that contain the executable along with required resources to
* run.
*/
EXECUTABLE,
/**
* Binaries meant to be loaded at load time (when the operating system is loading the binary
* into memory), which cannot be unloaded. They are usually distributed in frameworks, which are
* .framework bundles that contain the dylib as well as well as required resources to run.
*/
DYLIB;
@Override
public String toString() {
return name().toLowerCase();
}
/**
* Returns the {@link BinaryType} with given name (case insensitive).
*
* @throws IllegalArgumentException if the name does not match a valid platform type.
*/
public static BinaryType fromString(String name) {
for (BinaryType binaryType : BinaryType.values()) {
if (name.equalsIgnoreCase(binaryType.toString())) {
return binaryType;
}
}
throw new IllegalArgumentException(String.format("Unsupported binary type \"%s\"", name));
}
/** Returns the enum values as a list of strings for validation. */
static Iterable<String> getValues() {
return Iterables.transform(ImmutableList.copyOf(values()), Functions.toStringFunction());
}
}
@VisibleForTesting
static final String BUNDLE_LOADER_NOT_IN_BUNDLE_ERROR =
"Can only use bundle_loader when binary_type is bundle.";
@Override
public final ConfiguredTarget create(RuleContext ruleContext)
throws InterruptedException, RuleErrorException, ActionConflictException {
AppleBinaryOutput appleBinaryOutput = linkMultiArchBinary(ruleContext);
return ruleConfiguredTargetFromProvider(ruleContext, appleBinaryOutput);
}
/**
* Links a (potentially multi-architecture) binary targeting Apple platforms.
*
* <p>This method comprises a bulk of the logic of the {@code apple_binary} rule, and is
* statically available so that it may be referenced by Skylark APIs that replicate its
* functionality.
*
* @param ruleContext the current rule context
* @return a tuple containing all necessary information about the linked binary
*/
public static AppleBinaryOutput linkMultiArchBinary(RuleContext ruleContext)
throws InterruptedException, RuleErrorException, ActionConflictException {
return linkMultiArchBinary(ruleContext, ImmutableList.of(), ImmutableList.of());
}
/**
* Links a (potentially multi-architecture) binary targeting Apple platforms.
*
* <p>This method comprises a bulk of the logic of the {@code apple_binary} rule, and is
* statically available so that it may be referenced by Skylark APIs that replicate its
* functionality.
*
* @param ruleContext the current rule context
* @param extraLinkopts extra linkopts to pass to the linker actions
* @param extraLinkInputs extra input files to pass to the linker action
* @return a tuple containing all necessary information about the linked binary
*/
public static AppleBinaryOutput linkMultiArchBinary(
RuleContext ruleContext, Iterable<String> extraLinkopts, Iterable<Artifact> extraLinkInputs)
throws InterruptedException, RuleErrorException, ActionConflictException {
MultiArchSplitTransitionProvider.validateMinimumOs(ruleContext);
PlatformType platformType = MultiArchSplitTransitionProvider.getPlatformType(ruleContext);
AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class);
ApplePlatform platform = appleConfiguration.getMultiArchPlatform(platformType);
ImmutableListMultimap<String, TransitiveInfoCollection> cpuToDepsCollectionMap =
MultiArchBinarySupport.transformMap(
ruleContext.getPrerequisitesByConfiguration("deps", Mode.SPLIT));
ImmutableListMultimap<String, ConfiguredTargetAndData> cpuToCTATDepsCollectionMap =
MultiArchBinarySupport.transformMap(
ruleContext.getPrerequisiteCofiguredTargetAndTargetsByConfiguration(
"deps", Mode.SPLIT));
ImmutableMap<BuildConfiguration, CcToolchainProvider> childConfigurationsAndToolchains =
MultiArchBinarySupport.getChildConfigurationsAndToolchains(ruleContext);
Artifact outputArtifact =
ObjcRuleClasses.intermediateArtifacts(ruleContext).combinedArchitectureBinary();
MultiArchBinarySupport multiArchBinarySupport = new MultiArchBinarySupport(ruleContext);
ImmutableSet<DependencySpecificConfiguration> dependencySpecificConfigurations =
multiArchBinarySupport.getDependencySpecificConfigurations(
childConfigurationsAndToolchains,
cpuToDepsCollectionMap,
cpuToCTATDepsCollectionMap,
getDylibProviderTargets(ruleContext));
Map<String, NestedSet<Artifact>> outputGroupCollector = new TreeMap<>();
Iterable<Artifact> allLinkInputs =
Iterables.concat(getRequiredLinkInputs(ruleContext), extraLinkInputs);
ExtraLinkArgs allLinkopts =
new ExtraLinkArgs(Iterables.concat(getRequiredLinkopts(ruleContext), extraLinkopts));
NestedSet<Artifact> binariesToLipo =
multiArchBinarySupport.registerActions(
allLinkopts,
dependencySpecificConfigurations,
allLinkInputs,
cpuToDepsCollectionMap,
outputGroupCollector);
new LipoSupport(ruleContext)
.registerCombineArchitecturesAction(
binariesToLipo,
outputArtifact,
platform);
ObjcProvider.Builder objcProviderBuilder =
new ObjcProvider.NativeBuilder(ruleContext.getAnalysisEnvironment().getSkylarkSemantics());
for (DependencySpecificConfiguration dependencySpecificConfiguration :
dependencySpecificConfigurations) {
objcProviderBuilder.addTransitiveAndPropagate(
dependencySpecificConfiguration.objcProviderWithDylibSymbols());
}
objcProviderBuilder.add(MULTI_ARCH_LINKED_BINARIES, outputArtifact);
ObjcProvider objcProvider = objcProviderBuilder.build();
NativeInfo binaryInfoProvider;
switch (getBinaryType(ruleContext)) {
case EXECUTABLE:
binaryInfoProvider =
new AppleExecutableBinaryInfo(outputArtifact, objcProvider);
break;
case DYLIB:
binaryInfoProvider =
new AppleDylibBinaryInfo(outputArtifact, objcProvider);
break;
case LOADABLE_BUNDLE:
binaryInfoProvider =
new AppleLoadableBundleBinaryInfo(outputArtifact, objcProvider);
break;
default:
ruleContext.ruleError("Unhandled binary type " + getBinaryType(ruleContext));
throw new RuleErrorException();
}
AppleDebugOutputsInfo.Builder builder = AppleDebugOutputsInfo.Builder.create();
for (DependencySpecificConfiguration dependencySpecificConfiguration :
dependencySpecificConfigurations) {
AppleConfiguration childAppleConfig =
dependencySpecificConfiguration.config().getFragment(AppleConfiguration.class);
ObjcConfiguration childObjcConfig =
dependencySpecificConfiguration.config().getFragment(ObjcConfiguration.class);
IntermediateArtifacts intermediateArtifacts =
new IntermediateArtifacts(
ruleContext, /*archiveFileNameSuffix*/
"", /*outputPrefix*/
"",
dependencySpecificConfiguration.config());
String arch = childAppleConfig.getSingleArchitecture();
if (childAppleConfig.getBitcodeMode() == AppleBitcodeMode.EMBEDDED) {
Artifact bitcodeSymbol = intermediateArtifacts.bitcodeSymbolMap();
builder.addOutput(arch, OutputType.BITCODE_SYMBOLS, bitcodeSymbol);
}
if (childObjcConfig.generateDsym()) {
Artifact dsymBinary =
childObjcConfig.shouldStripBinary()
? intermediateArtifacts.dsymSymbolForUnstrippedBinary()
: intermediateArtifacts.dsymSymbolForStrippedBinary();
builder.addOutput(arch, OutputType.DSYM_BINARY, dsymBinary);
}
if (childObjcConfig.generateLinkmap()) {
Artifact linkmap = intermediateArtifacts.linkmap();
builder.addOutput(arch, OutputType.LINKMAP, linkmap);
}
}
return new AppleBinaryOutput(binaryInfoProvider, builder.build(), outputGroupCollector);
}
private static ExtraLinkArgs getRequiredLinkopts(RuleContext ruleContext)
throws RuleErrorException {
BinaryType binaryType = getBinaryType(ruleContext);
ImmutableList.Builder<String> extraLinkArgs = new ImmutableList.Builder<>();
boolean didProvideBundleLoader =
ruleContext.attributes().isAttributeValueExplicitlySpecified(BUNDLE_LOADER_ATTR_NAME);
if (didProvideBundleLoader && binaryType != BinaryType.LOADABLE_BUNDLE) {
ruleContext.throwWithRuleError(BUNDLE_LOADER_NOT_IN_BUNDLE_ERROR);
}
switch (binaryType) {
case LOADABLE_BUNDLE:
extraLinkArgs.add("-bundle");
extraLinkArgs.add("-Xlinker", "-rpath", "-Xlinker", "@loader_path/Frameworks");
if (didProvideBundleLoader) {
AppleExecutableBinaryInfo executableProvider =
ruleContext.getPrerequisite(
BUNDLE_LOADER_ATTR_NAME, Mode.TARGET,
AppleExecutableBinaryInfo.SKYLARK_CONSTRUCTOR);
extraLinkArgs.add(
"-bundle_loader", executableProvider.getAppleExecutableBinary().getExecPathString());
}
break;
case DYLIB:
extraLinkArgs.add("-dynamiclib");
break;
case EXECUTABLE:
break;
}
return new ExtraLinkArgs(extraLinkArgs.build());
}
private static ImmutableList<TransitiveInfoCollection> getDylibProviderTargets(
RuleContext ruleContext) {
return ImmutableList.<TransitiveInfoCollection>builder()
.addAll(ruleContext.getPrerequisites(DYLIBS_ATTR_NAME, Mode.TARGET))
.addAll(ruleContext.getPrerequisites(BUNDLE_LOADER_ATTR_NAME, Mode.TARGET))
.build();
}
private static Iterable<Artifact> getRequiredLinkInputs(RuleContext ruleContext) {
AppleExecutableBinaryInfo executableProvider =
ruleContext.getPrerequisite(
BUNDLE_LOADER_ATTR_NAME, Mode.TARGET,
AppleExecutableBinaryInfo.SKYLARK_CONSTRUCTOR);
if (executableProvider != null) {
return ImmutableSet.<Artifact>of(executableProvider.getAppleExecutableBinary());
}
return ImmutableSet.<Artifact>of();
}
private static BinaryType getBinaryType(RuleContext ruleContext) {
String binaryTypeString =
ruleContext.attributes().get(AppleBinaryRule.BINARY_TYPE_ATTR, STRING);
return BinaryType.fromString(binaryTypeString);
}
private static ConfiguredTarget ruleConfiguredTargetFromProvider(
RuleContext ruleContext, AppleBinaryOutput appleBinaryOutput)
throws RuleErrorException, ActionConflictException {
NativeInfo nativeInfo = appleBinaryOutput.getBinaryInfoProvider();
AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class);
ObjcProvider objcProvider;
Artifact outputArtifact;
switch (getBinaryType(ruleContext)) {
case EXECUTABLE:
AppleExecutableBinaryInfo executableProvider =
(AppleExecutableBinaryInfo) nativeInfo;
objcProvider = executableProvider.getDepsObjcProvider();
outputArtifact = executableProvider.getAppleExecutableBinary();
break;
case DYLIB:
AppleDylibBinaryInfo dylibProvider = (AppleDylibBinaryInfo) nativeInfo;
objcProvider = dylibProvider.getDepsObjcProvider();
outputArtifact = dylibProvider.getAppleDylibBinary();
break;
case LOADABLE_BUNDLE:
AppleLoadableBundleBinaryInfo loadableBundleProvider =
(AppleLoadableBundleBinaryInfo) nativeInfo;
objcProvider = loadableBundleProvider.getDepsObjcProvider();
outputArtifact = loadableBundleProvider.getAppleLoadableBundleBinary();
break;
default:
ruleContext.ruleError("Unhandled binary type " + getBinaryType(ruleContext));
throw new RuleErrorException();
}
NestedSetBuilder<Artifact> filesToBuild =
NestedSetBuilder.<Artifact>stableOrder().add(outputArtifact);
RuleConfiguredTargetBuilder targetBuilder =
ObjcRuleClasses.ruleConfiguredTarget(ruleContext, filesToBuild.build());
if (appleConfiguration.shouldLinkingRulesPropagateObjc() && objcProvider != null) {
targetBuilder.addNativeDeclaredProvider(objcProvider);
targetBuilder.addSkylarkTransitiveInfo(ObjcProvider.SKYLARK_NAME, objcProvider);
}
InstrumentedFilesInfo instrumentedFilesProvider =
InstrumentedFilesCollector.forward(ruleContext, "deps", "bundle_loader");
return targetBuilder
.addNativeDeclaredProvider(instrumentedFilesProvider)
.addNativeDeclaredProvider(nativeInfo)
.addNativeDeclaredProvider(appleBinaryOutput.getDebugOutputsProvider())
.addOutputGroups(appleBinaryOutput.getOutputGroups())
.build();
}
/**
* The set of rule outputs propagated by the {@code apple_binary} rule.
*/
public static class AppleBinaryOutput {
private final NativeInfo binaryInfoProvider;
private final AppleDebugOutputsInfo debugOutputsProvider;
private final Map<String, NestedSet<Artifact>> outputGroups;
private AppleBinaryOutput(NativeInfo binaryInfoProvider,
AppleDebugOutputsInfo debugOutputsProvider,
Map<String, NestedSet<Artifact>> outputGroups) {
this.binaryInfoProvider = binaryInfoProvider;
this.debugOutputsProvider = debugOutputsProvider;
this.outputGroups = outputGroups;
}
/**
* Returns a {@link NativeInfo} possessing information about the linked binary. Depending
* on the type of binary, this may be either a {@link AppleExecutableBinaryInfo}, a
* {@link AppleDylibBinaryInfo}, or a {@link AppleLoadableBundleBinaryInfo}.
*/
public NativeInfo getBinaryInfoProvider() {
return binaryInfoProvider;
}
/**
* Returns a {@link AppleDebugOutputsInfo} containing debug information about the linked
* binary.
*/
public AppleDebugOutputsInfo getDebugOutputsProvider() {
return debugOutputsProvider;
}
/**
* Returns a map from output group name to set of artifacts belonging to this output group.
* This should be added to configured target information using
* {@link RuleConfiguredTargetBuilder#addOutputGroups(Map)}.
*/
public Map<String, NestedSet<Artifact>> getOutputGroups() {
return outputGroups;
}
}
}