/* * 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.swift.SwiftLibraryDescription.isSwiftTarget; import com.facebook.buck.cxx.CxxBinaryDescription; import com.facebook.buck.cxx.CxxBinaryDescriptionArg; import com.facebook.buck.cxx.CxxCompilationDatabase; 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.file.WriteFile; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargets; import com.facebook.buck.model.Either; import com.facebook.buck.model.Flavor; 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.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.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.Sets; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; import java.util.Set; import org.immutables.value.Value; public class AppleBinaryDescription implements Description<AppleBinaryDescriptionArg>, Flavored, ImplicitDepsInferringDescription<AppleBinaryDescription.AbstractAppleBinaryDescriptionArg>, ImplicitFlavorsInferringDescription, MetadataProvidingDescription<AppleBinaryDescriptionArg> { public static final Flavor APP_FLAVOR = InternalFlavor.of("app"); public static final Sets.SetView<Flavor> NON_DELEGATE_FLAVORS = Sets.union(AppleDebugFormat.FLAVOR_DOMAIN.getFlavors(), ImmutableSet.of(APP_FLAVOR)); public static final Flavor LEGACY_WATCH_FLAVOR = InternalFlavor.of("legacy_watch"); @SuppressWarnings("PMD") // PMD doesn't understand method references private static final Set<Flavor> SUPPORTED_FLAVORS = ImmutableSet.of( APP_FLAVOR, CxxCompilationDatabase.COMPILATION_DATABASE, CxxCompilationDatabase.UBER_COMPILATION_DATABASE, AppleDebugFormat.DWARF_AND_DSYM.getFlavor(), AppleDebugFormat.DWARF.getFlavor(), AppleDebugFormat.NONE.getFlavor(), LinkerMapMode.NO_LINKER_MAP.getFlavor()); private final CxxBinaryDescription delegate; private final SwiftLibraryDescription swiftDelegate; private final FlavorDomain<AppleCxxPlatform> platformFlavorsToAppleCxxPlatforms; private final CodeSignIdentityStore codeSignIdentityStore; private final ProvisioningProfileStore provisioningProfileStore; private final AppleConfig appleConfig; public AppleBinaryDescription( CxxBinaryDescription delegate, SwiftLibraryDescription swiftDelegate, FlavorDomain<AppleCxxPlatform> platformFlavorsToAppleCxxPlatforms, CodeSignIdentityStore codeSignIdentityStore, ProvisioningProfileStore provisioningProfileStore, AppleConfig appleConfig) { this.delegate = delegate; this.swiftDelegate = swiftDelegate; this.platformFlavorsToAppleCxxPlatforms = platformFlavorsToAppleCxxPlatforms; this.codeSignIdentityStore = codeSignIdentityStore; this.provisioningProfileStore = provisioningProfileStore; this.appleConfig = appleConfig; } @Override public Class<AppleBinaryDescriptionArg> getConstructorArgType() { return AppleBinaryDescriptionArg.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) { if (FluentIterable.from(flavors).allMatch(SUPPORTED_FLAVORS::contains)) { return true; } ImmutableSet<Flavor> delegateFlavors = ImmutableSet.copyOf(Sets.difference(flavors, NON_DELEGATE_FLAVORS)); if (swiftDelegate.hasFlavors(delegateFlavors)) { return true; } ImmutableList<ImmutableSortedSet<Flavor>> thinFlavorSets = generateThinDelegateFlavors(delegateFlavors); if (thinFlavorSets.size() > 0) { return Iterables.all(thinFlavorSets, delegate::hasFlavors); } else { return delegate.hasFlavors(delegateFlavors); } } private ImmutableList<ImmutableSortedSet<Flavor>> generateThinDelegateFlavors( ImmutableSet<Flavor> delegateFlavors) { return MultiarchFileInfos.generateThinFlavors( platformFlavorsToAppleCxxPlatforms.getFlavors(), ImmutableSortedSet.copyOf(delegateFlavors)); } @Override public BuildRule createBuildRule( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, CellPathResolver cellRoots, AppleBinaryDescriptionArg args) throws NoSuchBuildTargetException { if (params.getBuildTarget().getFlavors().contains(APP_FLAVOR)) { return createBundleBuildRule(targetGraph, params, resolver, args); } else { return createBinaryBuildRule(targetGraph, params, resolver, cellRoots, args); } } // We want to wrap only if we have explicit debug flavor. This is because we don't want to // force dSYM generation in case if its enabled by default in config. We just want the binary, // so unless flavor is explicitly set, lets just produce binary! private boolean shouldWrapIntoAppleDebuggableBinary( BuildTarget buildTarget, BuildRule binaryBuildRule) { Optional<AppleDebugFormat> explicitDebugInfoFormat = AppleDebugFormat.FLAVOR_DOMAIN.getValue(buildTarget); boolean binaryIsWrappable = AppleDebuggableBinary.canWrapBinaryBuildRule(binaryBuildRule); return explicitDebugInfoFormat.isPresent() && binaryIsWrappable; } private BuildRule createBinaryBuildRule( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, CellPathResolver cellRoots, AppleBinaryDescriptionArg args) throws NoSuchBuildTargetException { // remove some flavors so binary will have the same output regardless their values BuildTarget unstrippedBinaryBuildTarget = params .getBuildTarget() .withoutFlavors(AppleDebugFormat.FLAVOR_DOMAIN.getFlavors()) .withoutFlavors(StripStyle.FLAVOR_DOMAIN.getFlavors()); BuildRule unstrippedBinaryRule = createBinary( targetGraph, params.withBuildTarget(unstrippedBinaryBuildTarget), resolver, cellRoots, args); if (shouldWrapIntoAppleDebuggableBinary(params.getBuildTarget(), unstrippedBinaryRule)) { return createAppleDebuggableBinary( targetGraph, params, resolver, cellRoots, args, unstrippedBinaryBuildTarget, (ProvidesLinkedBinaryDeps) unstrippedBinaryRule); } else { return unstrippedBinaryRule; } } private BuildRule createAppleDebuggableBinary( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, CellPathResolver cellRoots, AppleBinaryDescriptionArg args, BuildTarget unstrippedBinaryBuildTarget, ProvidesLinkedBinaryDeps unstrippedBinaryRule) throws NoSuchBuildTargetException { BuildTarget strippedBinaryBuildTarget = unstrippedBinaryBuildTarget.withAppendedFlavors( CxxStrip.RULE_FLAVOR, StripStyle.FLAVOR_DOMAIN .getFlavor(params.getBuildTarget().getFlavors()) .orElse(StripStyle.NON_GLOBAL_SYMBOLS.getFlavor())); BuildRule strippedBinaryRule = createBinary( targetGraph, params.withBuildTarget(strippedBinaryBuildTarget), resolver, cellRoots, args); return AppleDescriptions.createAppleDebuggableBinary( params.withBuildTarget(unstrippedBinaryBuildTarget), resolver, strippedBinaryRule, unstrippedBinaryRule, AppleDebugFormat.FLAVOR_DOMAIN.getRequiredValue(params.getBuildTarget()), delegate.getCxxPlatforms(), delegate.getDefaultCxxPlatform(), platformFlavorsToAppleCxxPlatforms); } private BuildRule createBundleBuildRule( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, AppleBinaryDescriptionArg args) throws NoSuchBuildTargetException { if (!args.getInfoPlist().isPresent()) { throw new HumanReadableException( "Cannot create application for apple_binary '%s':\n", "No value specified for 'info_plist' attribute.", params.getBuildTarget().getUnflavoredBuildTarget()); } AppleDebugFormat flavoredDebugFormat = AppleDebugFormat.FLAVOR_DOMAIN .getValue(params.getBuildTarget()) .orElse(appleConfig.getDefaultDebugInfoFormatForBinaries()); if (!params.getBuildTarget().getFlavors().contains(flavoredDebugFormat.getFlavor())) { return resolver.requireRule( params.getBuildTarget().withAppendedFlavors(flavoredDebugFormat.getFlavor())); } if (!AppleDescriptions.INCLUDE_FRAMEWORKS.getValue(params.getBuildTarget()).isPresent()) { CxxPlatform cxxPlatform = delegate .getCxxPlatforms() .getValue(params.getBuildTarget()) .orElse(delegate.getDefaultCxxPlatform()); ApplePlatform applePlatform = platformFlavorsToAppleCxxPlatforms .getValue(cxxPlatform.getFlavor()) .getAppleSdk() .getApplePlatform(); if (applePlatform.getAppIncludesFrameworks()) { return resolver.requireRule( params .getBuildTarget() .withAppendedFlavors(AppleDescriptions.INCLUDE_FRAMEWORKS_FLAVOR)); } return resolver.requireRule( params .getBuildTarget() .withAppendedFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR)); } BuildTarget binaryTarget = params.withoutFlavor(APP_FLAVOR).getBuildTarget(); return AppleDescriptions.createAppleBundle( delegate.getCxxPlatforms(), delegate.getDefaultCxxPlatform(), platformFlavorsToAppleCxxPlatforms, targetGraph, params, resolver, codeSignIdentityStore, provisioningProfileStore, binaryTarget, Either.ofLeft(AppleBundleExtension.APP), Optional.empty(), args.getInfoPlist().get(), args.getInfoPlistSubstitutions(), args.getDeps(), args.getTests(), flavoredDebugFormat, appleConfig.useDryRunCodeSigning(), appleConfig.cacheBundlesAndPackages()); } private BuildRule createBinary( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, CellPathResolver cellRoots, AppleBinaryDescriptionArg args) throws NoSuchBuildTargetException { if (AppleDescriptions.flavorsDoNotAllowLinkerMapMode(params)) { params = params.withoutFlavor(LinkerMapMode.NO_LINKER_MAP.getFlavor()); } Optional<MultiarchFileInfo> fatBinaryInfo = MultiarchFileInfos.create(platformFlavorsToAppleCxxPlatforms, params.getBuildTarget()); if (fatBinaryInfo.isPresent()) { if (shouldUseStubBinary(params)) { BuildTarget thinTarget = Iterables.getFirst(fatBinaryInfo.get().getThinTargets(), null); return requireThinBinary( targetGraph, params.withBuildTarget(thinTarget), resolver, cellRoots, args); } ImmutableSortedSet.Builder<BuildRule> thinRules = ImmutableSortedSet.naturalOrder(); for (BuildTarget thinTarget : fatBinaryInfo.get().getThinTargets()) { Optional<BuildRule> existingThinRule = resolver.getRuleOptional(thinTarget); if (existingThinRule.isPresent()) { thinRules.add(existingThinRule.get()); continue; } BuildRule thinRule = requireThinBinary( targetGraph, params.withBuildTarget(thinTarget), resolver, cellRoots, args); resolver.addToIndex(thinRule); thinRules.add(thinRule); } return MultiarchFileInfos.requireMultiarchRule( params, resolver, fatBinaryInfo.get(), thinRules.build()); } else { return requireThinBinary(targetGraph, params, resolver, cellRoots, args); } } private BuildRule requireThinBinary( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, CellPathResolver cellRoots, AppleBinaryDescriptionArg args) throws NoSuchBuildTargetException { Optional<BuildRule> existingThinRule = resolver.getRuleOptional(params.getBuildTarget()); if (existingThinRule.isPresent()) { return existingThinRule.get(); } ImmutableSortedSet.Builder<BuildTarget> extraCxxDepsBuilder = ImmutableSortedSet.naturalOrder(); 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 { extraCxxDepsBuilder.add(swiftCompanionBuildRule.get().getBuildTarget()); params = params.copyAppendingExtraDeps(ImmutableSet.of(swiftCompanionBuildRule.get())); } } ImmutableSortedSet<BuildTarget> extraCxxDeps = extraCxxDepsBuilder.build(); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); Optional<Path> stubBinaryPath = getStubBinaryPath(params, args); if (shouldUseStubBinary(params) && stubBinaryPath.isPresent()) { try { return resolver.addToIndex( new WriteFile( params, Files.readAllBytes(stubBinaryPath.get()), BuildTargets.getGenPath( params.getProjectFilesystem(), params.getBuildTarget(), "%s"), true)); } catch (IOException e) { throw new HumanReadableException("Could not read stub binary " + stubBinaryPath.get()); } } else { CxxBinaryDescriptionArg.Builder delegateArg = CxxBinaryDescriptionArg.builder().from(args); AppleDescriptions.populateCxxBinaryDescriptionArg( pathResolver, delegateArg, args, params.getBuildTarget()); return resolver.addToIndex( delegate.createBuildRule( targetGraph, params, resolver, cellRoots, delegateArg.build(), extraCxxDeps)); } } private boolean shouldUseStubBinary(BuildRuleParams params) { ImmutableSortedSet<Flavor> flavors = params.getBuildTarget().getFlavors(); return (flavors.contains(AppleBundleDescription.WATCH_OS_FLAVOR) || flavors.contains(AppleBundleDescription.WATCH_SIMULATOR_FLAVOR) || flavors.contains(LEGACY_WATCH_FLAVOR)); } private Optional<Path> getStubBinaryPath(BuildRuleParams params, AppleBinaryDescriptionArg args) { Optional<Path> stubBinaryPath = Optional.empty(); Optional<AppleCxxPlatform> appleCxxPlatform = getAppleCxxPlatformFromParams(params); if (appleCxxPlatform.isPresent() && args.getSrcs().isEmpty()) { stubBinaryPath = appleCxxPlatform.get().getStubBinary(); } return stubBinaryPath; } private Optional<AppleCxxPlatform> getAppleCxxPlatformFromParams(BuildRuleParams params) { return platformFlavorsToAppleCxxPlatforms.getValue(params.getBuildTarget()); } @Override public <U> Optional<U> createMetadata( BuildTarget buildTarget, BuildRuleResolver resolver, AppleBinaryDescriptionArg args, Optional<ImmutableMap<BuildTarget, Version>> selectedVersions, Class<U> metadataClass) throws NoSuchBuildTargetException { if (!metadataClass.isAssignableFrom(FrameworkDependencies.class)) { CxxBinaryDescriptionArg.Builder delegateArg = CxxBinaryDescriptionArg.builder().from(args); AppleDescriptions.populateCxxBinaryDescriptionArg( new SourcePathResolver(new SourcePathRuleFinder(resolver)), delegateArg, args, buildTarget); return delegate.createMetadata( buildTarget, resolver, delegateArg.build(), selectedVersions, metadataClass); } 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.NO_INCLUDE_FRAMEWORKS_FLAVOR) .addFlavors(cxxPlatformFlavor.get()) .build(), FrameworkDependencies.class); if (frameworks.isPresent()) { sourcePaths.addAll(frameworks.get().getSourcePaths()); } } return Optional.of(metadataClass.cast(FrameworkDependencies.of(sourcePaths.build()))); } @Override public ImmutableSortedSet<Flavor> addImplicitFlavors( ImmutableSortedSet<Flavor> argDefaultFlavors) { // Use defaults.apple_binary if present, but fall back to defaults.cxx_binary otherwise. return delegate.addImplicitFlavorsForRuleTypes( argDefaultFlavors, Description.getBuildRuleType(this), Description.getBuildRuleType(CxxBinaryDescription.class)); } @Override public void findDepsForTargetFromConstructorArgs( final BuildTarget buildTarget, final CellPathResolver cellRoots, final AbstractAppleBinaryDescriptionArg constructorArg, ImmutableCollection.Builder<BuildTarget> extraDepsBuilder, ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) { ImmutableList<ImmutableSortedSet<Flavor>> thinFlavorSets = generateThinDelegateFlavors(buildTarget.getFlavors()); if (thinFlavorSets.size() > 0) { for (ImmutableSortedSet<Flavor> flavors : thinFlavorSets) { extraDepsBuilder.addAll( delegate.findDepsForTargetFromConstructorArgs( buildTarget.withFlavors(flavors), Optional.empty())); } } else { extraDepsBuilder.addAll( delegate.findDepsForTargetFromConstructorArgs(buildTarget, Optional.empty())); } } @BuckStyleImmutable @Value.Immutable interface AbstractAppleBinaryDescriptionArg extends AppleNativeTargetDescriptionArg { Optional<SourcePath> getInfoPlist(); ImmutableMap<String, String> getInfoPlistSubstitutions(); } }