// Copyright 2015 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.collect.ImmutableSortedSet.toImmutableSortedSet; import static com.google.devtools.build.lib.packages.ImplicitOutputsFunction.fromTemplates; import static com.google.devtools.build.lib.rules.cpp.Link.LINK_LIBRARY_FILETYPES; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.DEFINE; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.DYNAMIC_FRAMEWORK_DIR; 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.FRAMEWORK_SEARCH_PATH_ONLY; 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.LIBRARY; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.LINK_INPUTS; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.SDK_DYLIB; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.SDK_FRAMEWORK; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.STATIC_FRAMEWORK_FILE; import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.COMPILABLE_SRCS_TYPE; 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 static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.STRIP; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.util.Comparator.naturalOrder; import static java.util.stream.Collectors.toCollection; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.collect.Streams; import com.google.devtools.build.lib.actions.ActionAnalysisMetadata; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.CommandLine; import com.google.devtools.build.lib.actions.ParamFileInfo; import com.google.devtools.build.lib.actions.ParameterFile; import com.google.devtools.build.lib.analysis.AnalysisEnvironment; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.PrerequisiteArtifacts; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg; 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.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.InstrumentedFilesCollector.InstrumentationSpec; import com.google.devtools.build.lib.analysis.test.InstrumentedFilesCollector.LocalMetadataCollector; import com.google.devtools.build.lib.analysis.test.InstrumentedFilesProvider; 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.packages.BuildType; import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction; import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; 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.apple.AppleToolchain; import com.google.devtools.build.lib.rules.apple.XcodeConfig; import com.google.devtools.build.lib.rules.apple.XcodeConfigProvider; import com.google.devtools.build.lib.rules.cpp.CcCommon; import com.google.devtools.build.lib.rules.cpp.CcCompilationContext; import com.google.devtools.build.lib.rules.cpp.CcCompilationHelper; import com.google.devtools.build.lib.rules.cpp.CcCompilationHelper.CompilationInfo; import com.google.devtools.build.lib.rules.cpp.CcCompilationOutputs; import com.google.devtools.build.lib.rules.cpp.CcLinkingHelper; import com.google.devtools.build.lib.rules.cpp.CcLinkingHelper.LinkingInfo; import com.google.devtools.build.lib.rules.cpp.CcToolchain; import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.CollidingProvidesException; import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration; import com.google.devtools.build.lib.rules.cpp.CcToolchainProvider; import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.VariablesExtension; import com.google.devtools.build.lib.rules.cpp.CppCompileAction; import com.google.devtools.build.lib.rules.cpp.CppFileTypes; import com.google.devtools.build.lib.rules.cpp.CppHelper; import com.google.devtools.build.lib.rules.cpp.CppLinkAction; import com.google.devtools.build.lib.rules.cpp.CppLinkActionBuilder; import com.google.devtools.build.lib.rules.cpp.CppModuleMap; import com.google.devtools.build.lib.rules.cpp.CppModuleMapAction; import com.google.devtools.build.lib.rules.cpp.CppRuleClasses; import com.google.devtools.build.lib.rules.cpp.FdoSupportProvider; import com.google.devtools.build.lib.rules.cpp.IncludeProcessing; import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType; import com.google.devtools.build.lib.rules.cpp.Link.LinkingMode; import com.google.devtools.build.lib.rules.cpp.NoProcessing; import com.google.devtools.build.lib.rules.cpp.PrecompiledFiles; import com.google.devtools.build.lib.rules.cpp.UmbrellaHeaderAction; import com.google.devtools.build.lib.rules.objc.ObjcProvider.Flag; import com.google.devtools.build.lib.rules.objc.ObjcVariablesExtension.VariableCategory; import com.google.devtools.build.lib.util.FileTypeSet; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.stream.Stream; import javax.annotation.Nullable; /** * Support for rules that compile sources. Provides ways to determine files that should be output, * registering Xcode settings and generating the various actions that might be needed for * compilation. * *

A subclass should express a particular strategy for compile and link action registration. * Subclasses should implement the API without adding new visible methods - rule implementations * should be able to use a {@link CompilationSupport} instance to compile and link source without * knowing the subclass being used. * *

