blob: b4228b226e1b3f8023685ed9ac59dfa91b25e40a [file] [log] [blame]
/*
* 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.android;
import com.facebook.buck.android.AndroidBinary.ExopackageMode;
import com.facebook.buck.android.ComputeExopackageDepsAbi.BuildOutput;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.java.Keystore;
import com.facebook.buck.log.Logger;
import com.facebook.buck.rules.AbstractBuildRule;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildOutputInitializer;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildableContext;
import com.facebook.buck.rules.InitializableFromDisk;
import com.facebook.buck.rules.OnDiskBuildInfo;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.rules.Sha1HashCode;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.step.AbstractExecutionStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import java.io.IOException;
import java.nio.file.Path;
import java.util.EnumSet;
import java.util.Map;
import javax.annotation.Nullable;
/**
* A buildable that hashes all of the files that go into an exopackage APK.
* This is used by AndroidBinaryRule to compute the ABI hash of its deps.
*/
public class ComputeExopackageDepsAbi extends AbstractBuildRule
implements InitializableFromDisk<BuildOutput> {
private static final Logger LOG = Logger.get(ComputeExopackageDepsAbi.class);
private static final String METADATA_KEY = "EXOPACKAGE_ABI_OF_DEPS";
private final EnumSet<ExopackageMode> exopackageModes;
private final AndroidPackageableCollection packageableCollection;
private final AaptPackageResources aaptPackageResources;
private final Optional<CopyNativeLibraries> copyNativeLibraries;
private final Optional<PackageStringAssets> packageStringAssets;
private final Optional<PreDexMerge> preDexMerge;
private final Keystore keystore;
private final BuildOutputInitializer<BuildOutput> buildOutputInitializer;
public ComputeExopackageDepsAbi(
BuildRuleParams params,
SourcePathResolver resolver,
EnumSet<ExopackageMode> exopackageModes,
AndroidPackageableCollection packageableCollection,
AaptPackageResources aaptPackageResources,
Optional<CopyNativeLibraries> copyNativeLibraries,
Optional<PackageStringAssets> packageStringAssets,
Optional<PreDexMerge> preDexMerge,
Keystore keystore) {
super(params, resolver);
this.exopackageModes = exopackageModes;
this.packageableCollection = packageableCollection;
this.aaptPackageResources = aaptPackageResources;
this.copyNativeLibraries = copyNativeLibraries;
this.packageStringAssets = packageStringAssets;
this.preDexMerge = preDexMerge;
this.keystore = keystore;
this.buildOutputInitializer = new BuildOutputInitializer<>(params.getBuildTarget(), this);
}
@Override
public ImmutableCollection<Path> getInputsToCompareToOutput() {
return ImmutableList.of();
}
@Override
public ImmutableList<Step> getBuildSteps(
BuildContext context, final BuildableContext buildableContext) {
return ImmutableList.<Step>of(
new AbstractExecutionStep("compute_android_binary_deps_abi") {
@Override
public int execute(ExecutionContext context) {
try {
ProjectFilesystem filesystem = context.getProjectFilesystem();
// For exopackages, the only significant thing android_binary does is apkbuilder,
// so we need to include all of the apkbuilder inputs in the ABI key.
final Hasher hasher = Hashing.sha1().newHasher();
// The first input to apkbuilder is the ap_ produced by aapt package.
// Get its hash from the buildable that created it.
String resourceApkHash = aaptPackageResources.getResourcePackageHash().toString();
LOG.verbose("resource apk = %s", resourceApkHash);
hasher.putUnencodedChars(resourceApkHash);
// Next is the primary dex. Same plan.
String primaryDexHash = Preconditions.checkNotNull(
preDexMerge.get().getPrimaryDexHash())
.toString();
LOG.verbose("primary dex = %s", primaryDexHash);
hasher.putUnencodedChars(primaryDexHash);
// Non-english strings packaged as assets.
if (packageStringAssets.isPresent()) {
String stringAssetsHash =
packageStringAssets.get().getStringAssetsZipHash().toString();
LOG.verbose("string assets = %s", stringAssetsHash);
hasher.putUnencodedChars(stringAssetsHash);
}
// We currently don't use any resource directories, so nothing to add there.
// Collect files whose sha1 hashes need to be added to our ABI key.
// This maps from the file to the role it plays, so changing (for example)
// a native library to an asset will change the ABI key.
// We assume that these are all order-insensitive to avoid having our ABI key
// affected by filesystem iteration order.
ImmutableSortedMap.Builder<Path, String> filesToHash =
ImmutableSortedMap.naturalOrder();
// If exopackage is disabled for secondary dexes, we need to hash the secondary dex
// files that end up in the APK. PreDexMerge already hashes those files, so we can
// just hash the summary of those hashes.
if (!ExopackageMode.enabledForSecondaryDexes(exopackageModes) &&
preDexMerge.isPresent()) {
filesToHash.put(preDexMerge.get().getMetadataTxtPath(), "secondary_dexes");
}
// If exopackage is disabled for native libraries, we add them in apkbuilder, so we
// need to include their hashes. AndroidTransitiveDependencies doesn't provide
// BuildRules, only paths. We could augment it, but our current native libraries are
// small enough that we can just hash them all without too much of a perf hit.
if (!ExopackageMode.enabledForNativeLibraries(exopackageModes) &&
copyNativeLibraries.isPresent()) {
filesToHash.put(copyNativeLibraries.get().getPathToMetadataTxt(), "native_libs");
}
// Same deal for native libs as assets.
for (final Path libDir : packageableCollection.getNativeLibAssetsDirectories()) {
for (Path nativeFile : filesystem.getFilesUnderPath(libDir)) {
filesToHash.put(nativeFile, "native_lib_as_asset");
}
}
// Resources get copied from third-party JARs, so hash them.
for (Path jar : packageableCollection.getPathsToThirdPartyJars()) {
filesToHash.put(jar, "third-party jar");
}
// The last input is the keystore.
filesToHash.put(keystore.getPathToStore(), "keystore");
filesToHash.put(keystore.getPathToPropertiesFile(), "keystore properties");
for (Map.Entry<Path, String> entry : filesToHash.build().entrySet()) {
Path path = entry.getKey();
hasher.putUnencodedChars(path.toString());
hasher.putByte((byte) 0);
String fileSha1 = filesystem.computeSha1(path);
hasher.putUnencodedChars(fileSha1);
hasher.putByte((byte) 0);
hasher.putUnencodedChars(entry.getValue());
hasher.putByte((byte) 0);
LOG.verbose("file %s(%s) = %s", path, entry.getValue(), fileSha1);
}
String abiHash = hasher.hash().toString();
LOG.verbose("ABI hash = %s", abiHash);
buildableContext.addMetadata(METADATA_KEY, abiHash);
return 0;
} catch (IOException e) {
context.logError(e, "Error computing ABI hash.");
return 1;
}
}
});
}
@Override
public RuleKey.Builder appendDetailsToRuleKey(RuleKey.Builder builder) {
return builder;
}
@Nullable
@Override
public Path getPathToOutputFile() {
return null;
}
public Sha1HashCode getAndroidBinaryAbiHash() {
return buildOutputInitializer.getBuildOutput().abiHash;
}
static class BuildOutput {
private final Sha1HashCode abiHash;
BuildOutput(Sha1HashCode abiHash) {
this.abiHash = abiHash;
}
}
@Override
public BuildOutput initializeFromDisk(OnDiskBuildInfo onDiskBuildInfo) {
return new BuildOutput(onDiskBuildInfo.getHash(METADATA_KEY).get());
}
@Override
public BuildOutputInitializer<BuildOutput> getBuildOutputInitializer() {
return buildOutputInitializer;
}
}