blob: befa6b20e7d79ae0b0e8f82e97de93329ab10f86 [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.rules.objc.ObjcProvider.DEFINE;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.DYNAMIC_FRAMEWORK_FILE;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.FORCE_LOAD_LIBRARY;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.Flag.USES_CPP;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.HEADER;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.IMPORTED_LIBRARY;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.INCLUDE;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.INCLUDE_SYSTEM;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.LINK_INPUTS;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.MODULE_MAP;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.STATIC_FRAMEWORK_FILE;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.WEAK_SDK_FRAMEWORK;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.CLANG;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.CLANG_PLUSPLUS;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.COMPILABLE_SRCS_TYPE;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.DSYMUTIL;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.HEADERS;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.NON_ARC_SRCS_TYPE;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.PRECOMPILED_SRCS_TYPE;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.SRCS_TYPE;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
import com.google.devtools.build.lib.analysis.PrerequisiteArtifacts;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.CommandLine;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate;
import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate.OutputPathMapper;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.packages.TargetUtils;
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.AppleToolchain;
import com.google.devtools.build.lib.rules.apple.DottedVersion;
import com.google.devtools.build.lib.rules.apple.Platform;
import com.google.devtools.build.lib.rules.cpp.CcToolchainProvider;
import com.google.devtools.build.lib.rules.cpp.CppCompileAction.DotdFile;
import com.google.devtools.build.lib.rules.cpp.CppModuleMap;
import com.google.devtools.build.lib.rules.cpp.FdoSupportProvider;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.List;
import javax.annotation.Nullable;
/**
* Constructs command lines for objc compilation, archiving, and linking. Uses hard-coded
* command line templates.
*
* TODO(b/28403953): Deprecate in favor of {@link CrosstoolCompilationSupport} in all objc rules.
*/
public class LegacyCompilationSupport extends CompilationSupport {
/**
* A mapper that maps input ObjC source {@link Artifact.TreeFileArtifact}s to output object file
* {@link Artifact.TreeFileArtifact}s.
*/
private static final OutputPathMapper COMPILE_ACTION_TEMPLATE_OUTPUT_PATH_MAPPER =
new OutputPathMapper() {
@Override
public PathFragment parentRelativeOutputPath(TreeFileArtifact inputTreeFileArtifact) {
return FileSystemUtils.replaceExtension(
inputTreeFileArtifact.getParentRelativePath(), ".o");
}
};
/**
* Returns information about the given rule's compilation artifacts. Dependencies specified in the
* current rule's attributes are obtained via {@code ruleContext}. Output locations are determined
* using the given {@code intermediateArtifacts} object. The fact that these are distinct objects
* allows the caller to generate compilation actions pertaining to a configuration separate from
* the current rule's configuration.
*/
static CompilationArtifacts compilationArtifacts(
RuleContext ruleContext, IntermediateArtifacts intermediateArtifacts) {
PrerequisiteArtifacts srcs =
ruleContext.getPrerequisiteArtifacts("srcs", Mode.TARGET).errorsForNonMatching(SRCS_TYPE);
return new CompilationArtifacts.Builder()
.addSrcs(srcs.filter(COMPILABLE_SRCS_TYPE).list())
.addNonArcSrcs(
ruleContext
.getPrerequisiteArtifacts("non_arc_srcs", Mode.TARGET)
.errorsForNonMatching(NON_ARC_SRCS_TYPE)
.list())
.addPrivateHdrs(srcs.filter(HEADERS).list())
.addPrecompiledSrcs(srcs.filter(PRECOMPILED_SRCS_TYPE).list())
.setIntermediateArtifacts(intermediateArtifacts)
.setPchFile(Optional.fromNullable(ruleContext.getPrerequisiteArtifact("pch", Mode.TARGET)))
.build();
}
/**
* Creates a new legacy compilation support for the given rule and build configuration.
*
* <p>All actions will be created under the given build configuration, which may be different than
* the current rule context configuration.
*
* <p>The compilation and linking flags will be retrieved from the given compilation attributes.
* The names of the generated artifacts will be retrieved from the given intermediate artifacts.
*
* <p>By instantiating multiple compilation supports for the same rule but with intermediate
* artifacts with different output prefixes, multiple archives can be compiled for the same rule
* context.
*/
LegacyCompilationSupport(
RuleContext ruleContext,
BuildConfiguration buildConfiguration,
IntermediateArtifacts intermediateArtifacts,
CompilationAttributes compilationAttributes) {
super(ruleContext, buildConfiguration, intermediateArtifacts, compilationAttributes);
}
@Override
CompilationSupport registerCompileAndArchiveActions(
CompilationArtifacts compilationArtifacts, ObjcProvider objcProvider,
ExtraCompileArgs extraCompileArgs, Iterable<PathFragment> priorityHeaders,
@Nullable CcToolchainProvider ccToolchain, @Nullable FdoSupportProvider fdoSupport) {
registerGenerateModuleMapAction(compilationArtifacts);
Optional<CppModuleMap> moduleMap;
if (objcConfiguration.moduleMapsEnabled()) {
moduleMap = Optional.of(intermediateArtifacts.moduleMap());
} else {
moduleMap = Optional.absent();
}
registerCompileAndArchiveActions(
compilationArtifacts,
objcProvider,
extraCompileArgs,
priorityHeaders,
moduleMap);
return this;
}
/**
* Creates actions to compile each source file individually, and link all the compiled object
* files into a single archive library.
*/
private void registerCompileAndArchiveActions(
CompilationArtifacts compilationArtifacts,
ObjcProvider objcProvider,
ExtraCompileArgs extraCompileArgs,
Iterable<PathFragment> priorityHeaders,
Optional<CppModuleMap> moduleMap) {
ImmutableList.Builder<Artifact> objFiles = ImmutableList.builder();
ImmutableList.Builder<ObjcHeaderThinningInfo> objcHeaderThinningInfos = ImmutableList.builder();
for (Artifact sourceFile : compilationArtifacts.getSrcs()) {
Artifact objFile = intermediateArtifacts.objFile(sourceFile);
objFiles.add(objFile);
if (objFile.isTreeArtifact()) {
registerCompileActionTemplate(
sourceFile,
objFile,
objcProvider,
priorityHeaders,
moduleMap,
compilationArtifacts,
Iterables.concat(extraCompileArgs, ImmutableList.of("-fobjc-arc")));
} else {
ObjcHeaderThinningInfo objcHeaderThinningInfo =
registerCompileAction(
sourceFile,
objFile,
objcProvider,
priorityHeaders,
moduleMap,
compilationArtifacts,
Iterables.concat(extraCompileArgs, ImmutableList.of("-fobjc-arc")));
if (objcHeaderThinningInfo != null) {
objcHeaderThinningInfos.add(objcHeaderThinningInfo);
}
}
}
for (Artifact nonArcSourceFile : compilationArtifacts.getNonArcSrcs()) {
Artifact objFile = intermediateArtifacts.objFile(nonArcSourceFile);
objFiles.add(objFile);
if (objFile.isTreeArtifact()) {
registerCompileActionTemplate(
nonArcSourceFile,
objFile,
objcProvider,
priorityHeaders,
moduleMap,
compilationArtifacts,
Iterables.concat(extraCompileArgs, ImmutableList.of("-fno-objc-arc")));
} else {
ObjcHeaderThinningInfo objcHeaderThinningInfo =
registerCompileAction(
nonArcSourceFile,
objFile,
objcProvider,
priorityHeaders,
moduleMap,
compilationArtifacts,
Iterables.concat(extraCompileArgs, ImmutableList.of("-fno-objc-arc")));
if (objcHeaderThinningInfo != null) {
objcHeaderThinningInfos.add(objcHeaderThinningInfo);
}
}
}
objFiles.addAll(compilationArtifacts.getPrecompiledSrcs());
for (Artifact archive : compilationArtifacts.getArchive().asSet()) {
registerArchiveActions(objFiles.build(), archive);
}
registerHeaderScanningActions(
objcHeaderThinningInfos.build(), objcProvider, compilationArtifacts);
}
private CustomCommandLine compileActionCommandLine(
Artifact sourceFile,
Artifact objFile,
ObjcProvider objcProvider,
Iterable<PathFragment> priorityHeaders,
Optional<CppModuleMap> moduleMap,
Optional<Artifact> pchFile,
Optional<Artifact> dotdFile,
Iterable<String> otherFlags,
boolean collectCodeCoverage,
boolean isCPlusPlusSource) {
CustomCommandLine.Builder commandLine = new CustomCommandLine.Builder().add(CLANG);
if (isCPlusPlusSource) {
commandLine.add("-stdlib=libc++");
commandLine.add("-std=gnu++11");
}
// The linker needs full debug symbol information to perform binary dead-code stripping.
if (objcConfiguration.shouldStripBinary()) {
commandLine.add("-g");
}
List<String> coverageFlags = ImmutableList.of();
if (collectCodeCoverage) {
if (buildConfiguration.isLLVMCoverageMapFormatEnabled()) {
coverageFlags = CLANG_LLVM_COVERAGE_FLAGS;
} else {
coverageFlags = CLANG_GCOV_COVERAGE_FLAGS;
}
}
commandLine
.add(compileFlagsForClang(appleConfiguration))
.add(commonLinkAndCompileFlagsForClang(objcProvider, objcConfiguration, appleConfiguration))
.add(objcConfiguration.getCoptsForCompilationMode())
.addBeforeEachPath(
"-iquote", ObjcCommon.userHeaderSearchPaths(objcProvider, buildConfiguration))
.addBeforeEachExecPath("-include", pchFile.asSet())
.addBeforeEachPath("-I", priorityHeaders)
.addBeforeEachPath("-I", objcProvider.get(INCLUDE))
.addBeforeEachPath("-isystem", objcProvider.get(INCLUDE_SYSTEM))
.add(otherFlags)
.addFormatEach("-D%s", objcProvider.get(DEFINE))
.add(coverageFlags)
.add(getCompileRuleCopts());
// Add input source file arguments
commandLine.add("-c");
if (!sourceFile.getExecPath().isAbsolute()
&& objcConfiguration.getUseAbsolutePathsForActions()) {
String workspaceRoot = objcConfiguration.getXcodeWorkspaceRoot();
// If the source file is a tree artifact, it means the file is basically a directory that may
// contain multiple concrete source files at execution time. When constructing the command
// line, we insert the source tree artifact as a placeholder, which will be replaced with
// one of its contained source files of type {@link Artifact.TreeFileArtifact} at execution
// time.
//
// We also do something similar for the object file arguments below.
if (sourceFile.isTreeArtifact()) {
commandLine.addPlaceholderTreeArtifactFormattedExecPath(workspaceRoot + "/%s", sourceFile);
} else {
commandLine.addPaths(workspaceRoot + "/%s", sourceFile.getExecPath());
}
} else {
if (sourceFile.isTreeArtifact()) {
commandLine.addPlaceholderTreeArtifactExecPath(sourceFile);
} else {
commandLine.addPath(sourceFile.getExecPath());
}
}
// Add output object file arguments.
commandLine.add("-o");
if (objFile.isTreeArtifact()) {
commandLine.addPlaceholderTreeArtifactExecPath(objFile);
} else {
commandLine.addPath(objFile.getExecPath());
}
// Add Dotd file arguments.
if (dotdFile.isPresent()) {
commandLine.add("-MD").addExecPath("-MF", dotdFile.get());
}
// Add module map arguments.
if (moduleMap.isPresent()) {
// If modules are enabled for the rule, -fmodules is added to the copts already. (This implies
// module map usage). Otherwise, we need to pass -fmodule-maps.
if (!attributes.enableModules()) {
commandLine.add("-fmodule-maps");
}
// -fmodule-map-file only loads the module in Xcode 7, so we add the module maps's directory
// to the include path instead.
// TODO(bazel-team): Use -fmodule-map-file when Xcode 6 support is dropped.
commandLine
.add("-iquote")
.add(moduleMap.get().getArtifact().getExecPath().getParentDirectory().toString())
.add("-fmodule-name=" + moduleMap.get().getName());
}
return commandLine.build();
}
@Nullable
private ObjcHeaderThinningInfo registerCompileAction(
Artifact sourceFile,
Artifact objFile,
ObjcProvider objcProvider,
Iterable<PathFragment> priorityHeaders,
Optional<CppModuleMap> moduleMap,
CompilationArtifacts compilationArtifacts,
Iterable<String> otherFlags) {
boolean isCPlusPlusSource = ObjcRuleClasses.CPP_SOURCES.matches(sourceFile.getExecPath());
boolean runCodeCoverage =
buildConfiguration.isCodeCoverageEnabled() && ObjcRuleClasses.isInstrumentable(sourceFile);
DotdFile dotdFile = intermediateArtifacts.dotdFile(sourceFile);
CustomCommandLine commandLine =
compileActionCommandLine(
sourceFile,
objFile,
objcProvider,
priorityHeaders,
moduleMap,
compilationArtifacts.getPchFile(),
Optional.of(dotdFile.artifact()),
otherFlags,
runCodeCoverage,
isCPlusPlusSource);
Optional<Artifact> gcnoFile = Optional.absent();
if (runCodeCoverage && !buildConfiguration.isLLVMCoverageMapFormatEnabled()) {
gcnoFile = Optional.of(intermediateArtifacts.gcnoFile(sourceFile));
}
NestedSet<Artifact> moduleMapInputs = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
if (objcConfiguration.moduleMapsEnabled()) {
moduleMapInputs = objcProvider.get(MODULE_MAP);
}
// TODO(bazel-team): Remove private headers from inputs once they're added to the provider.
ObjcCompileAction.Builder compileBuilder =
ObjcCompileAction.Builder.createObjcCompileActionBuilderWithAppleEnv(
appleConfiguration, appleConfiguration.getSingleArchPlatform())
.setDotdPruningPlan(objcConfiguration.getDotdPruningPlan())
.setSourceFile(sourceFile)
.addTransitiveHeaders(objcProvider.get(HEADER))
.addHeaders(compilationArtifacts.getPrivateHdrs())
.addTransitiveMandatoryInputs(moduleMapInputs)
.addTransitiveMandatoryInputs(objcProvider.get(STATIC_FRAMEWORK_FILE))
.addTransitiveMandatoryInputs(objcProvider.get(DYNAMIC_FRAMEWORK_FILE))
.setDotdFile(dotdFile)
.addMandatoryInputs(compilationArtifacts.getPchFile().asSet());
Artifact headersListFile = null;
if (isHeaderThinningEnabled()
&& SOURCES_FOR_HEADER_THINNING.matches(sourceFile.getFilename())) {
headersListFile = intermediateArtifacts.headersListFile(sourceFile);
compileBuilder.setHeadersListFile(headersListFile);
}
ruleContext.registerAction(
compileBuilder
.setMnemonic("ObjcCompile")
.setExecutable(xcrunwrapper(ruleContext))
.setCommandLine(commandLine)
.addOutput(objFile)
.addOutputs(gcnoFile.asSet())
.addOutput(dotdFile.artifact())
.build(ruleContext));
return headersListFile == null
? null
: new ObjcHeaderThinningInfo(
sourceFile, headersListFile, ImmutableList.copyOf(commandLine.arguments()));
}
/**
* Registers a SpawnActionTemplate to compile the source file tree artifact, {@code sourceFiles},
* which can contain multiple concrete source files unknown at analysis time. At execution time,
* the SpawnActionTemplate will register one ObjcCompile action for each individual source file
* under {@code sourceFiles}.
*
* <p>Note that this method currently does not support code coverage and sources other than ObjC
* sources.
*
* @param sourceFiles tree artifact containing source files to compile
* @param objFiles tree artifact containing object files compiled from {@code sourceFiles}
* @param objcProvider ObjcProvider instance for this invocation
* @param priorityHeaders priority headers to be included before the dependency headers
* @param moduleMap the module map generated from the associated headers
* @param compilationArtifacts the CompilationArtifacts instance for this invocation
* @param otherFlags extra compilation flags to add to the compile action command line
*/
private void registerCompileActionTemplate(
Artifact sourceFiles,
Artifact objFiles,
ObjcProvider objcProvider,
Iterable<PathFragment> priorityHeaders,
Optional<CppModuleMap> moduleMap,
CompilationArtifacts compilationArtifacts,
Iterable<String> otherFlags) {
CustomCommandLine commandLine =
compileActionCommandLine(
sourceFiles,
objFiles,
objcProvider,
priorityHeaders,
moduleMap,
compilationArtifacts.getPchFile(),
Optional.<Artifact>absent(),
otherFlags,
/* runCodeCoverage=*/ false,
/* isCPlusPlusSource=*/ false);
AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class);
Platform platform = appleConfiguration.getSingleArchPlatform();
NestedSet<Artifact> moduleMapInputs = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
if (objcConfiguration.moduleMapsEnabled()) {
moduleMapInputs = objcProvider.get(MODULE_MAP);
}
ruleContext.registerAction(
new SpawnActionTemplate.Builder(sourceFiles, objFiles)
.setMnemonics("ObjcCompileActionTemplate", "ObjcCompile")
.setExecutable(xcrunwrapper(ruleContext))
.setCommandLineTemplate(commandLine)
.setEnvironment(ObjcRuleClasses.appleToolchainEnvironment(appleConfiguration, platform))
.setExecutionInfo(ObjcRuleClasses.darwinActionExecutionRequirement())
.setOutputPathMapper(COMPILE_ACTION_TEMPLATE_OUTPUT_PATH_MAPPER)
.addCommonTransitiveInputs(objcProvider.get(HEADER))
.addCommonTransitiveInputs(moduleMapInputs)
.addCommonInputs(compilationArtifacts.getPrivateHdrs())
.addCommonTransitiveInputs(objcProvider.get(STATIC_FRAMEWORK_FILE))
.addCommonTransitiveInputs(objcProvider.get(DYNAMIC_FRAMEWORK_FILE))
.addCommonInputs(compilationArtifacts.getPchFile().asSet())
.build(ruleContext.getActionOwner()));
}
private void registerArchiveActions(List<Artifact> objFiles, Artifact archive) {
Artifact objList = intermediateArtifacts.archiveObjList();
registerObjFilelistAction(objFiles, objList);
registerArchiveAction(objFiles, archive);
}
private void registerArchiveAction(
Iterable<Artifact> objFiles,
Artifact archive) {
Artifact objList = intermediateArtifacts.archiveObjList();
ruleContext.registerAction(ObjcRuleClasses.spawnAppleEnvActionBuilder(
appleConfiguration, appleConfiguration.getSingleArchPlatform())
.setMnemonic("ObjcLink")
.setExecutable(libtool(ruleContext))
.setCommandLine(new CustomCommandLine.Builder()
.add("-static")
.add("-filelist").add(objList.getExecPathString())
.add("-arch_only").add(appleConfiguration.getSingleArchitecture())
.add("-syslibroot").add(AppleToolchain.sdkDir())
.add("-o").add(archive.getExecPathString())
.build())
.addInputs(objFiles)
.addInput(objList)
.addOutput(archive)
.build(ruleContext));
}
@Override
protected CompilationSupport registerFullyLinkAction(
ObjcProvider objcProvider, Iterable<Artifact> inputArtifacts, Artifact outputArchive,
@Nullable CcToolchainProvider ccToolchain, @Nullable FdoSupportProvider fdoSupport) {
ruleContext.registerAction(
ObjcRuleClasses.spawnAppleEnvActionBuilder(
appleConfiguration, appleConfiguration.getSingleArchPlatform())
.setMnemonic("ObjcLink")
.setExecutable(libtool(ruleContext))
.setCommandLine(
new CustomCommandLine.Builder()
.add("-static")
.add("-arch_only").add(appleConfiguration.getSingleArchitecture())
.add("-syslibroot").add(AppleToolchain.sdkDir())
.add("-o").add(outputArchive.getExecPathString())
.addExecPaths(inputArtifacts)
.build())
.addInputs(inputArtifacts)
.addOutput(outputArchive)
.build(ruleContext));
return this;
}
@Override
CompilationSupport registerLinkActions(
ObjcProvider objcProvider,
J2ObjcMappingFileProvider j2ObjcMappingFileProvider,
J2ObjcEntryClassProvider j2ObjcEntryClassProvider,
ExtraLinkArgs extraLinkArgs,
Iterable<Artifact> extraLinkInputs,
DsymOutputType dsymOutputType) {
Optional<Artifact> dsymBundleZip;
Optional<Artifact> linkmap;
Optional<Artifact> bitcodeSymbolMap;
if (objcConfiguration.generateDsym()) {
registerDsymActions(dsymOutputType);
dsymBundleZip = Optional.of(intermediateArtifacts.tempDsymBundleZip(dsymOutputType));
} else {
dsymBundleZip = Optional.absent();
}
Iterable<Artifact> prunedJ2ObjcArchives =
computeAndStripPrunedJ2ObjcArchives(
j2ObjcEntryClassProvider, j2ObjcMappingFileProvider, objcProvider);
if (objcConfiguration.generateLinkmap()) {
linkmap = Optional.of(intermediateArtifacts.linkmap());
} else {
linkmap = Optional.absent();
}
if (appleConfiguration.getBitcodeMode() == AppleBitcodeMode.EMBEDDED) {
bitcodeSymbolMap = Optional.of(intermediateArtifacts.bitcodeSymbolMap());
} else {
bitcodeSymbolMap = Optional.absent();
}
registerLinkAction(
objcProvider,
extraLinkArgs,
extraLinkInputs,
dsymBundleZip,
prunedJ2ObjcArchives,
linkmap,
bitcodeSymbolMap);
return this;
}
private StrippingType getStrippingType(CommandLine commandLine) {
return Iterables.contains(commandLine.arguments(), "-dynamiclib")
? StrippingType.DYNAMIC_LIB
: StrippingType.DEFAULT;
}
private void registerLinkAction(
ObjcProvider objcProvider,
ExtraLinkArgs extraLinkArgs,
Iterable<Artifact> extraLinkInputs,
Optional<Artifact> dsymBundleZip,
Iterable<Artifact> prunedJ2ObjcArchives,
Optional<Artifact> linkmap,
Optional<Artifact> bitcodeSymbolMap) {
Artifact binaryToLink = getBinaryToLink();
ImmutableList<Artifact> objcLibraries = objcProvider.getObjcLibraries();
ImmutableList<Artifact> ccLibraries = objcProvider.getCcLibraries();
ImmutableList<Artifact> bazelBuiltLibraries = Iterables.isEmpty(prunedJ2ObjcArchives)
? objcLibraries : substituteJ2ObjcPrunedLibraries(objcProvider);
CommandLine commandLine =
linkCommandLine(
extraLinkArgs,
objcProvider,
binaryToLink,
dsymBundleZip,
ccLibraries,
bazelBuiltLibraries,
linkmap,
bitcodeSymbolMap);
ruleContext.registerAction(
ObjcRuleClasses.spawnAppleEnvActionBuilder(
appleConfiguration, appleConfiguration.getSingleArchPlatform())
.setMnemonic("ObjcLink")
.setShellCommand(ImmutableList.of("/bin/bash", "-c"))
.setCommandLine(new SingleArgCommandLine(commandLine))
.addOutput(binaryToLink)
.addOutputs(dsymBundleZip.asSet())
.addOutputs(linkmap.asSet())
.addOutputs(bitcodeSymbolMap.asSet())
.addInputs(bazelBuiltLibraries)
.addTransitiveInputs(objcProvider.get(IMPORTED_LIBRARY))
.addTransitiveInputs(objcProvider.get(STATIC_FRAMEWORK_FILE))
.addTransitiveInputs(objcProvider.get(DYNAMIC_FRAMEWORK_FILE))
.addTransitiveInputs(objcProvider.get(LINK_INPUTS))
.addInputs(ccLibraries)
.addInputs(extraLinkInputs)
.addInputs(prunedJ2ObjcArchives)
.addInput(intermediateArtifacts.linkerObjList())
.addInput(xcrunwrapper(ruleContext).getExecutable())
.build(ruleContext));
if (objcConfiguration.shouldStripBinary()) {
registerBinaryStripAction(binaryToLink, getStrippingType(commandLine));
}
}
private CommandLine linkCommandLine(
ExtraLinkArgs extraLinkArgs,
ObjcProvider objcProvider,
Artifact linkedBinary,
Optional<Artifact> dsymBundleZip,
Iterable<Artifact> ccLibraries,
Iterable<Artifact> bazelBuiltLibraries,
Optional<Artifact> linkmap,
Optional<Artifact> bitcodeSymbolMap) {
Iterable<String> libraryNames = libraryNames(objcProvider);
CustomCommandLine.Builder commandLine = CustomCommandLine.builder()
.addPath(xcrunwrapper(ruleContext).getExecutable().getExecPath());
if (objcProvider.is(USES_CPP)) {
commandLine
.add(CLANG_PLUSPLUS)
.add("-stdlib=libc++")
.add("-std=gnu++11");
} else {
commandLine.add(CLANG);
}
// Do not perform code stripping on tests because XCTest binary is linked not as an executable
// but as a bundle without any entry point.
boolean isTestTarget = TargetUtils.isTestRule(ruleContext.getRule());
if (objcConfiguration.shouldStripBinary() && !isTestTarget) {
commandLine.add("-dead_strip").add("-no_dead_strip_inits_and_terms");
}
Iterable<Artifact> ccLibrariesToForceLoad =
Iterables.filter(ccLibraries, ALWAYS_LINKED_CC_LIBRARY);
ImmutableSet<Artifact> forceLinkArtifacts = ImmutableSet.<Artifact>builder()
.addAll(objcProvider.get(FORCE_LOAD_LIBRARY))
.addAll(ccLibrariesToForceLoad).build();
Artifact inputFileList = intermediateArtifacts.linkerObjList();
Iterable<Artifact> objFiles =
Iterables.concat(bazelBuiltLibraries, objcProvider.get(IMPORTED_LIBRARY), ccLibraries);
// Clang loads archives specified in filelists and also specified as -force_load twice,
// resulting in duplicate symbol errors unless they are deduped.
objFiles = Iterables.filter(objFiles, Predicates.not(Predicates.in(forceLinkArtifacts)));
registerObjFilelistAction(objFiles, inputFileList);
if (objcConfiguration.shouldPrioritizeStaticLibs()) {
commandLine.add("-filelist").add(inputFileList.getExecPathString());
}
AppleBitcodeMode bitcodeMode = appleConfiguration.getBitcodeMode();
commandLine.add(bitcodeMode.getCompileAndLinkFlags());
if (bitcodeMode == AppleBitcodeMode.EMBEDDED) {
commandLine.add("-Xlinker").add("-bitcode_verify");
commandLine.add("-Xlinker").add("-bitcode_hide_symbols");
commandLine
.add("-Xlinker")
.add("-bitcode_symbol_map")
.add("-Xlinker")
.add(bitcodeSymbolMap.get().getExecPathString());
}
commandLine
.add(commonLinkAndCompileFlagsForClang(objcProvider, objcConfiguration, appleConfiguration))
.add("-Xlinker")
.add("-objc_abi_version")
.add("-Xlinker")
.add("2")
// Set the rpath so that at runtime dylibs can be loaded from the bundle root's "Frameworks"
// directory.
.add("-Xlinker")
.add("-rpath")
.add("-Xlinker")
.add("@executable_path/Frameworks")
.add("-fobjc-link-runtime")
.add(DEFAULT_LINKER_FLAGS)
.addBeforeEach("-framework", frameworkNames(objcProvider))
.addBeforeEach("-weak_framework", SdkFramework.names(objcProvider.get(WEAK_SDK_FRAMEWORK)))
.addFormatEach("-l%s", libraryNames);
if (!objcConfiguration.shouldPrioritizeStaticLibs()) {
commandLine.add("-filelist").add(inputFileList.getExecPathString());
}
commandLine
.addExecPath("-o", linkedBinary)
.addBeforeEachExecPath("-force_load", forceLinkArtifacts)
.add(extraLinkArgs)
.add(objcProvider.get(ObjcProvider.LINKOPT));
if (buildConfiguration.isCodeCoverageEnabled()) {
if (buildConfiguration.isLLVMCoverageMapFormatEnabled()) {
commandLine.add(LINKER_LLVM_COVERAGE_FLAGS);
} else {
commandLine.add(LINKER_COVERAGE_FLAGS);
}
}
for (String linkopt : attributes.linkopts()) {
commandLine.add("-Wl," + linkopt);
}
if (linkmap.isPresent()) {
commandLine
.add("-Xlinker -map")
.add("-Xlinker " + linkmap.get().getExecPath());
}
// Call to dsymutil for debug symbol generation must happen in the link action.
// All debug symbol information is encoded in object files inside archive files. To generate
// the debug symbol bundle, dsymutil will look inside the linked binary for the encoded
// absolute paths to archive files, which are only valid in the link action.
if (dsymBundleZip.isPresent()) {
PathFragment dsymPath = FileSystemUtils.removeExtension(dsymBundleZip.get().getExecPath());
commandLine
.add("&&")
.addPath(xcrunwrapper(ruleContext).getExecutable().getExecPath())
.add(DSYMUTIL)
.add(linkedBinary.getExecPathString())
.add("-o " + dsymPath)
.add("&& zipped_bundle=${PWD}/" + dsymBundleZip.get().getShellEscapedExecPathString())
.add("&& cd " + dsymPath)
.add("&& /usr/bin/zip -q -r \"${zipped_bundle}\" .");
}
return commandLine.build();
}
/**
* Command line that converts its input's arg array to a single input.
*
* <p>Required as a hack to the link command line because that may contain two commands, which are
* then passed to {@code /bin/bash -c}, and accordingly need to be a single argument.
*/
@Immutable // if original is immutable
private static final class SingleArgCommandLine extends CommandLine {
private final CommandLine original;
private SingleArgCommandLine(CommandLine original) {
this.original = original;
}
@Override
public Iterable<String> arguments() {
return ImmutableList.of(Joiner.on(' ').join(original.arguments()));
}
}
private CompilationSupport registerDsymActions(DsymOutputType dsymOutputType) {
Artifact tempDsymBundleZip = intermediateArtifacts.tempDsymBundleZip(dsymOutputType);
Artifact linkedBinary =
objcConfiguration.shouldStripBinary()
? intermediateArtifacts.unstrippedSingleArchitectureBinary()
: intermediateArtifacts.strippedSingleArchitectureBinary();
Artifact debugSymbolFile = intermediateArtifacts.dsymSymbol(dsymOutputType);
Artifact dsymPlist = intermediateArtifacts.dsymPlist(dsymOutputType);
PathFragment dsymOutputDir = removeSuffix(tempDsymBundleZip.getExecPath(), ".temp.zip");
PathFragment dsymPlistZipEntry = dsymPlist.getExecPath().relativeTo(dsymOutputDir);
PathFragment debugSymbolFileZipEntry =
debugSymbolFile
.getExecPath()
.replaceName(linkedBinary.getFilename())
.relativeTo(dsymOutputDir);
StringBuilder unzipDsymCommand =
new StringBuilder()
.append(
String.format(
"unzip -p %s %s > %s",
tempDsymBundleZip.getExecPathString(),
dsymPlistZipEntry,
dsymPlist.getExecPathString()))
.append(
String.format(
" && unzip -p %s %s > %s",
tempDsymBundleZip.getExecPathString(),
debugSymbolFileZipEntry,
debugSymbolFile.getExecPathString()));
ruleContext.registerAction(
new SpawnAction.Builder()
.setMnemonic("UnzipDsym")
.setShellCommand(unzipDsymCommand.toString())
.addInput(tempDsymBundleZip)
.addOutput(dsymPlist)
.addOutput(debugSymbolFile)
.build(ruleContext));
return this;
}
private PathFragment removeSuffix(PathFragment path, String suffix) {
String name = path.getBaseName();
Preconditions.checkArgument(
name.endsWith(suffix), "expect %s to end with %s, but it does not", name, suffix);
return path.replaceName(name.substring(0, name.length() - suffix.length()));
}
/** Returns a list of clang flags used for all link and compile actions executed through clang. */
private List<String> commonLinkAndCompileFlagsForClang(
ObjcProvider provider, ObjcConfiguration objcConfiguration,
AppleConfiguration appleConfiguration) {
ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
Platform platform = appleConfiguration.getSingleArchPlatform();
String minOSVersionArg;
switch (platform) {
case IOS_SIMULATOR:
minOSVersionArg = "-mios-simulator-version-min";
break;
case IOS_DEVICE:
minOSVersionArg = "-miphoneos-version-min";
break;
case WATCHOS_SIMULATOR:
minOSVersionArg = "-mwatchos-simulator-version-min";
break;
case WATCHOS_DEVICE:
minOSVersionArg = "-mwatchos-version-min";
break;
case TVOS_SIMULATOR:
minOSVersionArg = "-mtvos-simulator-version-min";
break;
case TVOS_DEVICE:
minOSVersionArg = "-mtvos-version-min";
break;
default:
throw new IllegalArgumentException("Unhandled platform " + platform);
}
DottedVersion minOSVersion = appleConfiguration.getMinimumOsForPlatformType(platform.getType());
builder.add(minOSVersionArg + "=" + minOSVersion);
if (objcConfiguration.generateDsym()) {
builder.add("-g");
}
return builder
.add("-arch", appleConfiguration.getSingleArchitecture())
.add("-isysroot", AppleToolchain.sdkDir())
// TODO(bazel-team): Pass framework search paths to Xcodegen.
.addAll(commonFrameworkFlags(provider, appleConfiguration))
.build();
}
private static Iterable<String> compileFlagsForClang(AppleConfiguration configuration) {
return Iterables.concat(
AppleToolchain.DEFAULT_WARNINGS.values(),
platformSpecificCompileFlagsForClang(configuration),
configuration.getBitcodeMode().getCompileAndLinkFlags(),
DEFAULT_COMPILER_FLAGS
);
}
private static List<String> platformSpecificCompileFlagsForClang(
AppleConfiguration configuration) {
switch (configuration.getSingleArchPlatform()) {
case IOS_DEVICE:
case WATCHOS_DEVICE:
case TVOS_DEVICE:
return ImmutableList.of();
case IOS_SIMULATOR:
case WATCHOS_SIMULATOR:
case TVOS_SIMULATOR:
return SIMULATOR_COMPILE_FLAGS;
default:
throw new AssertionError();
}
}
}