Methods on this class can be called in any order without impacting the result. */ public class CompilationSupport { @VisibleForTesting static final String OBJC_MODULE_CACHE_DIR_NAME = "_objc_module_cache"; @VisibleForTesting static final String MODULES_CACHE_PATH_WARNING = "setting '-fmodules-cache-path' manually in copts is unsupported"; @VisibleForTesting static final String ABSOLUTE_INCLUDES_PATH_FORMAT = "The path '%s' is absolute, but only relative paths are allowed."; @VisibleForTesting static final ImmutableList LINKER_COVERAGE_FLAGS = ImmutableList.of("-ftest-coverage", "-fprofile-arcs"); @VisibleForTesting static final ImmutableList LINKER_LLVM_COVERAGE_FLAGS = ImmutableList.of("-fprofile-instr-generate"); // Flags for clang 6.1(xcode 6.4) @VisibleForTesting static final ImmutableList CLANG_GCOV_COVERAGE_FLAGS = ImmutableList.of("-fprofile-arcs", "-ftest-coverage"); @VisibleForTesting static final ImmutableList CLANG_LLVM_COVERAGE_FLAGS = ImmutableList.of("-fprofile-instr-generate", "-fcoverage-mapping"); // These are added by Xcode when building, because the simulator is built on OSX // frameworks so we aim compile to match the OSX objc runtime. @VisibleForTesting static final ImmutableList SIMULATOR_COMPILE_FLAGS = ImmutableList.of( "-fexceptions", "-fasm-blocks", "-fobjc-abi-version=2", "-fobjc-legacy-dispatch"); /** * Frameworks implicitly linked to iOS, watchOS, and tvOS binaries when using legacy compilation. */ @VisibleForTesting static final ImmutableList AUTOMATIC_SDK_FRAMEWORKS = ImmutableList.of(new SdkFramework("Foundation"), new SdkFramework("UIKit")); private static final String FRAMEWORK_SUFFIX = ".framework"; /** Selects cc libraries that have alwayslink=1. */ private static final Predicate ALWAYS_LINKED_CC_LIBRARY = input -> LINK_LIBRARY_FILETYPES.matches(input.getFilename()); private static final String OBJC_MODULE_FEATURE_NAME = "use_objc_modules"; private static final String NO_ENABLE_MODULES_FEATURE_NAME = "no_enable_modules"; private static final String DEAD_STRIP_FEATURE_NAME = "dead_strip"; /** * Enabled if this target's rule is not a test rule. Binary stripping should not be applied in the * link step. TODO(b/36562173): Replace this behavior with a condition on bundle creation. * *

Note that the crosstool does not support feature negation in FlagSet.with_feature, which is * the mechanism used to condition linker arguments here. Therefore, we expose * "is_not_test_target" instead of the more intuitive "is_test_target". */ private static final String IS_NOT_TEST_TARGET_FEATURE_NAME = "is_not_test_target"; /** Enabled if this target generates debug symbols in a dSYM file. */ private static final String GENERATE_DSYM_FILE_FEATURE_NAME = "generate_dsym_file"; /** Always enabled to simplify dSYMs handling for newer Bazel versions. */ private static final String NO_DSYM_ZIPS_FEATURE_NAME = "no_dsym_create_zip"; /** * Enabled if this target does not generate debug symbols. * *

Note that the crosstool does not support feature negation in FlagSet.with_feature, which is * the mechanism used to condition linker arguments here. Therefore, we expose * "no_generate_debug_symbols" in addition to "generate_dsym_file" */ private static final String NO_GENERATE_DEBUG_SYMBOLS_FEATURE_NAME = "no_generate_debug_symbols"; private static final String GENERATE_LINKMAP_FEATURE_NAME = "generate_linkmap"; private static final String XCODE_VERSION_FEATURE_NAME_PREFIX = "xcode_"; /** Enabled if this target has objc sources in its transitive closure. */ private static final String CONTAINS_OBJC = "contains_objc_sources"; private static final ImmutableList ACTIVATED_ACTIONS = ImmutableList.of( "objc-compile", "objc++-compile", "objc-archive", "objc-fully-link", "objc-executable", "objc++-executable", "assemble", "preprocess-assemble", "c-compile", "c++-compile"); /** * Returns the location of the xcrunwrapper tool. */ public static final FilesToRunProvider xcrunwrapper(RuleContext ruleContext) { return ruleContext.getExecutablePrerequisite("$xcrunwrapper", Mode.HOST); } /** * Returns the location of the libtool tool. */ public static final FilesToRunProvider libtool(RuleContext ruleContext) { return ruleContext.getExecutablePrerequisite(ObjcRuleClasses.LIBTOOL_ATTRIBUTE, Mode.HOST); } /** * Files which can be instrumented along with the attributes in which they may occur and the * attributes along which they are propagated from dependencies (via * {@link InstrumentedFilesProvider}). */ private static final InstrumentationSpec INSTRUMENTATION_SPEC = new InstrumentationSpec( FileTypeSet.of( ObjcRuleClasses.NON_CPP_SOURCES, ObjcRuleClasses.CPP_SOURCES, HEADERS)) .withSourceAttributes("srcs", "non_arc_srcs", "hdrs") .withDependencyAttributes("deps", "data", "binary", "xctest_app"); /** * Defines a library that contains the transitive closure of dependencies. */ public static final SafeImplicitOutputsFunction FULLY_LINKED_LIB = fromTemplates("%{name}_fully_linked.a"); private IncludeProcessing createIncludeProcessing( Iterable privateHdrs, ObjcProvider objcProvider, @Nullable Artifact pchHdr) { if (isHeaderThinningEnabled()) { Iterable potentialInputs = Iterables.concat( privateHdrs, objcProvider.get(HEADER), objcProvider.get(STATIC_FRAMEWORK_FILE), objcProvider.get(DYNAMIC_FRAMEWORK_FILE)); if (pchHdr != null) { potentialInputs = Iterables.concat(potentialInputs, ImmutableList.of(pchHdr)); } return new HeaderThinning(potentialInputs); } else { return NoProcessing.INSTANCE; } } private CompilationInfo compile( ObjcProvider objcProvider, VariablesExtension extension, ExtraCompileArgs extraCompileArgs, CcToolchainProvider ccToolchain, FdoSupportProvider fdoSupport, Iterable priorityHeaders, PrecompiledFiles precompiledFiles, Collection sources, Collection privateHdrs, Collection publicHdrs, Artifact pchHdr, // TODO(b/70777494): Find out how deps get used and remove if not needed. Iterable deps, ObjcCppSemantics semantics, String purpose) throws RuleErrorException, InterruptedException { CcCompilationHelper result = new CcCompilationHelper( ruleContext, semantics, getFeatureConfiguration(ruleContext, ccToolchain, buildConfiguration, objcProvider), CcCompilationHelper.SourceCategory.CC_AND_OBJC, ccToolchain, fdoSupport, buildConfiguration) .addSources(sources) .addPrivateHeaders(privateHdrs) .addDefines(objcProvider.get(DEFINE)) .enableCompileProviders() .addPublicHeaders(publicHdrs) .addPrecompiledFiles(precompiledFiles) .addDeps(deps) // Not all our dependencies need to export cpp information. // For example, objc_proto_library can depend on a proto_library rule that does not // generate C++ protos. .setCheckDepsGenerateCpp(false) .setCopts( ImmutableList.builder() .addAll(getCompileRuleCopts()) .addAll( ruleContext .getFragment(ObjcConfiguration.class) .getCoptsForCompilationMode()) .addAll(extraCompileArgs) .build()) .addIncludeDirs(priorityHeaders) .addIncludeDirs(objcProvider.get(INCLUDE)) .addSystemIncludeDirs(objcProvider.get(INCLUDE_SYSTEM)) .setCppModuleMap(intermediateArtifacts.moduleMap()) .setPropagateModuleMapToCompileAction(false) .addVariableExtension(extension) .setPurpose(purpose); if (pchHdr != null) { result.addNonModuleMapHeader(pchHdr); } if (!useDeps) { result.doNotUseDeps(); } if (getCustomModuleMap(ruleContext).isPresent()) { result.doNotGenerateModuleMap(); } return result.compile(); } private Pair>> ccCompileAndLink( ObjcProvider objcProvider, CompilationArtifacts compilationArtifacts, ObjcVariablesExtension.Builder extensionBuilder, ExtraCompileArgs extraCompileArgs, CcToolchainProvider ccToolchain, FdoSupportProvider fdoSupport, Iterable priorityHeaders, LinkTargetType linkType, Artifact linkActionInput) throws RuleErrorException, InterruptedException { PrecompiledFiles precompiledFiles = new PrecompiledFiles(ruleContext); Collection arcSources = ImmutableSortedSet.copyOf(compilationArtifacts.getSrcs()); Collection nonArcSources = ImmutableSortedSet.copyOf(compilationArtifacts.getNonArcSrcs()); Collection privateHdrs = ImmutableSortedSet.copyOf(compilationArtifacts.getPrivateHdrs()); Collection publicHdrs = Stream.concat( Streams.stream(attributes.hdrs()), Streams.stream(compilationArtifacts.getAdditionalHdrs())) .collect(toImmutableSortedSet(naturalOrder())); Artifact pchHdr = getPchFile().orNull(); Iterable deps = ruleContext.getPrerequisites("deps", Mode.TARGET); ObjcCppSemantics semantics = createObjcCppSemantics(objcProvider, privateHdrs, pchHdr); String purpose = String.format("%s_objc_arc", semantics.getPurpose()); extensionBuilder.setArcEnabled(true); CompilationInfo objcArcCompilationInfo = compile( objcProvider, extensionBuilder.build(), extraCompileArgs, ccToolchain, fdoSupport, priorityHeaders, precompiledFiles, arcSources, privateHdrs, publicHdrs, pchHdr, deps, semantics, purpose); purpose = String.format("%s_non_objc_arc", semantics.getPurpose()); extensionBuilder.setArcEnabled(false); CompilationInfo nonObjcArcCompilationInfo = compile( objcProvider, extensionBuilder.build(), extraCompileArgs, ccToolchain, fdoSupport, priorityHeaders, precompiledFiles, nonArcSources, privateHdrs, publicHdrs, pchHdr, deps, semantics, purpose); CcLinkingHelper resultLink = new CcLinkingHelper( ruleContext, semantics, getFeatureConfiguration(ruleContext, ccToolchain, buildConfiguration, objcProvider), ccToolchain, fdoSupport, buildConfiguration) .addDeps(ruleContext.getPrerequisites("deps", Mode.TARGET)) .setLinkedArtifactNameSuffix(intermediateArtifacts.archiveFileNameSuffix()) .setNeverLink(true) .setCheckDepsGenerateCpp(false) .addVariableExtension(extensionBuilder.build()); if (linkType != null) { resultLink.setStaticLinkType(linkType); } if (linkActionInput != null) { resultLink.addLinkActionInput(linkActionInput); } CcCompilationContext.Builder ccCompilationContextBuilder = new CcCompilationContext.Builder(ruleContext); ccCompilationContextBuilder.mergeDependentCcCompilationContexts( Arrays.asList( objcArcCompilationInfo.getCcCompilationContext(), nonObjcArcCompilationInfo.getCcCompilationContext())); ccCompilationContextBuilder.setPurpose( String.format("%s_merged_arc_non_arc_objc", semantics.getPurpose())); semantics.setupCcCompilationContext(ruleContext, ccCompilationContextBuilder); CcCompilationOutputs.Builder compilationOutputsBuilder = new CcCompilationOutputs.Builder(); compilationOutputsBuilder.merge(objcArcCompilationInfo.getCcCompilationOutputs()); compilationOutputsBuilder.merge(nonObjcArcCompilationInfo.getCcCompilationOutputs()); LinkingInfo linkingInfo = resultLink.link(compilationOutputsBuilder.build(), ccCompilationContextBuilder.build()); Map> mergedOutputGroups = CcCommon.mergeOutputGroups( ImmutableList.of( objcArcCompilationInfo.getOutputGroups(), nonObjcArcCompilationInfo.getOutputGroups(), linkingInfo.getOutputGroups())); return new Pair<>(compilationOutputsBuilder.build(), ImmutableMap.copyOf(mergedOutputGroups)); } private ObjcCppSemantics createObjcCppSemantics( ObjcProvider objcProvider, Collection privateHdrs, Artifact pchHdr) { return new ObjcCppSemantics( objcProvider, createIncludeProcessing(privateHdrs, objcProvider, pchHdr), ruleContext.getFragment(ObjcConfiguration.class), isHeaderThinningEnabled(), intermediateArtifacts, buildConfiguration); } private FeatureConfiguration getFeatureConfiguration( RuleContext ruleContext, CcToolchainProvider ccToolchain, BuildConfiguration configuration, ObjcProvider objcProvider) { boolean isHost = ruleContext.getConfiguration().isHostConfiguration(); ImmutableSet.Builder activatedCrosstoolSelectables = ImmutableSet.builder() .addAll(ccToolchain.getFeatures().getDefaultFeaturesAndActionConfigs()) .addAll(ACTIVATED_ACTIONS) .addAll( ruleContext .getFragment(AppleConfiguration.class) .getBitcodeMode() .getFeatureNames()) // We create a module map by default to allow for Swift interop. .add(CppRuleClasses.MODULE_MAPS) .add(CppRuleClasses.COMPILE_ALL_MODULES) .add(CppRuleClasses.EXCLUDE_PRIVATE_HEADERS_IN_MODULE_MAPS) .add(CppRuleClasses.ONLY_DOTH_HEADERS_IN_MODULE_MAPS) .add(CppRuleClasses.DEPENDENCY_FILE) .add(CppRuleClasses.INCLUDE_PATHS) .add(isHost ? "host" : "nonhost") .add(configuration.getCompilationMode().toString()); if (configuration.getFragment(ObjcConfiguration.class).moduleMapsEnabled() && !getCustomModuleMap(ruleContext).isPresent()) { activatedCrosstoolSelectables.add(OBJC_MODULE_FEATURE_NAME); } if (!CompilationAttributes.Builder.fromRuleContext(ruleContext).build().enableModules()) { activatedCrosstoolSelectables.add(NO_ENABLE_MODULES_FEATURE_NAME); } if (configuration.getFragment(ObjcConfiguration.class).shouldStripBinary()) { activatedCrosstoolSelectables.add(DEAD_STRIP_FEATURE_NAME); } if (getPchFile().isPresent()) { activatedCrosstoolSelectables.add("pch"); } if (!isTestRule) { activatedCrosstoolSelectables.add(IS_NOT_TEST_TARGET_FEATURE_NAME); } if (configuration.getFragment(ObjcConfiguration.class).generateDsym()) { activatedCrosstoolSelectables.add(GENERATE_DSYM_FILE_FEATURE_NAME); } else { activatedCrosstoolSelectables.add(NO_GENERATE_DEBUG_SYMBOLS_FEATURE_NAME); } if (configuration.getFragment(ObjcConfiguration.class).generateLinkmap()) { activatedCrosstoolSelectables.add(GENERATE_LINKMAP_FEATURE_NAME); } AppleBitcodeMode bitcodeMode = configuration.getFragment(AppleConfiguration.class).getBitcodeMode(); if (bitcodeMode != AppleBitcodeMode.NONE) { activatedCrosstoolSelectables.addAll(bitcodeMode.getFeatureNames()); } if (objcProvider.is(Flag.USES_OBJC)) { activatedCrosstoolSelectables.add(CONTAINS_OBJC); } if (toolchain.useFission()) { activatedCrosstoolSelectables.add(CppRuleClasses.PER_OBJECT_DEBUG_INFO); } // TODO(b/111205462): Remove this feature from here, CROSSTOOL, and wrapped_clang after the // next release. activatedCrosstoolSelectables.add(NO_DSYM_ZIPS_FEATURE_NAME); activatedCrosstoolSelectables.add(XCODE_VERSION_FEATURE_NAME_PREFIX + XcodeConfig.getXcodeVersion(ruleContext).toStringWithMinimumComponents(2)); activatedCrosstoolSelectables.addAll(ruleContext.getFeatures()); activatedCrosstoolSelectables.addAll(CcCommon.getCoverageFeatures(toolchain)); try { return ccToolchain .getFeatures() .getFeatureConfiguration(activatedCrosstoolSelectables.build()); } catch (CollidingProvidesException e) { ruleContext.ruleError(e.getMessage()); return FeatureConfiguration.EMPTY; } } /** * Iterable wrapper providing strong type safety for arguments to binary linking. */ static final class ExtraLinkArgs extends IterableWrapper { ExtraLinkArgs(String... args) { super(args); } ExtraLinkArgs(Iterable args) { super(args); } } /** * Iterable wrapper providing strong type safety for extra compile flags. */ static final class ExtraCompileArgs extends IterableWrapper { static final ExtraCompileArgs NONE = new ExtraCompileArgs(); ExtraCompileArgs(String... args) { super(args); } } @VisibleForTesting static final String FILE_IN_SRCS_AND_HDRS_WARNING_FORMAT = "File '%s' is in both srcs and hdrs."; @VisibleForTesting static final String FILE_IN_SRCS_AND_NON_ARC_SRCS_ERROR_FORMAT = "File '%s' is present in both srcs and non_arc_srcs which is forbidden."; static final ImmutableList DEFAULT_COMPILER_FLAGS = ImmutableList.of("-DOS_IOS"); static final ImmutableList DEFAULT_LINKER_FLAGS = ImmutableList.of("-ObjC"); /** * Set of {@link com.google.devtools.build.lib.util.FileType} of source artifacts that are * compatible with header thinning. */ private static final FileTypeSet SOURCES_FOR_HEADER_THINNING = FileTypeSet.of( CppFileTypes.OBJC_SOURCE, CppFileTypes.OBJCPP_SOURCE, CppFileTypes.CPP_SOURCE, CppFileTypes.C_SOURCE); /** * Returns information about the given rule's compilation artifacts. */ // TODO(bazel-team): Remove this information from ObjcCommon and move it internal to this class. static CompilationArtifacts compilationArtifacts(RuleContext ruleContext) { return compilationArtifacts(ruleContext, ObjcRuleClasses.intermediateArtifacts(ruleContext)); } /** * 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) .build(); } /** Returns a list of frameworks for clang actions. */ static Iterable commonFrameworkNames( ObjcProvider provider, RuleContext ruleContext, ApplePlatform platform) { ImmutableList.Builder frameworkNames = new ImmutableList.Builder() .add(AppleToolchain.sdkFrameworkDir(platform, ruleContext)); // As of sdk8.1, XCTest is in a base Framework dir. if (platform.getType() != PlatformType.WATCHOS) { // WatchOS does not have this directory. frameworkNames.add(AppleToolchain.platformDeveloperFrameworkDir(platform)); } return frameworkNames // Add custom (non-SDK) framework search paths. For each framework foo/bar.framework, // include "foo" as a search path. .addAll( Iterables.transform( uniqueParentDirectories(provider.getStaticFrameworkDirs()), PathFragment::getSafePathString)) .addAll( Iterables.transform( uniqueParentDirectories(provider.get(DYNAMIC_FRAMEWORK_DIR)), PathFragment::getSafePathString)) .addAll( Iterables.transform( uniqueParentDirectories(provider.get(FRAMEWORK_SEARCH_PATH_ONLY)), PathFragment::getSafePathString)) .build(); } private final RuleContext ruleContext; private final BuildConfiguration buildConfiguration; private final ObjcConfiguration objcConfiguration; private final AppleConfiguration appleConfiguration; private final CompilationAttributes attributes; private final IntermediateArtifacts intermediateArtifacts; private final boolean useDeps; private final Map> outputGroupCollector; private final ImmutableList.Builder objectFilesCollector; private final CcToolchainProvider toolchain; private final boolean isTestRule; private final boolean usePch; /** * Creates a new compilation support for the given rule and build configuration. * *

All actions will be created under the given build configuration, which may be different than * the current rule context configuration. * *

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. * *

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. */ private CompilationSupport( RuleContext ruleContext, BuildConfiguration buildConfiguration, IntermediateArtifacts intermediateArtifacts, CompilationAttributes compilationAttributes, boolean useDeps, Map> outputGroupCollector, ImmutableList.Builder objectFilesCollector, CcToolchainProvider toolchain, boolean isTestRule, boolean usePch) { this.ruleContext = ruleContext; this.buildConfiguration = buildConfiguration; this.objcConfiguration = buildConfiguration.getFragment(ObjcConfiguration.class); this.appleConfiguration = buildConfiguration.getFragment(AppleConfiguration.class); this.attributes = compilationAttributes; this.intermediateArtifacts = intermediateArtifacts; this.useDeps = useDeps; this.isTestRule = isTestRule; this.outputGroupCollector = outputGroupCollector; this.objectFilesCollector = objectFilesCollector; this.usePch = usePch; // TODO(b/62143697): Remove this check once all rules are using the crosstool support. if (ruleContext .attributes() .has(CcToolchain.CC_TOOLCHAIN_DEFAULT_ATTRIBUTE_NAME, BuildType.LABEL) || ruleContext.attributes().has(":j2objc_cc_toolchain", BuildType.LABEL)) { if (toolchain == null) { toolchain = CppHelper.getToolchainUsingDefaultCcToolchainAttribute(ruleContext); } this.toolchain = toolchain; } else { // Since the rule context doesn't have a toolchain at all, ignore any provided override. this.toolchain = null; } } /** Builder for {@link CompilationSupport} */ public static class Builder { private RuleContext ruleContext; private BuildConfiguration buildConfiguration; private IntermediateArtifacts intermediateArtifacts; private CompilationAttributes compilationAttributes; private boolean useDeps = true; private Map> outputGroupCollector; private ImmutableList.Builder objectFilesCollector; private CcToolchainProvider toolchain; private boolean isTestRule = false; private boolean usePch = true; /** Sets the {@link RuleContext} for the calling target. */ public Builder setRuleContext(RuleContext ruleContext) { this.ruleContext = ruleContext; return this; } /** Sets the {@link BuildConfiguration} for the calling target. */ public Builder setConfig(BuildConfiguration buildConfiguration) { this.buildConfiguration = buildConfiguration; return this; } /** Sets {@link IntermediateArtifacts} for deriving artifact paths. */ public Builder setIntermediateArtifacts(IntermediateArtifacts intermediateArtifacts) { this.intermediateArtifacts = intermediateArtifacts; return this; } /** Sets {@link CompilationAttributes} for the calling target. */ public Builder setCompilationAttributes(CompilationAttributes compilationAttributes) { this.compilationAttributes = compilationAttributes; return this; } /** * Sets that this {@link CompilationSupport} will not take deps into account in determining * compilation actions. */ public Builder doNotUseDeps() { this.useDeps = false; return this; } /** * Sets that this {@link CompilationSupport} will not use the pch from the rule context in * determining compilation actions. */ public Builder doNotUsePch() { this.usePch = false; return this; } /** * Indicates that this CompilationSupport is for use in a test rule. */ public Builder setIsTestRule() { this.isTestRule = true; return this; } /** * Causes the provided map to be updated with output groups produced by compile action * registration. * *

This map is intended to be mutated by {@link * CompilationSupport#registerCompileAndArchiveActions}. The added output groups should be * exported by the calling rule class implementation. */ public Builder setOutputGroupCollector(Map> outputGroupCollector) { this.outputGroupCollector = outputGroupCollector; return this; } /** * Set a collector for the object files produced by compile action registration. * *

The object files are intended to be added by {@link * CompilationSupport#registerCompileAndArchiveActions}. */ public Builder setObjectFilesCollector(ImmutableList.Builder objectFilesCollector) { this.objectFilesCollector = objectFilesCollector; return this; } /** * Sets {@link CcToolchainProvider} for the calling target. * *

This is needed if it can't correctly be inferred directly from the rule context. Setting * to null causes the default to be used as if this was never called. */ public Builder setToolchainProvider(CcToolchainProvider toolchain) { this.toolchain = toolchain; return this; } /** Returns a {@link CompilationSupport} instance. */ public CompilationSupport build() { Preconditions.checkNotNull(ruleContext, "CompilationSupport is missing RuleContext"); if (buildConfiguration == null) { buildConfiguration = ruleContext.getConfiguration(); } if (intermediateArtifacts == null) { intermediateArtifacts = ObjcRuleClasses.intermediateArtifacts(ruleContext, buildConfiguration); } if (compilationAttributes == null) { compilationAttributes = CompilationAttributes.Builder.fromRuleContext(ruleContext).build(); } if (outputGroupCollector == null) { outputGroupCollector = new TreeMap<>(); } if (objectFilesCollector == null) { objectFilesCollector = ImmutableList.builder(); } return new CompilationSupport( ruleContext, buildConfiguration, intermediateArtifacts, compilationAttributes, useDeps, outputGroupCollector, objectFilesCollector, toolchain, isTestRule, usePch); } } /** * Returns a provider that collects this target's instrumented sources as well as those of its * dependencies. * * @param objectFiles the object files generated by this target * @return an instrumented files provider */ public InstrumentedFilesProvider getInstrumentedFilesProvider( ImmutableList objectFiles) { return InstrumentedFilesCollector.collect( ruleContext, INSTRUMENTATION_SPEC, new ObjcCoverageMetadataCollector(), objectFiles, NestedSetBuilder.emptySet(Order.STABLE_ORDER), // The COVERAGE_GCOV_PATH environment variable is added in TestSupport#getExtraProviders() NestedSetBuilder.>emptySet(Order.COMPILE_ORDER), !isTestRule); } /** * Validates compilation-related attributes on this rule. * * @return this compilation support * @throws RuleErrorException if there are attribute errors */ CompilationSupport validateAttributes() throws RuleErrorException { for (PathFragment absoluteInclude : Iterables.filter(attributes.includes(), PathFragment::isAbsolute)) { ruleContext.attributeError( "includes", String.format(ABSOLUTE_INCLUDES_PATH_FORMAT, absoluteInclude)); } if (ruleContext.attributes().has("srcs", BuildType.LABEL_LIST)) { ImmutableSet hdrsSet = ImmutableSet.copyOf(attributes.hdrs()); ImmutableSet srcsSet = ImmutableSet.copyOf(ruleContext.getPrerequisiteArtifacts("srcs", Mode.TARGET).list()); // Check for overlap between srcs and hdrs. for (Artifact header : Sets.intersection(hdrsSet, srcsSet)) { String path = header.getRootRelativePath().toString(); ruleContext.attributeWarning( "srcs", String.format(FILE_IN_SRCS_AND_HDRS_WARNING_FORMAT, path)); } // Check for overlap between srcs and non_arc_srcs. ImmutableSet nonArcSrcsSet = ImmutableSet.copyOf( ruleContext.getPrerequisiteArtifacts("non_arc_srcs", Mode.TARGET).list()); for (Artifact conflict : Sets.intersection(nonArcSrcsSet, srcsSet)) { String path = conflict.getRootRelativePath().toString(); ruleContext.attributeError( "srcs", String.format(FILE_IN_SRCS_AND_NON_ARC_SRCS_ERROR_FORMAT, path)); } } ruleContext.assertNoErrors(); return this; } /** * Registers all actions necessary to compile this rule's sources and archive them. * * @param compilationArtifacts collection of artifacts required for the compilation * @param objcProvider provides all compiling and linking information to register these actions * @param toolchain the toolchain to be used in determining command lines * @return this compilation support * @throws RuleErrorException for invalid crosstool files */ CompilationSupport registerCompileAndArchiveActions( CompilationArtifacts compilationArtifacts, ObjcProvider objcProvider, CcToolchainProvider toolchain) throws RuleErrorException, InterruptedException { return registerCompileAndArchiveActions( compilationArtifacts, objcProvider, ExtraCompileArgs.NONE, ImmutableList.of(), toolchain, maybeGetFdoSupport()); } /** * Registers all actions necessary to compile this rule's sources and archive them. * * @param common common information about this rule and its dependencies * @return this compilation support * @throws RuleErrorException for invalid crosstool files */ CompilationSupport registerCompileAndArchiveActions(ObjcCommon common) throws RuleErrorException, InterruptedException { return registerCompileAndArchiveActions( common, ExtraCompileArgs.NONE, ImmutableList.of()); } /** * Registers all actions necessary to compile this rule's sources and archive them. * * @param common common information about this rule and its dependencies * @param priorityHeaders priority headers to be included before the dependency headers * @return this compilation support * @throws RuleErrorException for invalid crosstool files */ CompilationSupport registerCompileAndArchiveActions( ObjcCommon common, Iterable priorityHeaders) throws RuleErrorException, InterruptedException { return registerCompileAndArchiveActions(common, ExtraCompileArgs.NONE, priorityHeaders); } /** * Registers all actions necessary to compile this rule's sources and archive them. * * @param compilationArtifacts collection of artifacts required for the compilation * @param objcProvider provides all compiling and linking information to register these actions * @param extraCompileArgs args to be added to compile actions * @param priorityHeaders priority headers to be included before the dependency headers * @param ccToolchain the cpp toolchain provider, may be null * @param fdoSupport the cpp FDO support provider, may be null * @return this compilation support * @throws RuleErrorException for invalid crosstool files */ CompilationSupport registerCompileAndArchiveActions( CompilationArtifacts compilationArtifacts, ObjcProvider objcProvider, ExtraCompileArgs extraCompileArgs, Iterable priorityHeaders, @Nullable CcToolchainProvider ccToolchain, @Nullable FdoSupportProvider fdoSupport) throws RuleErrorException, InterruptedException { Preconditions.checkNotNull(ccToolchain); Preconditions.checkNotNull(fdoSupport); ObjcVariablesExtension.Builder extension = new ObjcVariablesExtension.Builder() .setRuleContext(ruleContext) .setObjcProvider(objcProvider) .setCompilationArtifacts(compilationArtifacts) .setIntermediateArtifacts(intermediateArtifacts) .setConfiguration(buildConfiguration); Pair>> compilationInfo; if (compilationArtifacts.getArchive().isPresent()) { Artifact objList = intermediateArtifacts.archiveObjList(); extension.addVariableCategory(VariableCategory.ARCHIVE_VARIABLES); compilationInfo = ccCompileAndLink( objcProvider, compilationArtifacts, extension, extraCompileArgs, ccToolchain, fdoSupport, priorityHeaders, LinkTargetType.OBJC_ARCHIVE, objList); // TODO(b/30783125): Signal the need for this action in the CROSSTOOL. registerObjFilelistAction( compilationInfo.getFirst().getObjectFiles(/* usePic= */ false), objList); } else { compilationInfo = ccCompileAndLink( objcProvider, compilationArtifacts, extension, extraCompileArgs, ccToolchain, fdoSupport, priorityHeaders, /* linkType */ null, /* linkActionInput */ null); } objectFilesCollector.addAll(compilationInfo.getFirst().getObjectFiles(/* usePic= */ false)); outputGroupCollector.putAll(compilationInfo.getSecond()); registerHeaderScanningActions(compilationInfo.getFirst(), objcProvider, compilationArtifacts); return this; } /** * Registers all actions necessary to compile this rule's sources and archive them. * * @param common common information about this rule and its dependencies * @param extraCompileArgs args to be added to compile actions * @param priorityHeaders priority headers to be included before the dependency headers * @return this compilation support * @throws RuleErrorException for invalid crosstool files */ CompilationSupport registerCompileAndArchiveActions( ObjcCommon common, ExtraCompileArgs extraCompileArgs, Iterable priorityHeaders) throws RuleErrorException, InterruptedException { if (common.getCompilationArtifacts().isPresent()) { registerCompileAndArchiveActions( common.getCompilationArtifacts().get(), common.getObjcProvider(), extraCompileArgs, priorityHeaders, toolchain, maybeGetFdoSupport()); } return this; } private StrippingType getStrippingType(ExtraLinkArgs extraLinkArgs) { return Iterables.contains(extraLinkArgs, "-dynamiclib") ? StrippingType.DYNAMIC_LIB : StrippingType.DEFAULT; } /** * Registers any actions necessary to link this rule and its dependencies. Automatically infers * the toolchain from the configuration of this CompilationSupport - if a different toolchain is * required, use the custom toolchain override. * *

Dsym bundle is generated if {@link ObjcConfiguration#generateDsym()} is set. * *

When Bazel flags {@code --compilation_mode=opt} and {@code --objc_enable_binary_stripping} * are specified, additional optimizations will be performed on the linked binary: all-symbol * stripping (using {@code /usr/bin/strip}) and dead-code stripping (using linker flags: {@code * -dead_strip} and {@code -no_dead_strip_inits_and_terms}). * * @param objcProvider common information about this rule's attributes and its dependencies * @param j2ObjcMappingFileProvider contains mapping files for j2objc transpilation * @param j2ObjcEntryClassProvider contains j2objc entry class information for dead code removal * @param extraLinkArgs any additional arguments to pass to the linker * @param extraLinkInputs any additional input artifacts to pass to the link action * @return this compilation support */ CompilationSupport registerLinkActions( ObjcProvider objcProvider, J2ObjcMappingFileProvider j2ObjcMappingFileProvider, J2ObjcEntryClassProvider j2ObjcEntryClassProvider, ExtraLinkArgs extraLinkArgs, Iterable extraLinkInputs, CcToolchainProvider toolchain) throws InterruptedException { Iterable prunedJ2ObjcArchives = computeAndStripPrunedJ2ObjcArchives( j2ObjcEntryClassProvider, j2ObjcMappingFileProvider, objcProvider); ImmutableList bazelBuiltLibraries = Iterables.isEmpty(prunedJ2ObjcArchives) ? objcProvider.getObjcLibraries() : substituteJ2ObjcPrunedLibraries(objcProvider); Artifact inputFileList = intermediateArtifacts.linkerObjList(); ImmutableSet forceLinkArtifacts = getForceLoadArtifacts(objcProvider); Iterable objFiles = Iterables.concat( bazelBuiltLibraries, objcProvider.get(IMPORTED_LIBRARY), objcProvider.getCcLibraries()); // 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); LinkTargetType linkType = (objcProvider.is(Flag.USES_CPP)) ? LinkTargetType.OBJCPP_EXECUTABLE : LinkTargetType.OBJC_EXECUTABLE; ObjcVariablesExtension.Builder extensionBuilder = new ObjcVariablesExtension.Builder() .setRuleContext(ruleContext) .setObjcProvider(objcProvider) .setConfiguration(buildConfiguration) .setIntermediateArtifacts(intermediateArtifacts) .setFrameworkNames(frameworkNames(objcProvider)) .setLibraryNames(libraryNames(objcProvider)) .setForceLoadArtifacts(getForceLoadArtifacts(objcProvider)) .setAttributeLinkopts(attributes.linkopts()) .addVariableCategory(VariableCategory.EXECUTABLE_LINKING_VARIABLES); Artifact binaryToLink = getBinaryToLink(); FdoSupportProvider fdoSupport = CppHelper.getFdoSupportUsingDefaultCcToolchainAttribute(ruleContext); CppLinkActionBuilder executableLinkAction = new CppLinkActionBuilder( ruleContext, binaryToLink, toolchain, fdoSupport, getFeatureConfiguration(ruleContext, toolchain, buildConfiguration, objcProvider), createObjcCppSemantics( objcProvider, /* privateHdrs= */ ImmutableList.of(), /* pchHdr= */ null)) .setMnemonic("ObjcLink") .addActionInputs(bazelBuiltLibraries) .addActionInputs(objcProvider.getCcLibraries()) .addTransitiveActionInputs(objcProvider.get(IMPORTED_LIBRARY)) .addTransitiveActionInputs(objcProvider.get(STATIC_FRAMEWORK_FILE)) .addTransitiveActionInputs(objcProvider.get(DYNAMIC_FRAMEWORK_FILE)) .addTransitiveActionInputs(objcProvider.get(LINK_INPUTS)) .setCrosstoolInputs(toolchain.getLink()) .addActionInputs(prunedJ2ObjcArchives) .addActionInputs(extraLinkInputs) .addActionInput(inputFileList) .setLinkType(linkType) .setLinkingMode(LinkingMode.LEGACY_FULLY_STATIC) .addLinkopts(ImmutableList.copyOf(extraLinkArgs)); if (objcConfiguration.generateDsym()) { Artifact dsymSymbol = objcConfiguration.shouldStripBinary() ? intermediateArtifacts.dsymSymbolForUnstrippedBinary() : intermediateArtifacts.dsymSymbolForStrippedBinary(); extensionBuilder .setDsymSymbol(dsymSymbol) .addVariableCategory(VariableCategory.DSYM_VARIABLES); executableLinkAction.addActionOutput(dsymSymbol); } if (objcConfiguration.generateLinkmap()) { Artifact linkmap = intermediateArtifacts.linkmap(); extensionBuilder.setLinkmap(linkmap).addVariableCategory(VariableCategory.LINKMAP_VARIABLES); executableLinkAction.addActionOutput(linkmap); } if (appleConfiguration.getBitcodeMode() == AppleBitcodeMode.EMBEDDED) { Artifact bitcodeSymbolMap = intermediateArtifacts.bitcodeSymbolMap(); extensionBuilder .setBitcodeSymbolMap(bitcodeSymbolMap) .addVariableCategory(VariableCategory.BITCODE_VARIABLES); executableLinkAction.addActionOutput(bitcodeSymbolMap); } executableLinkAction.addVariablesExtension(extensionBuilder.build()); ruleContext.registerAction(executableLinkAction.build()); if (objcConfiguration.shouldStripBinary()) { registerBinaryStripAction(binaryToLink, getStrippingType(extraLinkArgs)); } return this; } /** * Returns the copts for the compile action in the current rule context (using a combination of * the rule's "copts" attribute as well as the current configuration copts). */ private Iterable getCompileRuleCopts() { List copts = Stream.concat(objcConfiguration.getCopts().stream(), attributes.copts().stream()) .collect(toCollection(ArrayList::new)); for (String copt : copts) { if (copt.contains("-fmodules-cache-path")) { // Bazel decides on the cache path location. ruleContext.ruleWarning(MODULES_CACHE_PATH_WARNING); } } if (attributes.enableModules() && !getCustomModuleMap(ruleContext).isPresent()) { copts.add("-fmodules"); } if (copts.contains("-fmodules")) { // If modules are enabled, clang caches module information. If unspecified, this is a // system-wide cache directory, which is a problem for remote executors which may run // multiple actions with different source trees that can't share this cache. // We thus set its path to the root of the genfiles directory. // Unfortunately, this cache contains non-hermetic information, thus we avoid declaring it as // an implicit output (as outputs must be hermetic). String cachePath = buildConfiguration.getGenfilesFragment() + "/" + OBJC_MODULE_CACHE_DIR_NAME; copts.add("-fmodules-cache-path=" + cachePath); } return copts; } /** * Registers an action that writes given set of object files to the given objList. This objList is * suitable to signal symbols to archive in a libtool archiving invocation. */ private CompilationSupport registerObjFilelistAction( Iterable objFiles, Artifact objList) { ImmutableSet dedupedObjFiles = ImmutableSet.copyOf(objFiles); CustomCommandLine.Builder objFilesToLinkParam = new CustomCommandLine.Builder(); ImmutableList.Builder treeObjFiles = new ImmutableList.Builder<>(); for (Artifact objFile : dedupedObjFiles) { // If the obj file is a tree artifact, we need to expand it into the contained individual // files properly. if (objFile.isTreeArtifact()) { treeObjFiles.add(objFile); objFilesToLinkParam.addExpandedTreeArtifactExecPaths(objFile); } else { objFilesToLinkParam.addPath(objFile.getExecPath()); } } ruleContext.registerAction( new ParameterFileWriteAction( ruleContext.getActionOwner(), treeObjFiles.build(), objList, objFilesToLinkParam.build(), ParameterFile.ParameterFileType.UNQUOTED, ISO_8859_1)); return this; } /** * Registers an action to create an archive artifact by fully (statically) linking all transitive * dependencies of this rule. * * @param objcProvider provides all compiling and linking information to create this artifact * @param outputArchive the output artifact for this action */ public CompilationSupport registerFullyLinkAction( ObjcProvider objcProvider, Artifact outputArchive) throws InterruptedException { return registerFullyLinkAction(objcProvider, outputArchive, toolchain, maybeGetFdoSupport()); } /** * Registers an action to create an archive artifact by fully (statically) linking all transitive * dependencies of this rule. * * @param objcProvider provides all compiling and linking information to create this artifact * @param outputArchive the output artifact for this action * @param ccToolchain the cpp toolchain provider, may be null * @param fdoSupport the cpp FDO support provider, may be null * @return this {@link CompilationSupport} instance */ CompilationSupport registerFullyLinkAction( ObjcProvider objcProvider, Artifact outputArchive, @Nullable CcToolchainProvider ccToolchain, @Nullable FdoSupportProvider fdoSupport) throws InterruptedException { Preconditions.checkNotNull(ccToolchain); Preconditions.checkNotNull(fdoSupport); PathFragment labelName = PathFragment.create(ruleContext.getLabel().getName()); String libraryIdentifier = ruleContext .getPackageDirectory() .getRelative(labelName.replaceName("lib" + labelName.getBaseName())) .getPathString(); ObjcVariablesExtension extension = new ObjcVariablesExtension.Builder() .setRuleContext(ruleContext) .setObjcProvider(objcProvider) .setConfiguration(buildConfiguration) .setIntermediateArtifacts(intermediateArtifacts) .setFullyLinkArchive(outputArchive) .addVariableCategory(VariableCategory.FULLY_LINK_VARIABLES) .build(); CppLinkAction fullyLinkAction = new CppLinkActionBuilder( ruleContext, outputArchive, ccToolchain, fdoSupport, getFeatureConfiguration(ruleContext, ccToolchain, buildConfiguration, objcProvider), createObjcCppSemantics( objcProvider, /* privateHdrs= */ ImmutableList.of(), /* pchHdr= */ null)) .addActionInputs(objcProvider.getObjcLibraries()) .addActionInputs(objcProvider.getCcLibraries()) .addActionInputs(objcProvider.get(IMPORTED_LIBRARY).toSet()) .setCrosstoolInputs(ccToolchain.getLink()) .setLinkType(LinkTargetType.OBJC_FULLY_LINKED_ARCHIVE) .setLinkingMode(LinkingMode.LEGACY_FULLY_STATIC) .setLibraryIdentifier(libraryIdentifier) .addVariablesExtension(extension) .build(); ruleContext.registerAction(fullyLinkAction); return this; } /** * Returns all framework names to pass to the linker using {@code -framework} flags. For a * framework in the directory foo/bar.framework, the name is "bar". Each framework is found * without using the full path by means of the framework search paths. Search paths are added by * {@link#commonLinkAndCompileFlagsForClang(ObjcProvider, ObjcConfiguration, AppleConfiguration)}) * *

It's awful that we can't pass the full path to the framework and avoid framework search * paths, but this is imposed on us by clang. clang does not support passing the full path to the * framework, so Bazel cannot do it either. */ private Set frameworkNames(ObjcProvider provider) { Set names = new LinkedHashSet<>(); Iterables.addAll(names, SdkFramework.names(provider.get(SDK_FRAMEWORK))); for (PathFragment frameworkDir : Iterables.concat(provider.getStaticFrameworkDirs(), provider.get(DYNAMIC_FRAMEWORK_DIR))) { String segment = frameworkDir.getBaseName(); Preconditions.checkState( segment.endsWith(FRAMEWORK_SUFFIX), "expect %s to end with %s, but it does not", segment, FRAMEWORK_SUFFIX); names.add(segment.substring(0, segment.length() - FRAMEWORK_SUFFIX.length())); } return names; } /** Returns libraries that should be passed to the linker. */ private ImmutableList libraryNames(ObjcProvider objcProvider) { ImmutableList.Builder args = new ImmutableList.Builder<>(); for (String dylib : objcProvider.get(SDK_DYLIB)) { if (dylib.startsWith("lib")) { // remove lib prefix if it exists which is standard // for libraries (libxml.dylib -> -lxml). dylib = dylib.substring(3); } args.add(dylib); } return args.build(); } /** Returns libraries that should be passed into the linker with {@code -force_load}. */ private ImmutableSet getForceLoadArtifacts(ObjcProvider objcProvider) { ImmutableList ccLibraries = objcProvider.getCcLibraries(); Iterable ccLibrariesToForceLoad = Iterables.filter(ccLibraries, ALWAYS_LINKED_CC_LIBRARY); return ImmutableSet.builder() .addAll(objcProvider.get(FORCE_LOAD_LIBRARY)) .addAll(ccLibrariesToForceLoad) .build(); } /** Returns pruned J2Objc archives for this target. */ private ImmutableList j2objcPrunedLibraries(ObjcProvider objcProvider) { ImmutableList.Builder j2objcPrunedLibraryBuilder = ImmutableList.builder(); for (Artifact j2objcLibrary : objcProvider.get(ObjcProvider.J2OBJC_LIBRARY)) { j2objcPrunedLibraryBuilder.add(intermediateArtifacts.j2objcPrunedArchive(j2objcLibrary)); } return j2objcPrunedLibraryBuilder.build(); } /** Returns true if this build should strip J2Objc dead code. */ private boolean stripJ2ObjcDeadCode(J2ObjcEntryClassProvider j2ObjcEntryClassProvider) { J2ObjcConfiguration j2objcConfiguration = buildConfiguration.getFragment(J2ObjcConfiguration.class); // Only perform J2ObjC dead code stripping if flag --j2objc_dead_code_removal is specified and // users have specified entry classes. return j2objcConfiguration.removeDeadCode() && !j2ObjcEntryClassProvider.getEntryClasses().isEmpty(); } /** Registers actions to perform J2Objc dead code removal. */ private void registerJ2ObjcDeadCodeRemovalActions( ObjcProvider objcProvider, J2ObjcMappingFileProvider j2ObjcMappingFileProvider, J2ObjcEntryClassProvider j2ObjcEntryClassProvider) { NestedSet entryClasses = j2ObjcEntryClassProvider.getEntryClasses(); Artifact pruner = ruleContext.getPrerequisiteArtifact("$j2objc_dead_code_pruner", Mode.HOST); NestedSet j2ObjcDependencyMappingFiles = j2ObjcMappingFileProvider.getDependencyMappingFiles(); NestedSet j2ObjcHeaderMappingFiles = j2ObjcMappingFileProvider.getHeaderMappingFiles(); NestedSet j2ObjcArchiveSourceMappingFiles = j2ObjcMappingFileProvider.getArchiveSourceMappingFiles(); for (Artifact j2objcArchive : objcProvider.get(ObjcProvider.J2OBJC_LIBRARY)) { Artifact prunedJ2ObjcArchive = intermediateArtifacts.j2objcPrunedArchive(j2objcArchive); Artifact dummyArchive = Iterables.getOnlyElement( ruleContext .getPrerequisite("$dummy_lib", Mode.TARGET, ObjcProvider.SKYLARK_CONSTRUCTOR) .get(LIBRARY)); CustomCommandLine commandLine = CustomCommandLine.builder() .addExecPath("--input_archive", j2objcArchive) .addExecPath("--output_archive", prunedJ2ObjcArchive) .addExecPath("--dummy_archive", dummyArchive) .addExecPath("--xcrunwrapper", xcrunwrapper(ruleContext).getExecutable()) .addExecPaths( "--dependency_mapping_files", VectorArg.join(",").each(j2ObjcDependencyMappingFiles)) .addExecPaths( "--header_mapping_files", VectorArg.join(",").each(j2ObjcHeaderMappingFiles)) .addExecPaths( "--archive_source_mapping_files", VectorArg.join(",").each(j2ObjcArchiveSourceMappingFiles)) .add("--entry_classes") .addAll(VectorArg.join(",").each(entryClasses)) .build(); ruleContext.registerAction( ObjcRuleClasses.spawnAppleEnvActionBuilder( XcodeConfigProvider.fromRuleContext(ruleContext), appleConfiguration.getSingleArchPlatform()) .setMnemonic("DummyPruner") .setExecutable(pruner) .addInput(dummyArchive) .addInput(pruner) .addInput(j2objcArchive) .addInput(xcrunwrapper(ruleContext).getExecutable()) .addTransitiveInputs(j2ObjcDependencyMappingFiles) .addTransitiveInputs(j2ObjcHeaderMappingFiles) .addTransitiveInputs(j2ObjcArchiveSourceMappingFiles) .addCommandLine( commandLine, ParamFileInfo.builder(ParameterFile.ParameterFileType.UNQUOTED) .setCharset(ISO_8859_1) .setUseAlways(true) .build()) .addOutput(prunedJ2ObjcArchive) .build(ruleContext)); } } /** Returns archives arising from j2objc transpilation after dead code removal. */ private Iterable computeAndStripPrunedJ2ObjcArchives( J2ObjcEntryClassProvider j2ObjcEntryClassProvider, J2ObjcMappingFileProvider j2ObjcMappingFileProvider, ObjcProvider objcProvider) { Iterable prunedJ2ObjcArchives = ImmutableList.of(); if (stripJ2ObjcDeadCode(j2ObjcEntryClassProvider)) { registerJ2ObjcDeadCodeRemovalActions( objcProvider, j2ObjcMappingFileProvider, j2ObjcEntryClassProvider); prunedJ2ObjcArchives = j2objcPrunedLibraries(objcProvider); } return prunedJ2ObjcArchives; } /** * Returns a nested set of Bazel-built ObjC libraries with all unpruned J2ObjC libraries * substituted with pruned ones. */ private ImmutableList substituteJ2ObjcPrunedLibraries(ObjcProvider objcProvider) { ImmutableList.Builder libraries = new ImmutableList.Builder<>(); Set unprunedJ2ObjcLibs = objcProvider.get(ObjcProvider.J2OBJC_LIBRARY).toSet(); for (Artifact library : objcProvider.getObjcLibraries()) { // If we match an unpruned J2ObjC library, add the pruned version of the J2ObjC static library // instead. if (unprunedJ2ObjcLibs.contains(library)) { libraries.add(intermediateArtifacts.j2objcPrunedArchive(library)); } else { libraries.add(library); } } return libraries.build(); } /** Returns the artifact that should be the outcome of this build's link action */ private Artifact getBinaryToLink() { // When compilation_mode=opt and objc_enable_binary_stripping are specified, the unstripped // binary containing debug symbols is generated by the linker, which also needs the debug // symbols for dead-code removal. The binary is also used to generate dSYM bundle if // --apple_generate_dsym is specified. A symbol strip action is later registered to strip // the symbol table from the unstripped binary. return objcConfiguration.shouldStripBinary() ? intermediateArtifacts.unstrippedSingleArchitectureBinary() : intermediateArtifacts.strippedSingleArchitectureBinary(); } private static CommandLine symbolStripCommandLine( ImmutableList extraFlags, Artifact unstrippedArtifact, Artifact strippedArtifact) { return CustomCommandLine.builder() .add(STRIP) .addAll(extraFlags) .addExecPath("-o", strippedArtifact) .addPath(unstrippedArtifact.getExecPath()) .build(); } /** Signals if stripping should include options for dynamic libraries. */ private enum StrippingType { DEFAULT, DYNAMIC_LIB } /** * Registers an action that uses the 'strip' tool to perform binary stripping on the given binary * subject to the given {@link StrippingType}. */ private void registerBinaryStripAction(Artifact binaryToLink, StrippingType strippingType) { final ImmutableList stripArgs; if (isTestRule) { // For test targets, only debug symbols are stripped off, since /usr/bin/strip is not able // to strip off all symbols in XCTest bundle. stripArgs = ImmutableList.of("-S"); } else if (strippingType == StrippingType.DYNAMIC_LIB) { // For dynamic libs must pass "-x" to strip only local symbols. stripArgs = ImmutableList.of("-x"); } else { stripArgs = ImmutableList.of(); } Artifact strippedBinary = intermediateArtifacts.strippedSingleArchitectureBinary(); ruleContext.registerAction( ObjcRuleClasses.spawnAppleEnvActionBuilder( XcodeConfigProvider.fromRuleContext(ruleContext), appleConfiguration.getSingleArchPlatform()) .setMnemonic("ObjcBinarySymbolStrip") .setExecutable(xcrunwrapper(ruleContext)) .addCommandLine(symbolStripCommandLine(stripArgs, binaryToLink, strippedBinary)) .addOutput(strippedBinary) .addInput(binaryToLink) .build(ruleContext)); } private CompilationSupport registerGenerateUmbrellaHeaderAction( Artifact umbrellaHeader, Iterable publicHeaders) { ruleContext.registerAction( new UmbrellaHeaderAction( ruleContext.getActionOwner(), umbrellaHeader, publicHeaders, ImmutableList.of())); return this; } private Optional getPchFile() { if (!usePch) { return Optional.absent(); } Artifact pchHdr = null; if (ruleContext.attributes().has("pch", BuildType.LABEL)) { pchHdr = ruleContext.getPrerequisiteArtifact("pch", Mode.TARGET); } return Optional.fromNullable(pchHdr); } /** * Registers an action that will generate a clang module map for this target, using the hdrs * attribute of this rule. */ CompilationSupport registerGenerateModuleMapAction(CompilationArtifacts compilationArtifacts) { // TODO(bazel-team): Include textual headers in the module map when Xcode 6 support is // dropped. // TODO(b/32225593): Include private headers in the module map. Iterable publicHeaders = attributes.hdrs(); publicHeaders = Iterables.concat(publicHeaders, compilationArtifacts.getAdditionalHdrs()); CppModuleMap moduleMap = intermediateArtifacts.moduleMap(); registerGenerateModuleMapAction(moduleMap, publicHeaders); Optional umbrellaHeader = moduleMap.getUmbrellaHeader(); if (umbrellaHeader.isPresent()) { registerGenerateUmbrellaHeaderAction(umbrellaHeader.get(), publicHeaders); } return this; } /** * Registers an action that will generate a clang module map. * @param moduleMap the module map to generate * @param publicHeaders the headers that should be directly accessible by dependers * @return this compilation support */ public CompilationSupport registerGenerateModuleMapAction( CppModuleMap moduleMap, Iterable publicHeaders) { publicHeaders = Iterables.filter(publicHeaders, CppFileTypes.MODULE_MAP_HEADER); ruleContext.registerAction( new CppModuleMapAction( ruleContext.getActionOwner(), moduleMap, ImmutableList.of(), publicHeaders, attributes.moduleMapsForDirectDeps(), ImmutableList.of(), /*compiledModule=*/ true, /*moduleMapHomeIsCwd=*/ false, /* generateSubmodules= */ false, /*externDependencies=*/ true)); return this; } /** * Collector that, given a list of output artifacts, finds and registers coverage notes metadata * for any compilation action. */ private static class ObjcCoverageMetadataCollector extends LocalMetadataCollector { @Override public void collectMetadataArtifacts( Iterable artifacts, AnalysisEnvironment analysisEnvironment, NestedSetBuilder metadataFilesBuilder) { for (Artifact artifact : artifacts) { ActionAnalysisMetadata action = analysisEnvironment.getLocalGeneratingAction(artifact); if (action.getMnemonic().equals("ObjcCompile")) { addOutputs(metadataFilesBuilder, action, ObjcRuleClasses.COVERAGE_NOTES); } } } } private static Iterable uniqueParentDirectories(Iterable paths) { ImmutableSet.Builder parents = new ImmutableSet.Builder<>(); for (PathFragment path : paths) { parents.add(path.getParentDirectory()); } return parents.build(); } /** Holds information about Objective-C compile actions that require header thinning. */ private static final class ObjcHeaderThinningInfo { /** Source file for compile action. */ public final Artifact sourceFile; /** headers_list file for compile action. */ public final Artifact headersListFile; /** Command line arguments for compile action execution. */ public final ImmutableList arguments; public ObjcHeaderThinningInfo( Artifact sourceFile, Artifact headersListFile, ImmutableList arguments) { this.sourceFile = Preconditions.checkNotNull(sourceFile); this.headersListFile = Preconditions.checkNotNull(headersListFile); this.arguments = Preconditions.checkNotNull(arguments); } public ObjcHeaderThinningInfo( Artifact sourceFile, Artifact headersListFile, Iterable arguments) { this(sourceFile, headersListFile, ImmutableList.copyOf(arguments)); } } /** * Returns true when ObjC header thinning is enabled via configuration and an a valid * header_scanner executable target is provided. */ private boolean isHeaderThinningEnabled() { if (objcConfiguration.useExperimentalHeaderThinning() && ruleContext.isAttrDefined(ObjcRuleClasses.HEADER_SCANNER_ATTRIBUTE, BuildType.LABEL)) { FilesToRunProvider tool = getHeaderThinningToolExecutable(); // Additional here to ensure that an Executable Artifact exists to disable where the tool // is an empty filegroup return tool != null && tool.getExecutable() != null; } return false; } private FilesToRunProvider getHeaderThinningToolExecutable() { return ruleContext .getPrerequisite(ObjcRuleClasses.HEADER_SCANNER_ATTRIBUTE, Mode.HOST) .getProvider(FilesToRunProvider.class); } private void registerHeaderScanningActions( CcCompilationOutputs ccCompilationOutputs, ObjcProvider objcProvider, CompilationArtifacts compilationArtifacts) { // PIC is not used for Obj-C builds, if that changes this method will need to change if (!isHeaderThinningEnabled() || ccCompilationOutputs.getObjectFiles(false).isEmpty()) { return; } ImmutableList.Builder headerThinningInfos = ImmutableList.builder(); AnalysisEnvironment analysisEnvironment = ruleContext.getAnalysisEnvironment(); for (Artifact objectFile : ccCompilationOutputs.getObjectFiles(false)) { ActionAnalysisMetadata generatingAction = analysisEnvironment.getLocalGeneratingAction(objectFile); if (generatingAction instanceof CppCompileAction) { CppCompileAction action = (CppCompileAction) generatingAction; Artifact sourceFile = action.getSourceFile(); if (!sourceFile.isTreeArtifact() && SOURCES_FOR_HEADER_THINNING.matches(sourceFile.getFilename())) { headerThinningInfos.add( new ObjcHeaderThinningInfo( sourceFile, intermediateArtifacts.headersListFile(objectFile), action.getCompilerOptions())); } } } registerHeaderScanningActions(headerThinningInfos.build(), objcProvider, compilationArtifacts); } /** * Creates and registers ObjcHeaderScanning {@link SpawnAction}. Groups all the actions by their * compilation command line arguments and creates a ObjcHeaderScanning action for each unique one. * *

The number of sources to scan per actions are bounded so that targets with a high number of * sources are not penalized. A large number of sources may require a lot of processing * particularly when the headers required for different sources vary greatly and the caching * mechanism in the tool is largely useless. In these instances these actions would benefit by * being distributed so they don't contribute to the critical path. The partition size is * configurable so that it can be tuned. */ private void registerHeaderScanningActions( ImmutableList headerThinningInfo, ObjcProvider objcProvider, CompilationArtifacts compilationArtifacts) { if (headerThinningInfo.isEmpty()) { return; } ListMultimap, ObjcHeaderThinningInfo> objcHeaderThinningInfoByCommandLine = groupActionsByCommandLine(headerThinningInfo); // Register a header scanning spawn action for each unique set of command line arguments for (ImmutableList args : objcHeaderThinningInfoByCommandLine.keySet()) { // As infos is in insertion order we should reliably get the same sublists below for (List partition : Lists.partition( objcHeaderThinningInfoByCommandLine.get(args), objcConfiguration.objcHeaderThinningPartitionSize())) { registerHeaderScanningAction(objcProvider, compilationArtifacts, args, partition); } } } private void registerHeaderScanningAction( ObjcProvider objcProvider, CompilationArtifacts compilationArtifacts, ImmutableList args, List infos) { SpawnAction.Builder builder = new SpawnAction.Builder() .setMnemonic("ObjcHeaderScanning") .setExecutable(getHeaderThinningToolExecutable()) .addInputs( ruleContext .getPrerequisiteArtifacts(ObjcRuleClasses.APPLE_SDK_ATTRIBUTE, Mode.TARGET) .list()); CustomCommandLine.Builder cmdLine = CustomCommandLine.builder() .add("--arch", appleConfiguration.getSingleArchitecture().toLowerCase()) .add("--platform", appleConfiguration.getSingleArchPlatform().getLowerCaseNameInPlist()) .add( "--sdk_version", XcodeConfig.getSdkVersionForPlatform( ruleContext, appleConfiguration.getSingleArchPlatform()) .toStringWithMinimumComponents(2)) .add( "--xcode_version", XcodeConfig.getXcodeVersion(ruleContext).toStringWithMinimumComponents(2)) .add("--"); for (ObjcHeaderThinningInfo info : infos) { cmdLine.addFormatted( "%s:%s", info.sourceFile.getExecPath(), info.headersListFile.getExecPath()); builder.addInput(info.sourceFile).addOutput(info.headersListFile); } ruleContext.registerAction( builder .addCommandLine(cmdLine.add("--").addAll(args).build()) .addInputs(compilationArtifacts.getPrivateHdrs()) .addTransitiveInputs(attributes.hdrs()) .addTransitiveInputs(objcProvider.get(ObjcProvider.HEADER)) .addInputs(getPchFile().asSet()) .addTransitiveInputs(objcProvider.get(ObjcProvider.STATIC_FRAMEWORK_FILE)) .addTransitiveInputs(objcProvider.get(ObjcProvider.DYNAMIC_FRAMEWORK_FILE)) .build(ruleContext)); } /** * Groups {@link ObjcHeaderThinningInfo} objects based on the command line arguments of the * ObjcCompile action. * *

Grouping by command line arguments allows {@link * #registerHeaderScanningActions(ImmutableList, ObjcProvider, CompilationArtifacts)} to create a * {@link SpawnAction} based on the compiler command line flags that may cause a difference in * behaviour by the preprocessor. Some of the command line arguments must be filtered out as they * change with every source {@link Artifact}; for example the object file (-o) and dotd filenames * (-MF). These arguments are known not to change the preprocessor behaviour. * * @param headerThinningInfos information for compile actions that require header thinning * @return values in {@code headerThinningInfos} grouped by compile action command line arguments */ private static ListMultimap, ObjcHeaderThinningInfo> groupActionsByCommandLine(ImmutableList headerThinningInfos) { // Maintain insertion order so that iteration in #registerHeaderScanningActions is deterministic ListMultimap, ObjcHeaderThinningInfo> objcHeaderThinningInfoByCommandLine = ArrayListMultimap.create(); for (ObjcHeaderThinningInfo info : headerThinningInfos) { ImmutableList.Builder filteredArgumentsBuilder = ImmutableList.builder(); List arguments = info.arguments; for (int i = 0; i < arguments.size(); ++i) { String arg = arguments.get(i); if (arg.equals("-MF") || arg.equals("-o") || arg.equals("-c")) { ++i; } else if (!arg.equals("-MD")) { filteredArgumentsBuilder.add(arg); } } objcHeaderThinningInfoByCommandLine.put(filteredArgumentsBuilder.build(), info); } return objcHeaderThinningInfoByCommandLine; } @Nullable private FdoSupportProvider maybeGetFdoSupport() { // TODO(rduan): Remove this check once all rules are using the crosstool support. if (ruleContext .attributes() .has(CcToolchain.CC_TOOLCHAIN_DEFAULT_ATTRIBUTE_NAME, BuildType.LABEL)) { return CppHelper.getFdoSupportUsingDefaultCcToolchainAttribute(ruleContext); } else { return null; } } public static Optional getCustomModuleMap(RuleContext ruleContext) { if (ruleContext.attributes().has("module_map", BuildType.LABEL)) { return Optional.fromNullable(ruleContext.getPrerequisiteArtifact("module_map", Mode.TARGET)); } return Optional.absent(); } }