/*
* Copyright 2014-present Facebook, Inc.
*
* 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.facebook.buck.apple;
import static com.facebook.buck.swift.SwiftDescriptions.SWIFT_EXTENSION;
import com.facebook.buck.cxx.BuildRuleWithBinary;
import com.facebook.buck.cxx.CxxBinaryDescriptionArg;
import com.facebook.buck.cxx.CxxCompilationDatabase;
import com.facebook.buck.cxx.CxxDescriptionEnhancer;
import com.facebook.buck.cxx.CxxLibraryDescription;
import com.facebook.buck.cxx.CxxLibraryDescriptionArg;
import com.facebook.buck.cxx.CxxPlatform;
import com.facebook.buck.cxx.CxxStrip;
import com.facebook.buck.cxx.FrameworkDependencies;
import com.facebook.buck.cxx.LinkerMapMode;
import com.facebook.buck.cxx.NativeLinkable;
import com.facebook.buck.cxx.ProvidesLinkedBinaryDeps;
import com.facebook.buck.cxx.StripStyle;
import com.facebook.buck.io.MorePaths;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.Either;
import com.facebook.buck.model.Flavor;
import com.facebook.buck.model.FlavorDomain;
import com.facebook.buck.model.InternalFlavor;
import com.facebook.buck.parser.NoSuchBuildTargetException;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.BuildRules;
import com.facebook.buck.rules.BuildTargetSourcePath;
import com.facebook.buck.rules.PathSourcePath;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.SourceWithFlags;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.TargetNode;
import com.facebook.buck.rules.Tool;
import com.facebook.buck.rules.coercer.SourceList;
import com.facebook.buck.shell.AbstractGenruleDescription;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.MoreCollectors;
import com.facebook.buck.util.OptionalCompat;
import com.facebook.buck.util.RichStream;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Suppliers;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
/**
* Common logic for a {@link com.facebook.buck.rules.Description} that creates Apple target rules.
*/
public class AppleDescriptions {
public static final Flavor FRAMEWORK_FLAVOR = InternalFlavor.of("framework");
public static final Flavor INCLUDE_FRAMEWORKS_FLAVOR = InternalFlavor.of("include-frameworks");
public static final Flavor NO_INCLUDE_FRAMEWORKS_FLAVOR =
InternalFlavor.of("no-include-frameworks");
public static final FlavorDomain<Boolean> INCLUDE_FRAMEWORKS =
new FlavorDomain<>(
"Include frameworks",
ImmutableMap.of(
INCLUDE_FRAMEWORKS_FLAVOR, Boolean.TRUE,
NO_INCLUDE_FRAMEWORKS_FLAVOR, Boolean.FALSE));
private static final ImmutableSet<Flavor> BUNDLE_SPECIFIC_FLAVORS =
ImmutableSet.of(INCLUDE_FRAMEWORKS_FLAVOR, NO_INCLUDE_FRAMEWORKS_FLAVOR);
private static final String MERGED_ASSET_CATALOG_NAME = "Merged";
/** Utility class: do not instantiate. */
private AppleDescriptions() {}
public static Path getHeaderPathPrefix(
AppleNativeTargetDescriptionArg arg, BuildTarget buildTarget) {
return Paths.get(arg.getHeaderPathPrefix().orElse(buildTarget.getShortName()));
}
public static ImmutableSortedMap<String, SourcePath> convertAppleHeadersToPublicCxxHeaders(
Function<SourcePath, Path> pathResolver,
Path headerPathPrefix,
CxxLibraryDescription.CommonArg arg) {
// The exported headers in the populated cxx constructor arg will contain exported headers from
// the apple constructor arg with the public include style.
return AppleDescriptions.parseAppleHeadersForUseFromOtherTargets(
pathResolver, headerPathPrefix, arg.getExportedHeaders());
}
public static ImmutableSortedMap<String, SourcePath> convertAppleHeadersToPrivateCxxHeaders(
Function<SourcePath, Path> pathResolver,
Path headerPathPrefix,
CxxLibraryDescription.CommonArg arg) {
// The private headers will contain exported headers with the private include style and private
// headers with both styles.
return ImmutableSortedMap.<String, SourcePath>naturalOrder()
.putAll(
AppleDescriptions.parseAppleHeadersForUseFromTheSameTarget(
pathResolver, arg.getHeaders()))
.putAll(
AppleDescriptions.parseAppleHeadersForUseFromOtherTargets(
pathResolver, headerPathPrefix, arg.getHeaders()))
.putAll(
AppleDescriptions.parseAppleHeadersForUseFromTheSameTarget(
pathResolver, arg.getExportedHeaders()))
.build();
}
@VisibleForTesting
static ImmutableSortedMap<String, SourcePath> parseAppleHeadersForUseFromOtherTargets(
Function<SourcePath, Path> pathResolver, Path headerPathPrefix, SourceList headers) {
if (headers.getUnnamedSources().isPresent()) {
// The user specified a set of header files. For use from other targets, prepend their names
// with the header path prefix.
return convertToFlatCxxHeaders(
headerPathPrefix, pathResolver, headers.getUnnamedSources().get());
} else {
// The user specified a map from include paths to header files. Just use the specified map.
return headers.getNamedSources().get();
}
}
@VisibleForTesting
static ImmutableMap<String, SourcePath> parseAppleHeadersForUseFromTheSameTarget(
Function<SourcePath, Path> pathResolver, SourceList headers) {
if (headers.getUnnamedSources().isPresent()) {
// The user specified a set of header files. Headers can be included from the same target
// using only their file name without a prefix.
return convertToFlatCxxHeaders(
Paths.get(""), pathResolver, headers.getUnnamedSources().get());
} else {
// The user specified a map from include paths to header files. There is nothing we need to
// add on top of the exported headers.
return ImmutableMap.of();
}
}
/**
* Convert {@link SourcePath} to a mapping of {@code include path -> file path}.
*
* <p>{@code include path} is the path that can be referenced in {@code #include} directives.
* {@code file path} is the actual path to the file on disk.
*
* @throws HumanReadableException when two {@code SourcePath} yields the same IncludePath.
*/
@VisibleForTesting
static ImmutableSortedMap<String, SourcePath> convertToFlatCxxHeaders(
Path headerPathPrefix,
Function<SourcePath, Path> sourcePathResolver,
Set<SourcePath> headerPaths) {
Set<String> includeToFile = new HashSet<String>(headerPaths.size());
ImmutableSortedMap.Builder<String, SourcePath> builder = ImmutableSortedMap.naturalOrder();
for (SourcePath headerPath : headerPaths) {
Path fileName = sourcePathResolver.apply(headerPath).getFileName();
String key = headerPathPrefix.resolve(fileName).toString();
if (includeToFile.contains(key)) {
ImmutableSortedMap<String, SourcePath> result = builder.build();
throw new HumanReadableException(
"The same include path maps to multiple files:\n"
+ " Include path: %s\n"
+ " Conflicting files:\n"
+ " %s\n"
+ " %s",
key, headerPath, result.get(key));
}
includeToFile.add(key);
builder.put(key, headerPath);
}
return builder.build();
}
public static void populateCxxConstructorArg(
SourcePathResolver resolver,
AppleNativeTargetDescriptionArg arg,
BuildTarget buildTarget,
Consumer<ImmutableSortedSet<SourceWithFlags>> setSrcs,
Consumer<SourceList> setHeaders,
Consumer<String> setHeaderNamespace) {
Path headerPathPrefix = AppleDescriptions.getHeaderPathPrefix(arg, buildTarget);
// The resulting cxx constructor arg will have no exported headers and both headers and exported
// headers specified in the apple arg will be available with both public and private include
// styles.
ImmutableSortedMap<String, SourcePath> headerMap =
ImmutableSortedMap.<String, SourcePath>naturalOrder()
.putAll(
convertAppleHeadersToPublicCxxHeaders(
resolver::getRelativePath, headerPathPrefix, arg))
.putAll(
convertAppleHeadersToPrivateCxxHeaders(
resolver::getRelativePath, headerPathPrefix, arg))
.build();
ImmutableSortedSet.Builder<SourceWithFlags> nonSwiftSrcs = ImmutableSortedSet.naturalOrder();
for (SourceWithFlags src : arg.getSrcs()) {
if (!MorePaths.getFileExtension(resolver.getAbsolutePath(src.getSourcePath()))
.equalsIgnoreCase(SWIFT_EXTENSION)) {
nonSwiftSrcs.add(src);
}
}
setSrcs.accept(nonSwiftSrcs.build());
setHeaders.accept(SourceList.ofNamedSources(headerMap));
// This is intentionally an empty string; we put all prefixes into
// the header map itself.
setHeaderNamespace.accept("");
}
public static void populateCxxBinaryDescriptionArg(
SourcePathResolver resolver,
CxxBinaryDescriptionArg.Builder output,
AppleNativeTargetDescriptionArg arg,
BuildTarget buildTarget) {
populateCxxConstructorArg(
resolver,
arg,
buildTarget,
output::setSrcs,
output::setHeaders,
output::setHeaderNamespace);
output.setDefaultPlatform(Optional.empty());
}
public static void populateCxxLibraryDescriptionArg(
SourcePathResolver resolver,
CxxLibraryDescriptionArg.Builder output,
AppleNativeTargetDescriptionArg arg,
BuildTarget buildTarget) {
populateCxxConstructorArg(
resolver,
arg,
buildTarget,
output::setSrcs,
output::setHeaders,
output::setHeaderNamespace);
Path headerPathPrefix = AppleDescriptions.getHeaderPathPrefix(arg, buildTarget);
output.setHeaders(
SourceList.ofNamedSources(
convertAppleHeadersToPrivateCxxHeaders(
resolver::getRelativePath, headerPathPrefix, arg)));
output.setExportedHeaders(
SourceList.ofNamedSources(
convertAppleHeadersToPublicCxxHeaders(
resolver::getRelativePath, headerPathPrefix, arg)));
}
public static Optional<AppleAssetCatalog> createBuildRuleForTransitiveAssetCatalogDependencies(
TargetGraph targetGraph,
BuildRuleParams params,
SourcePathResolver sourcePathResolver,
ApplePlatform applePlatform,
String targetSDKVersion,
Tool actool) {
TargetNode<?, ?> targetNode = targetGraph.get(params.getBuildTarget());
ImmutableSet<AppleAssetCatalogDescriptionArg> assetCatalogArgs =
AppleBuildRules.collectRecursiveAssetCatalogs(
targetGraph, Optional.empty(), ImmutableList.of(targetNode));
ImmutableSortedSet.Builder<SourcePath> assetCatalogDirsBuilder =
ImmutableSortedSet.naturalOrder();
Optional<String> appIcon = Optional.empty();
Optional<String> launchImage = Optional.empty();
AppleAssetCatalogDescription.Optimization optimization = null;
for (AppleAssetCatalogDescriptionArg arg : assetCatalogArgs) {
if (optimization == null) {
optimization = arg.getOptimization();
}
assetCatalogDirsBuilder.addAll(arg.getDirs());
if (arg.getAppIcon().isPresent()) {
if (appIcon.isPresent()) {
throw new HumanReadableException(
"At most one asset catalog in the dependencies of %s " + "can have a app_icon",
params.getBuildTarget());
}
appIcon = arg.getAppIcon();
}
if (arg.getLaunchImage().isPresent()) {
if (launchImage.isPresent()) {
throw new HumanReadableException(
"At most one asset catalog in the dependencies of %s " + "can have a launch_image",
params.getBuildTarget());
}
launchImage = arg.getLaunchImage();
}
if (arg.getOptimization() != optimization) {
throw new HumanReadableException(
"At most one asset catalog optimisation style can be "
+ "specified in the dependencies %s",
params.getBuildTarget());
}
}
ImmutableSortedSet<SourcePath> assetCatalogDirs = assetCatalogDirsBuilder.build();
if (assetCatalogDirs.isEmpty()) {
return Optional.empty();
}
Preconditions.checkNotNull(
optimization, "optimization was null even though assetCatalogArgs was not empty");
for (SourcePath assetCatalogDir : assetCatalogDirs) {
Path baseName = sourcePathResolver.getRelativePath(assetCatalogDir).getFileName();
if (!baseName.toString().endsWith(".xcassets")) {
throw new HumanReadableException(
"Target %s had asset catalog dir %s - asset catalog dirs must end with .xcassets",
params.getBuildTarget(), assetCatalogDir);
}
}
BuildRuleParams assetCatalogParams =
params
.withAppendedFlavor(AppleAssetCatalog.FLAVOR)
.copyReplacingDeclaredAndExtraDeps(
Suppliers.ofInstance(ImmutableSortedSet.of()),
Suppliers.ofInstance(ImmutableSortedSet.of()));
return Optional.of(
new AppleAssetCatalog(
assetCatalogParams,
applePlatform.getName(),
targetSDKVersion,
actool,
assetCatalogDirs,
appIcon,
launchImage,
optimization,
MERGED_ASSET_CATALOG_NAME));
}
public static Optional<CoreDataModel> createBuildRulesForCoreDataDependencies(
TargetGraph targetGraph,
BuildRuleParams params,
String moduleName,
AppleCxxPlatform appleCxxPlatform) {
TargetNode<?, ?> targetNode = targetGraph.get(params.getBuildTarget());
ImmutableSet<AppleWrapperResourceArg> coreDataModelArgs =
AppleBuildRules.collectTransitiveBuildRules(
targetGraph,
Optional.empty(),
AppleBuildRules.CORE_DATA_MODEL_DESCRIPTION_CLASSES,
ImmutableList.of(targetNode));
BuildRuleParams coreDataModelParams =
params
.withAppendedFlavor(CoreDataModel.FLAVOR)
.copyReplacingDeclaredAndExtraDeps(
Suppliers.ofInstance(ImmutableSortedSet.of()),
Suppliers.ofInstance(ImmutableSortedSet.of()));
if (coreDataModelArgs.isEmpty()) {
return Optional.empty();
} else {
return Optional.of(
new CoreDataModel(
coreDataModelParams,
appleCxxPlatform,
moduleName,
coreDataModelArgs
.stream()
.map(input -> new PathSourcePath(params.getProjectFilesystem(), input.getPath()))
.collect(MoreCollectors.toImmutableSet())));
}
}
public static Optional<SceneKitAssets> createBuildRulesForSceneKitAssetsDependencies(
TargetGraph targetGraph, BuildRuleParams params, AppleCxxPlatform appleCxxPlatform) {
TargetNode<?, ?> targetNode = targetGraph.get(params.getBuildTarget());
ImmutableSet<AppleWrapperResourceArg> sceneKitAssetsArgs =
AppleBuildRules.collectTransitiveBuildRules(
targetGraph,
Optional.empty(),
AppleBuildRules.SCENEKIT_ASSETS_DESCRIPTION_CLASSES,
ImmutableList.of(targetNode));
BuildRuleParams sceneKitAssetsParams =
params
.withAppendedFlavor(SceneKitAssets.FLAVOR)
.copyReplacingDeclaredAndExtraDeps(
Suppliers.ofInstance(ImmutableSortedSet.of()),
Suppliers.ofInstance(ImmutableSortedSet.of()));
if (sceneKitAssetsArgs.isEmpty()) {
return Optional.empty();
} else {
return Optional.of(
new SceneKitAssets(
sceneKitAssetsParams,
appleCxxPlatform,
sceneKitAssetsArgs
.stream()
.map(input -> new PathSourcePath(params.getProjectFilesystem(), input.getPath()))
.collect(MoreCollectors.toImmutableSet())));
}
}
static AppleDebuggableBinary createAppleDebuggableBinary(
BuildRuleParams params,
BuildRuleResolver resolver,
BuildRule strippedBinaryRule,
ProvidesLinkedBinaryDeps unstrippedBinaryRule,
AppleDebugFormat debugFormat,
FlavorDomain<CxxPlatform> cxxPlatformFlavorDomain,
CxxPlatform defaultCxxPlatform,
FlavorDomain<AppleCxxPlatform> appleCxxPlatforms) {
Optional<AppleDsym> appleDsym =
createAppleDsymForDebugFormat(
debugFormat,
params,
resolver,
unstrippedBinaryRule,
cxxPlatformFlavorDomain,
defaultCxxPlatform,
appleCxxPlatforms);
BuildRule buildRuleForDebugFormat;
if (debugFormat == AppleDebugFormat.DWARF) {
buildRuleForDebugFormat = unstrippedBinaryRule;
} else {
buildRuleForDebugFormat = strippedBinaryRule;
}
AppleDebuggableBinary rule =
new AppleDebuggableBinary(
params
.withBuildTarget(
strippedBinaryRule
.getBuildTarget()
.withAppendedFlavors(
AppleDebuggableBinary.RULE_FLAVOR, debugFormat.getFlavor()))
.copyReplacingDeclaredAndExtraDeps(
Suppliers.ofInstance(
AppleDebuggableBinary.getRequiredRuntimeDeps(
debugFormat, strippedBinaryRule, unstrippedBinaryRule, appleDsym)),
Suppliers.ofInstance(ImmutableSortedSet.of())),
buildRuleForDebugFormat);
return rule;
}
private static Optional<AppleDsym> createAppleDsymForDebugFormat(
AppleDebugFormat debugFormat,
BuildRuleParams params,
BuildRuleResolver resolver,
ProvidesLinkedBinaryDeps unstrippedBinaryRule,
FlavorDomain<CxxPlatform> cxxPlatformFlavorDomain,
CxxPlatform defaultCxxPlatform,
FlavorDomain<AppleCxxPlatform> appleCxxPlatforms) {
if (debugFormat == AppleDebugFormat.DWARF_AND_DSYM) {
BuildTarget dsymBuildTarget =
params
.getBuildTarget()
.withoutFlavors(CxxStrip.RULE_FLAVOR)
.withoutFlavors(StripStyle.FLAVOR_DOMAIN.getFlavors())
.withoutFlavors(AppleDebugFormat.FLAVOR_DOMAIN.getFlavors())
.withoutFlavors(LinkerMapMode.NO_LINKER_MAP.getFlavor())
.withAppendedFlavors(AppleDsym.RULE_FLAVOR);
Optional<BuildRule> dsymRule = resolver.getRuleOptional(dsymBuildTarget);
if (!dsymRule.isPresent()) {
dsymRule =
Optional.of(
createAppleDsym(
params.withBuildTarget(dsymBuildTarget),
resolver,
unstrippedBinaryRule,
cxxPlatformFlavorDomain,
defaultCxxPlatform,
appleCxxPlatforms));
}
Preconditions.checkArgument(dsymRule.get() instanceof AppleDsym);
return Optional.of((AppleDsym) dsymRule.get());
}
return Optional.empty();
}
static AppleDsym createAppleDsym(
BuildRuleParams params,
BuildRuleResolver resolver,
ProvidesLinkedBinaryDeps unstrippedBinaryBuildRule,
FlavorDomain<CxxPlatform> cxxPlatformFlavorDomain,
CxxPlatform defaultCxxPlatform,
FlavorDomain<AppleCxxPlatform> appleCxxPlatforms) {
AppleCxxPlatform appleCxxPlatform =
ApplePlatforms.getAppleCxxPlatformForBuildTarget(
cxxPlatformFlavorDomain,
defaultCxxPlatform,
appleCxxPlatforms,
unstrippedBinaryBuildRule.getBuildTarget(),
MultiarchFileInfos.create(
appleCxxPlatforms, unstrippedBinaryBuildRule.getBuildTarget()));
AppleDsym appleDsym =
new AppleDsym(
params.copyReplacingDeclaredAndExtraDeps(
Suppliers.ofInstance(
ImmutableSortedSet.<BuildRule>naturalOrder()
.add(unstrippedBinaryBuildRule)
.addAll(unstrippedBinaryBuildRule.getCompileDeps())
.addAll(unstrippedBinaryBuildRule.getStaticLibraryDeps())
.build()),
Suppliers.ofInstance(ImmutableSortedSet.of())),
appleCxxPlatform.getDsymutil(),
appleCxxPlatform.getLldb(),
unstrippedBinaryBuildRule.getSourcePathToOutput(),
AppleDsym.getDsymOutputPath(params.getBuildTarget(), params.getProjectFilesystem()));
resolver.addToIndex(appleDsym);
return appleDsym;
}
static AppleBundle createAppleBundle(
FlavorDomain<CxxPlatform> cxxPlatformFlavorDomain,
CxxPlatform defaultCxxPlatform,
FlavorDomain<AppleCxxPlatform> appleCxxPlatforms,
TargetGraph targetGraph,
BuildRuleParams params,
BuildRuleResolver resolver,
CodeSignIdentityStore codeSignIdentityStore,
ProvisioningProfileStore provisioningProfileStore,
BuildTarget binary,
Either<AppleBundleExtension, String> extension,
Optional<String> productName,
final SourcePath infoPlist,
ImmutableMap<String, String> infoPlistSubstitutions,
ImmutableSortedSet<BuildTarget> deps,
ImmutableSortedSet<BuildTarget> tests,
AppleDebugFormat debugFormat,
boolean dryRunCodeSigning,
boolean cacheable)
throws NoSuchBuildTargetException {
AppleCxxPlatform appleCxxPlatform =
ApplePlatforms.getAppleCxxPlatformForBuildTarget(
cxxPlatformFlavorDomain,
defaultCxxPlatform,
appleCxxPlatforms,
params.getBuildTarget(),
MultiarchFileInfos.create(appleCxxPlatforms, params.getBuildTarget()));
AppleBundleDestinations destinations;
if (extension.isLeft() && extension.getLeft().equals(AppleBundleExtension.FRAMEWORK)) {
destinations =
AppleBundleDestinations.platformFrameworkDestinations(
appleCxxPlatform.getAppleSdk().getApplePlatform());
} else {
destinations =
AppleBundleDestinations.platformDestinations(
appleCxxPlatform.getAppleSdk().getApplePlatform());
}
AppleBundleResources collectedResources =
AppleResources.collectResourceDirsAndFiles(
targetGraph, resolver, Optional.empty(), targetGraph.get(params.getBuildTarget()));
ImmutableSet.Builder<SourcePath> frameworksBuilder = ImmutableSet.builder();
if (INCLUDE_FRAMEWORKS.getRequiredValue(params.getBuildTarget())) {
for (BuildTarget dep : deps) {
Optional<FrameworkDependencies> frameworkDependencies =
resolver.requireMetadata(
BuildTarget.builder(dep)
.addFlavors(FRAMEWORK_FLAVOR)
.addFlavors(NO_INCLUDE_FRAMEWORKS_FLAVOR)
.addFlavors(appleCxxPlatform.getCxxPlatform().getFlavor())
.build(),
FrameworkDependencies.class);
if (frameworkDependencies.isPresent()) {
frameworksBuilder.addAll(frameworkDependencies.get().getSourcePaths());
}
}
}
// TODO(17155714): framework embedding is currently oddly entwined with framework generation.
// This change simply treats all the immediate prebuilt framework dependencies as wishing to be
// embedded, but in the future this should be dealt with with some greater sophistication.
for (BuildTarget dep : deps) {
Optional<TargetNode<PrebuiltAppleFrameworkDescriptionArg, ?>> prebuiltNode =
targetGraph
.getOptional(dep)
.flatMap(node -> node.castArg(PrebuiltAppleFrameworkDescriptionArg.class));
if (prebuiltNode.isPresent()
&& !prebuiltNode
.get()
.getConstructorArg()
.getPreferredLinkage()
.equals(NativeLinkable.Linkage.STATIC)) {
frameworksBuilder.add(resolver.requireRule(dep).getSourcePathToOutput());
}
}
ImmutableSet<SourcePath> frameworks = frameworksBuilder.build();
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
SourcePathResolver sourcePathResolver = new SourcePathResolver(ruleFinder);
BuildRuleParams paramsWithoutBundleSpecificFlavors = stripBundleSpecificFlavors(params);
Optional<AppleAssetCatalog> assetCatalog =
createBuildRuleForTransitiveAssetCatalogDependencies(
targetGraph,
paramsWithoutBundleSpecificFlavors,
sourcePathResolver,
appleCxxPlatform.getAppleSdk().getApplePlatform(),
appleCxxPlatform.getMinVersion(),
appleCxxPlatform.getActool());
addToIndex(resolver, assetCatalog);
Optional<CoreDataModel> coreDataModel =
createBuildRulesForCoreDataDependencies(
targetGraph,
paramsWithoutBundleSpecificFlavors,
AppleBundle.getBinaryName(params.getBuildTarget(), productName),
appleCxxPlatform);
addToIndex(resolver, coreDataModel);
Optional<SceneKitAssets> sceneKitAssets =
createBuildRulesForSceneKitAssetsDependencies(
targetGraph, paramsWithoutBundleSpecificFlavors, appleCxxPlatform);
addToIndex(resolver, sceneKitAssets);
// TODO(beng): Sort through the changes needed to make project generation work with
// binary being optional.
BuildRule flavoredBinaryRule =
getFlavoredBinaryRule(
cxxPlatformFlavorDomain,
defaultCxxPlatform,
targetGraph,
paramsWithoutBundleSpecificFlavors.getBuildTarget().getFlavors(),
resolver,
binary);
if (!AppleDebuggableBinary.isBuildRuleDebuggable(flavoredBinaryRule)) {
debugFormat = AppleDebugFormat.NONE;
}
BuildTarget unstrippedTarget =
flavoredBinaryRule
.getBuildTarget()
.withoutFlavors(
CxxStrip.RULE_FLAVOR,
AppleDebuggableBinary.RULE_FLAVOR,
AppleBinaryDescription.APP_FLAVOR)
.withoutFlavors(StripStyle.FLAVOR_DOMAIN.getFlavors())
.withoutFlavors(AppleDebugFormat.FLAVOR_DOMAIN.getFlavors())
.withoutFlavors(AppleDebuggableBinary.RULE_FLAVOR)
.withoutFlavors(ImmutableSet.of(AppleBinaryDescription.APP_FLAVOR));
Optional<LinkerMapMode> linkerMapMode =
LinkerMapMode.FLAVOR_DOMAIN.getValue(params.getBuildTarget());
if (linkerMapMode.isPresent()) {
unstrippedTarget = unstrippedTarget.withAppendedFlavors(linkerMapMode.get().getFlavor());
}
BuildRule unstrippedBinaryRule = resolver.requireRule(unstrippedTarget);
BuildRule targetDebuggableBinaryRule;
Optional<AppleDsym> appleDsym;
if (unstrippedBinaryRule instanceof ProvidesLinkedBinaryDeps) {
BuildTarget binaryBuildTarget =
getBinaryFromBuildRuleWithBinary(flavoredBinaryRule)
.getBuildTarget()
.withoutFlavors(AppleDebugFormat.FLAVOR_DOMAIN.getFlavors());
BuildRuleParams binaryParams = params.withBuildTarget(binaryBuildTarget);
targetDebuggableBinaryRule =
createAppleDebuggableBinary(
binaryParams,
resolver,
getBinaryFromBuildRuleWithBinary(flavoredBinaryRule),
(ProvidesLinkedBinaryDeps) unstrippedBinaryRule,
debugFormat,
cxxPlatformFlavorDomain,
defaultCxxPlatform,
appleCxxPlatforms);
appleDsym =
createAppleDsymForDebugFormat(
debugFormat,
binaryParams,
resolver,
(ProvidesLinkedBinaryDeps) unstrippedBinaryRule,
cxxPlatformFlavorDomain,
defaultCxxPlatform,
appleCxxPlatforms);
} else {
targetDebuggableBinaryRule = unstrippedBinaryRule;
appleDsym = Optional.empty();
}
BuildRuleParams bundleParamsWithFlavoredBinaryDep =
getBundleParamsWithUpdatedDeps(
params,
binary,
ImmutableSet.<BuildRule>builder()
.add(targetDebuggableBinaryRule)
.addAll(OptionalCompat.asSet(assetCatalog))
.addAll(OptionalCompat.asSet(coreDataModel))
.addAll(OptionalCompat.asSet(sceneKitAssets))
.addAll(
BuildRules.toBuildRulesFor(
params.getBuildTarget(),
resolver,
RichStream.from(collectedResources.getAll())
.concat(frameworks.stream())
.filter(BuildTargetSourcePath.class)
.map(BuildTargetSourcePath::getTarget)
.collect(MoreCollectors.toImmutableSet())))
.addAll(OptionalCompat.asSet(appleDsym))
.build());
ImmutableMap<SourcePath, String> extensionBundlePaths =
collectFirstLevelAppleDependencyBundles(params.getBuildDeps(), destinations);
return new AppleBundle(
bundleParamsWithFlavoredBinaryDep,
resolver,
extension,
productName,
infoPlist,
infoPlistSubstitutions,
Optional.of(getBinaryFromBuildRuleWithBinary(flavoredBinaryRule)),
appleDsym,
destinations,
collectedResources,
extensionBundlePaths,
frameworks,
appleCxxPlatform,
assetCatalog,
coreDataModel,
sceneKitAssets,
tests,
codeSignIdentityStore,
provisioningProfileStore,
dryRunCodeSigning,
cacheable);
}
private static void addToIndex(BuildRuleResolver resolver, Optional<? extends BuildRule> rule) {
if (rule.isPresent()) {
resolver.addToIndex(rule.get());
}
}
private static BuildRule getBinaryFromBuildRuleWithBinary(BuildRule rule) {
if (rule instanceof BuildRuleWithBinary) {
rule = ((BuildRuleWithBinary) rule).getBinaryBuildRule();
}
return rule;
}
private static BuildRule getFlavoredBinaryRule(
FlavorDomain<CxxPlatform> cxxPlatformFlavorDomain,
CxxPlatform defaultCxxPlatform,
TargetGraph targetGraph,
ImmutableSet<Flavor> flavors,
BuildRuleResolver resolver,
BuildTarget binary)
throws NoSuchBuildTargetException {
// Don't flavor genrule deps.
if (targetGraph.get(binary).getDescription() instanceof AbstractGenruleDescription) {
return resolver.requireRule(binary);
}
// Cxx targets must have one Platform Flavor set otherwise nothing gets compiled.
if (flavors.contains(AppleDescriptions.FRAMEWORK_FLAVOR)) {
flavors =
ImmutableSet.<Flavor>builder()
.addAll(flavors)
.add(CxxDescriptionEnhancer.SHARED_FLAVOR)
.build();
}
flavors =
ImmutableSet.copyOf(
Sets.difference(
flavors,
ImmutableSet.of(
AppleDescriptions.FRAMEWORK_FLAVOR, AppleBinaryDescription.APP_FLAVOR)));
if (!cxxPlatformFlavorDomain.containsAnyOf(flavors)) {
flavors =
new ImmutableSet.Builder<Flavor>()
.addAll(flavors)
.add(defaultCxxPlatform.getFlavor())
.build();
}
BuildTarget.Builder buildTargetBuilder =
BuildTarget.builder(binary.getUnflavoredBuildTarget()).addAllFlavors(flavors);
if (!(AppleLibraryDescription.LIBRARY_TYPE.getFlavor(flavors).isPresent())) {
buildTargetBuilder.addAllFlavors(binary.getFlavors());
} else {
buildTargetBuilder.addAllFlavors(
Sets.difference(binary.getFlavors(), AppleLibraryDescription.LIBRARY_TYPE.getFlavors()));
}
BuildTarget buildTarget = buildTargetBuilder.build();
final TargetNode<?, ?> binaryTargetNode = targetGraph.get(buildTarget);
if (binaryTargetNode.getDescription() instanceof AppleTestDescription) {
return resolver.getRule(binary);
}
// If the binary target of the AppleBundle is an AppleLibrary then the build flavor
// must be specified.
if (binaryTargetNode.getDescription() instanceof AppleLibraryDescription
&& (Sets.intersection(
AppleBundleDescription.SUPPORTED_LIBRARY_FLAVORS, buildTarget.getFlavors())
.size()
!= 1)) {
throw new HumanReadableException(
"AppleExtension bundle [%s] must have exactly one of these flavors: [%s].",
binaryTargetNode.getBuildTarget().toString(),
Joiner.on(", ").join(AppleBundleDescription.SUPPORTED_LIBRARY_FLAVORS));
}
if (!StripStyle.FLAVOR_DOMAIN.containsAnyOf(buildTarget.getFlavors())) {
buildTarget = buildTarget.withAppendedFlavors(StripStyle.NON_GLOBAL_SYMBOLS.getFlavor());
}
return resolver.requireRule(buildTarget);
}
private static BuildRuleParams getBundleParamsWithUpdatedDeps(
final BuildRuleParams params,
final BuildTarget originalBinaryTarget,
final Set<BuildRule> newDeps) {
// Remove the unflavored binary rule and add the flavored one instead.
final Predicate<BuildRule> notOriginalBinaryRule =
Predicates.not(BuildRules.isBuildRuleWithTarget(originalBinaryTarget));
return params.copyReplacingDeclaredAndExtraDeps(
Suppliers.ofInstance(
FluentIterable.from(params.getDeclaredDeps().get())
.filter(notOriginalBinaryRule)
.append(newDeps)
.toSortedSet(Ordering.natural())),
Suppliers.ofInstance(
FluentIterable.from(params.getExtraDeps().get())
.filter(notOriginalBinaryRule)
.toSortedSet(Ordering.natural())));
}
private static ImmutableMap<SourcePath, String> collectFirstLevelAppleDependencyBundles(
ImmutableSortedSet<BuildRule> deps, AppleBundleDestinations destinations) {
ImmutableMap.Builder<SourcePath, String> extensionBundlePaths = ImmutableMap.builder();
// We only care about the direct layer of dependencies. ExtensionBundles inside ExtensionBundles
// do not get pulled in to the top-level Bundle.
for (BuildRule rule : deps) {
if (rule instanceof AppleBundle) {
AppleBundle appleBundle = (AppleBundle) rule;
SourcePath sourcePath =
Preconditions.checkNotNull(
appleBundle.getSourcePathToOutput(),
"Path cannot be null for AppleBundle [%s].",
appleBundle);
if (AppleBundleExtension.APPEX.toFileExtension().equals(appleBundle.getExtension())
|| AppleBundleExtension.APP.toFileExtension().equals(appleBundle.getExtension())) {
Path destinationPath;
String platformName = appleBundle.getPlatformName();
if ((platformName.equals(ApplePlatform.WATCHOS.getName())
|| platformName.equals(ApplePlatform.WATCHSIMULATOR.getName()))
&& appleBundle.getExtension().equals(AppleBundleExtension.APP.toFileExtension())) {
destinationPath = destinations.getWatchAppPath();
} else if (appleBundle.isLegacyWatchApp()) {
destinationPath = destinations.getResourcesPath();
} else {
destinationPath = destinations.getPlugInsPath();
}
extensionBundlePaths.put(sourcePath, destinationPath.toString());
} else if (AppleBundleExtension.FRAMEWORK
.toFileExtension()
.equals(appleBundle.getExtension())) {
extensionBundlePaths.put(sourcePath, destinations.getFrameworksPath().toString());
}
}
}
return extensionBundlePaths.build();
}
/**
* Strip flavors that only apply to a bundle from build targets that are passed to constituent
* rules of the bundle, such as its associated binary, asset catalog, etc.
*/
private static BuildRuleParams stripBundleSpecificFlavors(BuildRuleParams params) {
return params.withBuildTarget(params.getBuildTarget().withoutFlavors(BUNDLE_SPECIFIC_FLAVORS));
}
public static boolean flavorsDoNotAllowLinkerMapMode(BuildRuleParams params) {
ImmutableSet<Flavor> flavors = params.getBuildTarget().getFlavors();
return flavors.contains(CxxCompilationDatabase.COMPILATION_DATABASE)
|| flavors.contains(CxxCompilationDatabase.UBER_COMPILATION_DATABASE)
|| flavors.contains(CxxDescriptionEnhancer.STATIC_FLAVOR)
|| flavors.contains(CxxDescriptionEnhancer.STATIC_PIC_FLAVOR)
|| flavors.contains(CxxDescriptionEnhancer.EXPORTED_HEADER_SYMLINK_TREE_FLAVOR)
|| flavors.contains(CxxDescriptionEnhancer.HEADER_SYMLINK_TREE_FLAVOR);
}
}