/*
* Copyright 2015-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.android;
import com.facebook.buck.android.AndroidBinary.RelinkerMode;
import com.facebook.buck.android.relinker.NativeRelinker;
import com.facebook.buck.cxx.CxxBuckConfig;
import com.facebook.buck.cxx.CxxPlatform;
import com.facebook.buck.cxx.NativeLinkable;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.Flavor;
import com.facebook.buck.model.InternalFlavor;
import com.facebook.buck.model.Pair;
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.util.immutables.BuckStyleImmutable;
import com.google.common.base.Preconditions;
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.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import org.immutables.value.Value;
public class AndroidNativeLibsPackageableGraphEnhancer {
private static final String COPY_NATIVE_LIBS = "copy_native_libs";
private final BuildTarget originalBuildTarget;
private final BuildRuleParams buildRuleParams;
private final BuildRuleResolver ruleResolver;
private final SourcePathResolver pathResolver;
private final SourcePathRuleFinder ruleFinder;
private final ImmutableSet<NdkCxxPlatforms.TargetCpuType> cpuFilters;
private final CxxBuckConfig cxxBuckConfig;
private final Optional<Map<String, List<Pattern>>> nativeLibraryMergeMap;
private final Optional<BuildTarget> nativeLibraryMergeGlue;
private final Optional<ImmutableSortedSet<String>> nativeLibraryMergeLocalizedSymbols;
private final RelinkerMode relinkerMode;
private final APKModuleGraph apkModuleGraph;
/**
* Maps a {@link NdkCxxPlatforms.TargetCpuType} to the {@link CxxPlatform} we need to use to build
* C/C++ libraries for it.
*/
private final ImmutableMap<NdkCxxPlatforms.TargetCpuType, NdkCxxPlatform> nativePlatforms;
public AndroidNativeLibsPackageableGraphEnhancer(
BuildRuleResolver ruleResolver,
BuildRuleParams originalParams,
ImmutableMap<NdkCxxPlatforms.TargetCpuType, NdkCxxPlatform> nativePlatforms,
ImmutableSet<NdkCxxPlatforms.TargetCpuType> cpuFilters,
CxxBuckConfig cxxBuckConfig,
Optional<Map<String, List<Pattern>>> nativeLibraryMergeMap,
Optional<BuildTarget> nativeLibraryMergeGlue,
Optional<ImmutableSortedSet<String>> nativeLibraryMergeLocalizedSymbols,
RelinkerMode relinkerMode,
APKModuleGraph apkModuleGraph) {
this.originalBuildTarget = originalParams.getBuildTarget();
this.ruleFinder = new SourcePathRuleFinder(ruleResolver);
this.nativeLibraryMergeLocalizedSymbols = nativeLibraryMergeLocalizedSymbols;
this.pathResolver = new SourcePathResolver(ruleFinder);
this.buildRuleParams = originalParams;
this.ruleResolver = ruleResolver;
this.nativePlatforms = nativePlatforms;
this.cpuFilters = cpuFilters;
this.cxxBuckConfig = cxxBuckConfig;
this.nativeLibraryMergeMap = nativeLibraryMergeMap;
this.nativeLibraryMergeGlue = nativeLibraryMergeGlue;
this.relinkerMode = relinkerMode;
this.apkModuleGraph = apkModuleGraph;
}
@Value.Immutable
@BuckStyleImmutable
interface AbstractAndroidNativeLibsGraphEnhancementResult {
Optional<ImmutableMap<APKModule, CopyNativeLibraries>> getCopyNativeLibraries();
Optional<ImmutableSortedMap<String, String>> getSonameMergeMap();
}
// Populates an immutable map builder with all given linkables set to the given cpu type.
// Returns true iff linkables is not empty.
private boolean populateMapWithLinkables(
Iterable<NativeLinkable> linkables,
ImmutableMap.Builder<Pair<NdkCxxPlatforms.TargetCpuType, String>, SourcePath> builder,
NdkCxxPlatforms.TargetCpuType targetCpuType,
NdkCxxPlatform platform)
throws NoSuchBuildTargetException {
boolean hasNativeLibs = false;
for (NativeLinkable nativeLinkable : linkables) {
if (nativeLinkable.getPreferredLinkage(platform.getCxxPlatform())
!= NativeLinkable.Linkage.STATIC) {
ImmutableMap<String, SourcePath> solibs =
nativeLinkable.getSharedLibraries(platform.getCxxPlatform());
for (Map.Entry<String, SourcePath> entry : solibs.entrySet()) {
builder.put(new Pair<>(targetCpuType, entry.getKey()), entry.getValue());
hasNativeLibs = true;
}
}
}
return hasNativeLibs;
}
public AndroidNativeLibsGraphEnhancementResult enhance(
AndroidPackageableCollection packageableCollection) throws NoSuchBuildTargetException {
@SuppressWarnings("PMD.PrematureDeclaration")
AndroidNativeLibsGraphEnhancementResult.Builder resultBuilder =
AndroidNativeLibsGraphEnhancementResult.builder();
ImmutableMultimap<APKModule, NativeLinkable> nativeLinkables =
packageableCollection.getNativeLinkables();
ImmutableMultimap<APKModule, NativeLinkable> nativeLinkablesAssets =
packageableCollection.getNativeLinkablesAssets();
if (nativeLibraryMergeMap.isPresent() && !nativeLibraryMergeMap.get().isEmpty()) {
NativeLibraryMergeEnhancementResult enhancement =
NativeLibraryMergeEnhancer.enhance(
cxxBuckConfig,
ruleResolver,
pathResolver,
ruleFinder,
buildRuleParams,
nativePlatforms,
nativeLibraryMergeMap.get(),
nativeLibraryMergeGlue,
nativeLibraryMergeLocalizedSymbols,
nativeLinkables,
nativeLinkablesAssets);
nativeLinkables = enhancement.getMergedLinkables();
nativeLinkablesAssets = enhancement.getMergedLinkablesAssets();
resultBuilder.setSonameMergeMap(enhancement.getSonameMapping());
}
// Iterate over all the {@link AndroidNativeLinkable}s from the collector and grab the shared
// libraries for all the {@link TargetCpuType}s that we care about. We deposit them into a map
// of CPU type and SONAME to the shared library path, which the {@link CopyNativeLibraries}
// rule will use to compose the destination name.
ImmutableMap.Builder<APKModule, CopyNativeLibraries> moduleMappedCopyNativeLibriesBuilder =
ImmutableMap.builder();
boolean hasCopyNativeLibraries = false;
List<NdkCxxPlatform> platformsWithNativeLibs = new ArrayList<>();
List<NdkCxxPlatform> platformsWithNativeLibsAssets = new ArrayList<>();
// Make sure we process the root module last so that we know if any of the module contain
// libraries that depend on a non-system runtime and add it to the root module if needed.
ImmutableSet<APKModule> apkModules =
FluentIterable.from(apkModuleGraph.getAPKModules())
.filter(input -> !input.isRootModule())
.append(apkModuleGraph.getRootAPKModule())
.toSet();
for (APKModule module : apkModules) {
ImmutableMap.Builder<Pair<NdkCxxPlatforms.TargetCpuType, String>, SourcePath>
nativeLinkableLibsBuilder = ImmutableMap.builder();
ImmutableMap.Builder<Pair<NdkCxxPlatforms.TargetCpuType, String>, SourcePath>
nativeLinkableLibsAssetsBuilder = ImmutableMap.builder();
// TODO(agallagher): We currently treat an empty set of filters to mean to allow everything.
// We should fix this by assigning a default list of CPU filters in the descriptions, but
// until we do, if the set of filters is empty, just build for all available platforms.
ImmutableSet<NdkCxxPlatforms.TargetCpuType> filters =
cpuFilters.isEmpty() ? nativePlatforms.keySet() : cpuFilters;
for (NdkCxxPlatforms.TargetCpuType targetCpuType : filters) {
NdkCxxPlatform platform =
Preconditions.checkNotNull(
nativePlatforms.get(targetCpuType),
"Unknown platform type " + targetCpuType.toString());
// Populate nativeLinkableLibs and nativeLinkableLibsAssets with the appropriate entries.
if (populateMapWithLinkables(
nativeLinkables.get(module), nativeLinkableLibsBuilder, targetCpuType, platform)
&& !platformsWithNativeLibs.contains(platform)) {
platformsWithNativeLibs.add(platform);
}
if (populateMapWithLinkables(
nativeLinkablesAssets.get(module),
nativeLinkableLibsAssetsBuilder,
targetCpuType,
platform)
&& !platformsWithNativeLibsAssets.contains(platform)) {
platformsWithNativeLibsAssets.add(platform);
}
if (module.isRootModule()) {
// If we're using a C/C++ runtime other than the system one, add it to the APK.
NdkCxxRuntime cxxRuntime = platform.getCxxRuntime();
if ((platformsWithNativeLibs.contains(platform)
|| platformsWithNativeLibsAssets.contains(platform))
&& !cxxRuntime.equals(NdkCxxRuntime.SYSTEM)) {
nativeLinkableLibsBuilder.put(
new Pair<>(targetCpuType, cxxRuntime.getSoname()),
new PathSourcePath(
buildRuleParams.getProjectFilesystem(),
platform.getCxxSharedRuntimePath().get()));
}
}
}
ImmutableMap<Pair<NdkCxxPlatforms.TargetCpuType, String>, SourcePath> nativeLinkableLibs =
nativeLinkableLibsBuilder.build();
ImmutableMap<Pair<NdkCxxPlatforms.TargetCpuType, String>, SourcePath>
nativeLinkableLibsAssets = nativeLinkableLibsAssetsBuilder.build();
if (packageableCollection.getNativeLibsDirectories().get(module).isEmpty()
&& nativeLinkableLibs.isEmpty()
&& nativeLinkableLibsAssets.isEmpty()) {
continue;
}
if (relinkerMode == RelinkerMode.ENABLED
&& (!nativeLinkableLibs.isEmpty() || !nativeLinkableLibsAssets.isEmpty())) {
NativeRelinker relinker =
new NativeRelinker(
buildRuleParams.copyReplacingExtraDeps(
Suppliers.ofInstance(
ImmutableSortedSet.<BuildRule>naturalOrder()
.addAll(ruleFinder.filterBuildRuleInputs(nativeLinkableLibs.values()))
.addAll(
ruleFinder.filterBuildRuleInputs(nativeLinkableLibsAssets.values()))
.build())),
pathResolver,
ruleFinder,
cxxBuckConfig,
nativePlatforms,
nativeLinkableLibs,
nativeLinkableLibsAssets);
nativeLinkableLibs = relinker.getRelinkedLibs();
nativeLinkableLibsAssets = relinker.getRelinkedLibsAssets();
for (BuildRule rule : relinker.getRules()) {
ruleResolver.addToIndex(rule);
}
}
ImmutableMap<StripLinkable, StrippedObjectDescription> strippedLibsMap =
generateStripRules(
buildRuleParams,
ruleFinder,
ruleResolver,
originalBuildTarget,
nativePlatforms,
nativeLinkableLibs);
ImmutableMap<StripLinkable, StrippedObjectDescription> strippedLibsAssetsMap =
generateStripRules(
buildRuleParams,
ruleFinder,
ruleResolver,
originalBuildTarget,
nativePlatforms,
nativeLinkableLibsAssets);
ImmutableSortedSet<BuildRule> nativeLibsRules =
BuildRules.toBuildRulesFor(
originalBuildTarget,
ruleResolver,
packageableCollection.getNativeLibsTargets().get(module));
BuildRuleParams paramsForCopyNativeLibraries =
buildRuleParams
.withAppendedFlavor(InternalFlavor.of(COPY_NATIVE_LIBS + "_" + module.getName()))
.copyReplacingDeclaredAndExtraDeps(
Suppliers.ofInstance(
ImmutableSortedSet.<BuildRule>naturalOrder()
.addAll(nativeLibsRules)
.addAll(
ruleFinder.filterBuildRuleInputs(
packageableCollection.getNativeLibsDirectories().get(module)))
.addAll(strippedLibsMap.keySet())
.addAll(strippedLibsAssetsMap.keySet())
.build()),
Suppliers.ofInstance(ImmutableSortedSet.of()));
moduleMappedCopyNativeLibriesBuilder.put(
module,
new CopyNativeLibraries(
paramsForCopyNativeLibraries,
ImmutableSet.copyOf(packageableCollection.getNativeLibsDirectories().get(module)),
ImmutableSet.copyOf(strippedLibsMap.values()),
ImmutableSet.copyOf(strippedLibsAssetsMap.values()),
cpuFilters,
module.getName()));
hasCopyNativeLibraries = true;
}
return resultBuilder
.setCopyNativeLibraries(
hasCopyNativeLibraries
? Optional.of(moduleMappedCopyNativeLibriesBuilder.build())
: Optional.empty())
.build();
}
// Note: this method produces rules that will be shared between multiple apps,
// so be careful not to let information about this particular app slip into the definitions.
private static ImmutableMap<StripLinkable, StrippedObjectDescription> generateStripRules(
BuildRuleParams buildRuleParams,
SourcePathRuleFinder ruleFinder,
BuildRuleResolver ruleResolver,
BuildTarget appRuleTarget,
ImmutableMap<NdkCxxPlatforms.TargetCpuType, NdkCxxPlatform> nativePlatforms,
ImmutableMap<Pair<NdkCxxPlatforms.TargetCpuType, String>, SourcePath> libs) {
ImmutableMap.Builder<StripLinkable, StrippedObjectDescription> result = ImmutableMap.builder();
for (Map.Entry<Pair<NdkCxxPlatforms.TargetCpuType, String>, SourcePath> entry :
libs.entrySet()) {
SourcePath sourcePath = entry.getValue();
NdkCxxPlatforms.TargetCpuType targetCpuType = entry.getKey().getFirst();
NdkCxxPlatform platform = Preconditions.checkNotNull(nativePlatforms.get(targetCpuType));
// To be safe, default to using the app rule target as the base for the strip rule.
// This will be used for stripping the C++ runtime. We could use something more easily
// shareable (like just using the app's containing directory, or even the repo root),
// but stripping the C++ runtime is pretty fast, so just keep the safe old behavior for now.
BuildTarget baseBuildTarget = appRuleTarget;
// But if we're stripping a cxx_library, use that library as the base of the target
// to allow sharing the rule between all apps that depend on it.
if (sourcePath instanceof BuildTargetSourcePath) {
baseBuildTarget = ((BuildTargetSourcePath) sourcePath).getTarget();
}
String sharedLibrarySoName = entry.getKey().getSecond();
BuildTarget targetForStripRule =
BuildTarget.builder(baseBuildTarget)
.addFlavors(InternalFlavor.of("android-strip"))
.addFlavors(InternalFlavor.of(Flavor.replaceInvalidCharacters(sharedLibrarySoName)))
.addFlavors(InternalFlavor.of(Flavor.replaceInvalidCharacters(targetCpuType.name())))
.build();
Optional<BuildRule> previouslyCreated = ruleResolver.getRuleOptional(targetForStripRule);
StripLinkable stripLinkable;
if (previouslyCreated.isPresent()) {
stripLinkable = (StripLinkable) previouslyCreated.get();
} else {
BuildRuleParams paramsForStripLinkable =
buildRuleParams
.withBuildTarget(targetForStripRule)
.copyReplacingDeclaredAndExtraDeps(
Suppliers.ofInstance(
ImmutableSortedSet.<BuildRule>naturalOrder()
.addAll(ruleFinder.filterBuildRuleInputs(ImmutableList.of(sourcePath)))
.build()),
Suppliers.ofInstance(ImmutableSortedSet.of()));
stripLinkable =
new StripLinkable(
paramsForStripLinkable,
platform.getCxxPlatform().getStrip(),
sourcePath,
sharedLibrarySoName);
ruleResolver.addToIndex(stripLinkable);
}
result.put(
stripLinkable,
StrippedObjectDescription.builder()
.setSourcePath(stripLinkable.getSourcePathToOutput())
.setStrippedObjectName(sharedLibrarySoName)
.setTargetCpuType(targetCpuType)
.build());
}
return result.build();
}
}