Java tutorial
/* * 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.SwiftUtil.Constants.SWIFT_EXTENSION; import com.facebook.buck.cxx.BuildRuleWithBinary; import com.facebook.buck.cxx.CxxBinaryDescription; import com.facebook.buck.cxx.CxxCompilationDatabase; import com.facebook.buck.cxx.CxxConstructorArg; import com.facebook.buck.cxx.CxxDescriptionEnhancer; import com.facebook.buck.cxx.CxxLibraryDescription; 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.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.ImmutableFlavor; 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.SourcePaths; 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.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.Iterables; 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; /** * Common logic for a {@link com.facebook.buck.rules.Description} that creates Apple target rules. */ public class AppleDescriptions { public static final Flavor FRAMEWORK_FLAVOR = ImmutableFlavor.of("framework"); public static final Flavor INCLUDE_FRAMEWORKS_FLAVOR = ImmutableFlavor.of("include-frameworks"); public static final Flavor NO_INCLUDE_FRAMEWORKS_FLAVOR = ImmutableFlavor.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.headerPathPrefix.orElse(buildTarget.getShortName())); } public static ImmutableSortedMap<String, SourcePath> convertAppleHeadersToPublicCxxHeaders( Function<SourcePath, Path> pathResolver, Path headerPathPrefix, CxxLibraryDescription.Arg 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.exportedHeaders); } public static ImmutableSortedMap<String, SourcePath> convertAppleHeadersToPrivateCxxHeaders( Function<SourcePath, Path> pathResolver, Path headerPathPrefix, CxxLibraryDescription.Arg 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.headers)) .putAll(AppleDescriptions.parseAppleHeadersForUseFromOtherTargets(pathResolver, headerPathPrefix, arg.headers)) .putAll(AppleDescriptions.parseAppleHeadersForUseFromTheSameTarget(pathResolver, arg.exportedHeaders)) .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, CxxConstructorArg output, AppleNativeTargetDescriptionArg arg, BuildTarget buildTarget) { 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.srcs) { if (!MorePaths.getFileExtension(resolver.getAbsolutePath(src.getSourcePath())) .equalsIgnoreCase(SWIFT_EXTENSION)) { nonSwiftSrcs.add(src); } } output.srcs = nonSwiftSrcs.build(); output.platformSrcs = arg.platformSrcs; output.headers = SourceList.ofNamedSources(headerMap); output.platformHeaders = arg.platformHeaders; output.prefixHeader = arg.prefixHeader; output.compilerFlags = arg.compilerFlags; output.platformCompilerFlags = arg.platformCompilerFlags; output.langCompilerFlags = arg.langCompilerFlags; output.preprocessorFlags = arg.preprocessorFlags; output.platformPreprocessorFlags = arg.platformPreprocessorFlags; output.langPreprocessorFlags = arg.langPreprocessorFlags; output.linkerFlags = arg.linkerFlags; output.platformLinkerFlags = arg.platformLinkerFlags; output.frameworks = arg.frameworks; output.libraries = arg.libraries; output.deps = arg.deps; // This is intentionally an empty string; we put all prefixes into // the header map itself. output.headerNamespace = Optional.of(""); output.cxxRuntimeType = arg.cxxRuntimeType; output.tests = arg.tests; output.precompiledHeader = arg.precompiledHeader; } public static void populateCxxBinaryDescriptionArg(SourcePathResolver resolver, CxxBinaryDescription.Arg output, AppleNativeTargetDescriptionArg arg, BuildTarget buildTarget) { populateCxxConstructorArg(resolver, output, arg, buildTarget); output.linkStyle = arg.linkStyle; } public static void populateCxxLibraryDescriptionArg(SourcePathResolver resolver, CxxLibraryDescription.Arg output, AppleNativeTargetDescriptionArg arg, BuildTarget buildTarget) { populateCxxConstructorArg(resolver, output, arg, buildTarget); Path headerPathPrefix = AppleDescriptions.getHeaderPathPrefix(arg, buildTarget); output.headers = SourceList.ofNamedSources( convertAppleHeadersToPrivateCxxHeaders(resolver::getRelativePath, headerPathPrefix, arg)); output.exportedDeps = arg.exportedDeps; output.exportedPreprocessorFlags = arg.exportedPreprocessorFlags; output.exportedHeaders = SourceList.ofNamedSources( convertAppleHeadersToPublicCxxHeaders(resolver::getRelativePath, headerPathPrefix, arg)); output.exportedPlatformHeaders = arg.exportedPlatformHeaders; output.exportedPlatformPreprocessorFlags = arg.exportedPlatformPreprocessorFlags; output.exportedLangPreprocessorFlags = arg.exportedLangPreprocessorFlags; output.exportedLinkerFlags = arg.exportedLinkerFlags; output.exportedPlatformLinkerFlags = arg.exportedPlatformLinkerFlags; output.soname = arg.soname; output.forceStatic = arg.forceStatic; output.preferredLinkage = arg.preferredLinkage; output.linkWhole = arg.linkWhole; output.supportedPlatformsRegex = arg.supportedPlatformsRegex; output.canBeAsset = arg.canBeAsset; output.exportedDeps = arg.exportedDeps; output.xcodePublicHeadersSymlinks = arg.xcodePublicHeadersSymlinks; output.xcodePrivateHeadersSymlinks = arg.xcodePrivateHeadersSymlinks; } public static Optional<AppleAssetCatalog> createBuildRuleForTransitiveAssetCatalogDependencies( TargetGraph targetGraph, BuildRuleParams params, SourcePathResolver sourcePathResolver, ApplePlatform applePlatform, Tool actool) { TargetNode<?, ?> targetNode = targetGraph.get(params.getBuildTarget()); ImmutableSet<AppleAssetCatalogDescription.Arg> 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 (AppleAssetCatalogDescription.Arg arg : assetCatalogArgs) { if (optimization == null) { optimization = arg.optimization; } assetCatalogDirsBuilder.addAll(arg.dirs); if (arg.appIcon.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.appIcon; } if (arg.launchImage.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.launchImage; } if (arg.optimization != 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(); } BuildRuleParams assetCatalogParams = params.copyWithChanges( params.getBuildTarget().withAppendedFlavors(AppleAssetCatalog.FLAVOR), Suppliers.ofInstance(ImmutableSortedSet.of()), Suppliers.ofInstance(ImmutableSortedSet.of())); return Optional.of(new AppleAssetCatalog(assetCatalogParams, sourcePathResolver, applePlatform.getName(), 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.copyWithChanges( params.getBuildTarget().withAppendedFlavors(CoreDataModel.FLAVOR), 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.path)) .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.copyWithChanges( params.getBuildTarget().withAppendedFlavors(SceneKitAssets.FLAVOR), 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.path)) .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.copyWithChanges( strippedBinaryRule.getBuildTarget().withAppendedFlavors(AppleDebuggableBinary.RULE_FLAVOR, debugFormat.getFlavor()), Suppliers.ofInstance(AppleDebuggableBinary.getRequiredRuntimeDeps(debugFormat, strippedBinaryRule, unstrippedBinaryRule, appleDsym)), Suppliers.ofInstance(ImmutableSortedSet.of())), new SourcePathResolver(new SourcePathRuleFinder(resolver)), 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.copyWithBuildTarget(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.copyWithDeps( Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>naturalOrder() .add(unstrippedBinaryBuildRule).addAll(unstrippedBinaryBuildRule.getCompileDeps()) .addAll(unstrippedBinaryBuildRule.getStaticLibraryDeps()).build()), Suppliers.ofInstance(ImmutableSortedSet.of())), new SourcePathResolver(new SourcePathRuleFinder(resolver)), appleCxxPlatform.getDsymutil(), appleCxxPlatform.getLldb(), new BuildTargetSourcePath(unstrippedBinaryBuildRule.getBuildTarget()), 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, 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()); } } } 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.getActool()); Optional<CoreDataModel> coreDataModel = createBuildRulesForCoreDataDependencies(targetGraph, paramsWithoutBundleSpecificFlavors, AppleBundle.getBinaryName(params.getBuildTarget(), productName), appleCxxPlatform); Optional<SceneKitAssets> sceneKitAssets = createBuildRulesForSceneKitAssetsDependencies(targetGraph, paramsWithoutBundleSpecificFlavors, appleCxxPlatform); // TODO(bhamiltoncx): 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.copyWithBuildTarget(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, SourcePaths.filterBuildTargetSourcePaths(Iterables .concat(ImmutableList.of(collectedResources.getAll(), frameworks))))) .addAll(OptionalCompat.asSet(appleDsym)).build()); ImmutableMap<SourcePath, String> extensionBundlePaths = collectFirstLevelAppleDependencyBundles( params.getDeps(), destinations); return new AppleBundle(bundleParamsWithFlavoredBinaryDep, sourcePathResolver, extension, productName, infoPlist, infoPlistSubstitutions, Optional.of(getBinaryFromBuildRuleWithBinary(flavoredBinaryRule)), appleDsym, destinations, collectedResources, extensionBundlePaths, frameworks, appleCxxPlatform, assetCatalog, coreDataModel, sceneKitAssets, tests, codeSignIdentityStore, provisioningProfileStore, dryRunCodeSigning, cacheable); } 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.copyWithDeps( 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; Path outputPath = Preconditions.checkNotNull(appleBundle.getPathToOutput(), "Path cannot be null for AppleBundle [%s].", appleBundle); SourcePath sourcePath = new BuildTargetSourcePath(appleBundle.getBuildTarget(), outputPath); 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.copyWithBuildTarget(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); } }