/* * Copyright 2013-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.cxx.NativeLinkable.Linkage; import static com.facebook.buck.swift.SwiftLibraryDescription.isSwiftTarget; 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.Linker; import com.facebook.buck.cxx.LinkerMapMode; import com.facebook.buck.cxx.ProvidesLinkedBinaryDeps; import com.facebook.buck.cxx.StripStyle; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.Either; import com.facebook.buck.model.Flavor; import com.facebook.buck.model.FlavorConvertible; import com.facebook.buck.model.FlavorDomain; import com.facebook.buck.model.Flavored; 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.CellPathResolver; import com.facebook.buck.rules.Description; import com.facebook.buck.rules.ImplicitDepsInferringDescription; import com.facebook.buck.rules.ImplicitFlavorsInferringDescription; import com.facebook.buck.rules.MetadataProvidingDescription; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.rules.TargetNode; import com.facebook.buck.swift.SwiftLibraryDescription; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.immutables.BuckStyleImmutable; import com.facebook.buck.versions.Version; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableCollection; 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.Sets; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.SortedSet; import java.util.function.Function; import org.immutables.value.Value; public class AppleLibraryDescription implements Description<AppleLibraryDescriptionArg>, Flavored, ImplicitDepsInferringDescription< AppleLibraryDescription.AbstractAppleLibraryDescriptionArg>, ImplicitFlavorsInferringDescription, MetadataProvidingDescription<AppleLibraryDescriptionArg> { @SuppressWarnings("PMD") // PMD doesn't understand method references private static final Set<Flavor> SUPPORTED_FLAVORS = ImmutableSet.of( CxxCompilationDatabase.COMPILATION_DATABASE, CxxCompilationDatabase.UBER_COMPILATION_DATABASE, CxxDescriptionEnhancer.HEADER_SYMLINK_TREE_FLAVOR, CxxDescriptionEnhancer.EXPORTED_HEADER_SYMLINK_TREE_FLAVOR, CxxDescriptionEnhancer.STATIC_FLAVOR, CxxDescriptionEnhancer.SHARED_FLAVOR, AppleDescriptions.FRAMEWORK_FLAVOR, AppleDebugFormat.DWARF_AND_DSYM.getFlavor(), AppleDebugFormat.DWARF.getFlavor(), AppleDebugFormat.NONE.getFlavor(), StripStyle.NON_GLOBAL_SYMBOLS.getFlavor(), StripStyle.ALL_SYMBOLS.getFlavor(), StripStyle.DEBUGGING_SYMBOLS.getFlavor(), LinkerMapMode.NO_LINKER_MAP.getFlavor(), InternalFlavor.of("default")); private enum Type implements FlavorConvertible { HEADERS(CxxDescriptionEnhancer.HEADER_SYMLINK_TREE_FLAVOR), EXPORTED_HEADERS(CxxDescriptionEnhancer.EXPORTED_HEADER_SYMLINK_TREE_FLAVOR), SANDBOX(CxxDescriptionEnhancer.SANDBOX_TREE_FLAVOR), SHARED(CxxDescriptionEnhancer.SHARED_FLAVOR), STATIC_PIC(CxxDescriptionEnhancer.STATIC_PIC_FLAVOR), STATIC(CxxDescriptionEnhancer.STATIC_FLAVOR), MACH_O_BUNDLE(CxxDescriptionEnhancer.MACH_O_BUNDLE_FLAVOR), FRAMEWORK(AppleDescriptions.FRAMEWORK_FLAVOR), ; private final Flavor flavor; Type(Flavor flavor) { this.flavor = flavor; } @Override public Flavor getFlavor() { return flavor; } } public static final FlavorDomain<Type> LIBRARY_TYPE = FlavorDomain.from("C/C++ Library Type", Type.class); private final CxxLibraryDescription delegate; private final SwiftLibraryDescription swiftDelegate; private final FlavorDomain<AppleCxxPlatform> appleCxxPlatformFlavorDomain; private final CxxPlatform defaultCxxPlatform; private final CodeSignIdentityStore codeSignIdentityStore; private final ProvisioningProfileStore provisioningProfileStore; private final AppleConfig appleConfig; public AppleLibraryDescription( CxxLibraryDescription delegate, SwiftLibraryDescription swiftDelegate, FlavorDomain<AppleCxxPlatform> appleCxxPlatformFlavorDomain, CxxPlatform defaultCxxPlatform, CodeSignIdentityStore codeSignIdentityStore, ProvisioningProfileStore provisioningProfileStore, AppleConfig appleConfig) { this.delegate = delegate; this.swiftDelegate = swiftDelegate; this.appleCxxPlatformFlavorDomain = appleCxxPlatformFlavorDomain; this.defaultCxxPlatform = defaultCxxPlatform; this.codeSignIdentityStore = codeSignIdentityStore; this.provisioningProfileStore = provisioningProfileStore; this.appleConfig = appleConfig; } @Override public Class<AppleLibraryDescriptionArg> getConstructorArgType() { return AppleLibraryDescriptionArg.class; } @Override public Optional<ImmutableSet<FlavorDomain<?>>> flavorDomains() { ImmutableSet.Builder<FlavorDomain<?>> builder = ImmutableSet.builder(); ImmutableSet<FlavorDomain<?>> localDomains = ImmutableSet.of(AppleDebugFormat.FLAVOR_DOMAIN); builder.addAll(localDomains); delegate.flavorDomains().ifPresent(domains -> builder.addAll(domains)); swiftDelegate.flavorDomains().ifPresent(domains -> builder.addAll(domains)); ImmutableSet<FlavorDomain<?>> result = builder.build(); // Drop StripStyle because it's overridden by AppleDebugFormat result = result .stream() .filter(domain -> !domain.equals(StripStyle.FLAVOR_DOMAIN)) .collect(MoreCollectors.toImmutableSet()); return Optional.of(result); } @Override public boolean hasFlavors(ImmutableSet<Flavor> flavors) { return FluentIterable.from(flavors).allMatch(SUPPORTED_FLAVORS::contains) || delegate.hasFlavors(flavors) || swiftDelegate.hasFlavors(flavors); } @Override public BuildRule createBuildRule( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, CellPathResolver cellRoots, AppleLibraryDescriptionArg args) throws NoSuchBuildTargetException { Optional<Map.Entry<Flavor, Type>> type = LIBRARY_TYPE.getFlavorAndValue(params.getBuildTarget()); if (type.isPresent() && type.get().getValue().equals(Type.FRAMEWORK)) { return createFrameworkBundleBuildRule(targetGraph, params, resolver, args); } else { return createLibraryBuildRule( targetGraph, params, resolver, cellRoots, args, args::withExportedDeps, args.getLinkStyle(), Optional.empty(), ImmutableSet.of(), ImmutableSortedSet.of()); } } private <A extends AbstractAppleLibraryDescriptionArg> BuildRule createFrameworkBundleBuildRule( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, AppleLibraryDescriptionArg args) throws NoSuchBuildTargetException { if (!args.getInfoPlist().isPresent()) { throw new HumanReadableException( "Cannot create framework for apple_library '%s':\n" + "No value specified for 'info_plist' attribute.", params.getBuildTarget().getUnflavoredBuildTarget()); } if (!AppleDescriptions.INCLUDE_FRAMEWORKS.getValue(params.getBuildTarget()).isPresent()) { return resolver.requireRule( params.getBuildTarget().withAppendedFlavors(AppleDescriptions.INCLUDE_FRAMEWORKS_FLAVOR)); } AppleDebugFormat debugFormat = AppleDebugFormat.FLAVOR_DOMAIN .getValue(params.getBuildTarget()) .orElse(appleConfig.getDefaultDebugInfoFormatForLibraries()); if (!params.getBuildTarget().getFlavors().contains(debugFormat.getFlavor())) { return resolver.requireRule( params.getBuildTarget().withAppendedFlavors(debugFormat.getFlavor())); } return AppleDescriptions.createAppleBundle( delegate.getCxxPlatforms(), defaultCxxPlatform, appleCxxPlatformFlavorDomain, targetGraph, params, resolver, codeSignIdentityStore, provisioningProfileStore, params.getBuildTarget(), Either.ofLeft(AppleBundleExtension.FRAMEWORK), Optional.empty(), args.getInfoPlist().get(), args.getInfoPlistSubstitutions(), args.getDeps(), args.getTests(), debugFormat, appleConfig.useDryRunCodeSigning(), appleConfig.cacheBundlesAndPackages()); } /** * @param targetGraph The target graph. * @param cellRoots The roots of known cells. * @param bundleLoader The binary in which the current library will be (dynamically) loaded into. */ public <A extends AppleNativeTargetDescriptionArg> BuildRule createLibraryBuildRule( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, CellPathResolver cellRoots, A args, Function<ImmutableSortedSet<BuildTarget>, A> copyArgsWithNewExportedDeps, Optional<Linker.LinkableDepType> linkableDepType, Optional<SourcePath> bundleLoader, ImmutableSet<BuildTarget> blacklist, ImmutableSortedSet<BuildTarget> extraCxxDeps) throws NoSuchBuildTargetException { // We explicitly remove flavors from params to make sure rule // has the same output regardless if we will strip or not. Optional<StripStyle> flavoredStripStyle = StripStyle.FLAVOR_DOMAIN.getValue(params.getBuildTarget()); params = CxxStrip.removeStripStyleFlavorInParams(params, flavoredStripStyle); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver)); BuildRule unstrippedBinaryRule = requireUnstrippedBuildRule( params, resolver, cellRoots, targetGraph, args, copyArgsWithNewExportedDeps, linkableDepType, bundleLoader, blacklist, pathResolver, extraCxxDeps); if (!shouldWrapIntoDebuggableBinary(params.getBuildTarget(), unstrippedBinaryRule)) { return unstrippedBinaryRule; } // If we built a multiarch binary, we can just use the strip tool from any platform. // We pick the platform in this odd way due to FlavorDomain's restriction of allowing only one // matching flavor in the build target. CxxPlatform representativePlatform = delegate .getCxxPlatforms() .getValue( Iterables.getFirst( Sets.intersection( delegate.getCxxPlatforms().getFlavors(), params.getBuildTarget().getFlavors()), defaultCxxPlatform.getFlavor())); params = CxxStrip.restoreStripStyleFlavorInParams(params, flavoredStripStyle); BuildRule strippedBinaryRule = CxxDescriptionEnhancer.createCxxStripRule( params, resolver, flavoredStripStyle.orElse(StripStyle.NON_GLOBAL_SYMBOLS), unstrippedBinaryRule, representativePlatform); return AppleDescriptions.createAppleDebuggableBinary( params, resolver, strippedBinaryRule, (ProvidesLinkedBinaryDeps) unstrippedBinaryRule, AppleDebugFormat.FLAVOR_DOMAIN .getValue(params.getBuildTarget()) .orElse(appleConfig.getDefaultDebugInfoFormatForLibraries()), delegate.getCxxPlatforms(), delegate.getDefaultCxxPlatform(), appleCxxPlatformFlavorDomain); } private <A extends AppleNativeTargetDescriptionArg> BuildRule requireUnstrippedBuildRule( BuildRuleParams params, BuildRuleResolver resolver, CellPathResolver cellRoots, TargetGraph targetGraph, A args, Function<ImmutableSortedSet<BuildTarget>, A> copyArgsWithNewExportedDeps, Optional<Linker.LinkableDepType> linkableDepType, Optional<SourcePath> bundleLoader, ImmutableSet<BuildTarget> blacklist, SourcePathResolver pathResolver, ImmutableSortedSet<BuildTarget> extraCxxDeps) throws NoSuchBuildTargetException { Optional<MultiarchFileInfo> multiarchFileInfo = MultiarchFileInfos.create(appleCxxPlatformFlavorDomain, params.getBuildTarget()); if (multiarchFileInfo.isPresent()) { ImmutableSortedSet.Builder<BuildRule> thinRules = ImmutableSortedSet.naturalOrder(); for (BuildTarget thinTarget : multiarchFileInfo.get().getThinTargets()) { thinRules.add( requireSingleArchUnstrippedBuildRule( params.withBuildTarget(thinTarget), resolver, cellRoots, targetGraph, args, copyArgsWithNewExportedDeps, linkableDepType, bundleLoader, blacklist, pathResolver, extraCxxDeps)); } return MultiarchFileInfos.requireMultiarchRule( // In the same manner that debug flavors are omitted from single-arch constituents, they // are omitted here as well. params.withBuildTarget( params.getBuildTarget().withoutFlavors(AppleDebugFormat.FLAVOR_DOMAIN.getFlavors())), resolver, multiarchFileInfo.get(), thinRules.build()); } else { return requireSingleArchUnstrippedBuildRule( params, resolver, cellRoots, targetGraph, args, copyArgsWithNewExportedDeps, linkableDepType, bundleLoader, blacklist, pathResolver, extraCxxDeps); } } private <A extends AppleNativeTargetDescriptionArg> BuildRule requireSingleArchUnstrippedBuildRule( BuildRuleParams params, BuildRuleResolver resolver, CellPathResolver cellRoots, TargetGraph targetGraph, A args, Function<ImmutableSortedSet<BuildTarget>, A> copyArgsWithNewExportedDeps, Optional<Linker.LinkableDepType> linkableDepType, Optional<SourcePath> bundleLoader, ImmutableSet<BuildTarget> blacklist, SourcePathResolver pathResolver, ImmutableSortedSet<BuildTarget> extraCxxDeps) throws NoSuchBuildTargetException { Optional<BuildRule> swiftCompanionBuildRule = swiftDelegate.createCompanionBuildRule(targetGraph, params, resolver, cellRoots, args); if (swiftCompanionBuildRule.isPresent()) { // when creating a swift target, there is no need to proceed with apple binary rules, // otherwise, add this swift rule as a dependency. if (isSwiftTarget(params.getBuildTarget())) { return swiftCompanionBuildRule.get(); } else { args = copyArgsWithNewExportedDeps.apply( ImmutableSortedSet.<BuildTarget>naturalOrder() .addAll(args.getExportedDeps()) .add(swiftCompanionBuildRule.get().getBuildTarget()) .build()); params = params.copyAppendingExtraDeps(ImmutableSet.of(swiftCompanionBuildRule.get())); extraCxxDeps = ImmutableSortedSet.<BuildTarget>naturalOrder() .addAll(extraCxxDeps) .add(swiftCompanionBuildRule.get().getBuildTarget()) .build(); } } CxxLibraryDescriptionArg.Builder delegateArg = CxxLibraryDescriptionArg.builder().from(args); AppleDescriptions.populateCxxLibraryDescriptionArg( pathResolver, delegateArg, args, params.getBuildTarget()); // remove some flavors from cxx rule that don't affect the rule output BuildTarget unstrippedTarget = params.getBuildTarget().withoutFlavors(AppleDebugFormat.FLAVOR_DOMAIN.getFlavors()); if (AppleDescriptions.flavorsDoNotAllowLinkerMapMode(params)) { unstrippedTarget = unstrippedTarget.withoutFlavors(LinkerMapMode.NO_LINKER_MAP.getFlavor()); } Optional<BuildRule> existingRule = resolver.getRuleOptional(unstrippedTarget); if (existingRule.isPresent()) { return existingRule.get(); } else { BuildRule rule = delegate.createBuildRule( params.withBuildTarget(unstrippedTarget), resolver, cellRoots, delegateArg.build(), linkableDepType, bundleLoader, blacklist, extraCxxDeps); return resolver.addToIndex(rule); } } private boolean shouldWrapIntoDebuggableBinary(BuildTarget buildTarget, BuildRule buildRule) { if (!AppleDebugFormat.FLAVOR_DOMAIN.getValue(buildTarget).isPresent()) { return false; } if (!buildTarget.getFlavors().contains(CxxDescriptionEnhancer.SHARED_FLAVOR) && !buildTarget.getFlavors().contains(CxxDescriptionEnhancer.MACH_O_BUNDLE_FLAVOR)) { return false; } return AppleDebuggableBinary.isBuildRuleDebuggable(buildRule); } <U> Optional<U> createMetadataForLibrary( BuildTarget buildTarget, BuildRuleResolver resolver, Optional<ImmutableMap<BuildTarget, Version>> selectedVersions, AppleNativeTargetDescriptionArg args, Class<U> metadataClass) throws NoSuchBuildTargetException { // Forward to C/C++ library description. if (CxxLibraryDescription.METADATA_TYPE.containsAnyOf(buildTarget.getFlavors())) { CxxLibraryDescriptionArg.Builder delegateArg = CxxLibraryDescriptionArg.builder().from(args); AppleDescriptions.populateCxxLibraryDescriptionArg( new SourcePathResolver(new SourcePathRuleFinder(resolver)), delegateArg, args, buildTarget); return delegate.createMetadata( buildTarget, resolver, delegateArg.build(), selectedVersions, metadataClass); } if (metadataClass.isAssignableFrom(FrameworkDependencies.class) && buildTarget.getFlavors().contains(AppleDescriptions.FRAMEWORK_FLAVOR)) { Optional<Flavor> cxxPlatformFlavor = delegate.getCxxPlatforms().getFlavor(buildTarget); Preconditions.checkState( cxxPlatformFlavor.isPresent(), "Could not find cxx platform in:\n%s", Joiner.on(", ").join(buildTarget.getFlavors())); ImmutableSet.Builder<SourcePath> sourcePaths = ImmutableSet.builder(); for (BuildTarget dep : args.getDeps()) { Optional<FrameworkDependencies> frameworks = resolver.requireMetadata( BuildTarget.builder(dep) .addFlavors(AppleDescriptions.FRAMEWORK_FLAVOR) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .addFlavors(cxxPlatformFlavor.get()) .build(), FrameworkDependencies.class); if (frameworks.isPresent()) { sourcePaths.addAll(frameworks.get().getSourcePaths()); } } // Not all parts of Buck use require yet, so require the rule here so it's available in the // resolver for the parts that don't. BuildRule buildRule = resolver.requireRule(buildTarget); sourcePaths.add(buildRule.getSourcePathToOutput()); return Optional.of(metadataClass.cast(FrameworkDependencies.of(sourcePaths.build()))); } return Optional.empty(); } @Override public <U> Optional<U> createMetadata( BuildTarget buildTarget, BuildRuleResolver resolver, AppleLibraryDescriptionArg args, Optional<ImmutableMap<BuildTarget, Version>> selectedVersions, Class<U> metadataClass) throws NoSuchBuildTargetException { return createMetadataForLibrary(buildTarget, resolver, selectedVersions, args, metadataClass); } @Override public ImmutableSortedSet<Flavor> addImplicitFlavors( ImmutableSortedSet<Flavor> argDefaultFlavors) { // Use defaults.apple_library if present, but fall back to defaults.cxx_library otherwise. return delegate.addImplicitFlavorsForRuleTypes( argDefaultFlavors, Description.getBuildRuleType(this), Description.getBuildRuleType(CxxLibraryDescription.class)); } @Override public void findDepsForTargetFromConstructorArgs( final BuildTarget buildTarget, final CellPathResolver cellRoots, final AbstractAppleLibraryDescriptionArg constructorArg, ImmutableCollection.Builder<BuildTarget> extraDepsBuilder, ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) { findDepsForTargetFromConstructorArgs( buildTarget, cellRoots, (AppleNativeTargetDescriptionArg) constructorArg, extraDepsBuilder, targetGraphOnlyDepsBuilder); } public void findDepsForTargetFromConstructorArgs( final BuildTarget buildTarget, final CellPathResolver cellRoots, final AppleNativeTargetDescriptionArg constructorArg, ImmutableCollection.Builder<BuildTarget> extraDepsBuilder, ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) { delegate.findDepsForTargetFromConstructorArgs( buildTarget, cellRoots, constructorArg, extraDepsBuilder, targetGraphOnlyDepsBuilder); } public static boolean isNotStaticallyLinkedLibraryNode( TargetNode<CxxLibraryDescription.CommonArg, ?> node) { SortedSet<Flavor> flavors = node.getBuildTarget().getFlavors(); if (LIBRARY_TYPE.getFlavor(flavors).isPresent()) { return flavors.contains(CxxDescriptionEnhancer.SHARED_FLAVOR) || flavors.contains(CxxDescriptionEnhancer.EXPORTED_HEADER_SYMLINK_TREE_FLAVOR); } else { return node.getConstructorArg().getPreferredLinkage().equals(Optional.of(Linkage.SHARED)); } } @BuckStyleImmutable @Value.Immutable interface AbstractAppleLibraryDescriptionArg extends AppleNativeTargetDescriptionArg { Optional<SourcePath> getInfoPlist(); ImmutableMap<String, String> getInfoPlistSubstitutions(); @Value.Default default boolean isModular() { return false; } } }