blob: 2bc81802587b469a184fae05b5b5f1b04fd9e62a [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.io.ProjectFilesystem;
import com.facebook.buck.io.ProjectFilesystem.CopySourceMode;
import com.facebook.buck.java.JarDirectoryStepHelper;
import com.facebook.buck.java.JavacOptions;
import com.facebook.buck.java.PrebuiltJar;
import com.facebook.buck.java.PrebuiltJarDescription;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.model.Flavor;
import com.facebook.buck.model.ImmutableFlavor;
import com.facebook.buck.model.UnflavoredBuildTarget;
import com.facebook.buck.rules.AbstractBuildRule;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.BuildRuleType;
import com.facebook.buck.rules.BuildTargetSourcePath;
import com.facebook.buck.rules.BuildableContext;
import com.facebook.buck.rules.ImmutableBuildRuleType;
import com.facebook.buck.rules.OutputOnlyBuildRule;
import com.facebook.buck.rules.RuleKey.Builder;
import com.facebook.buck.rules.SourcePath;
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.facebook.buck.step.fs.MakeCleanDirectoryStep;
import com.facebook.buck.step.fs.MkdirStep;
import com.facebook.buck.step.fs.TouchStep;
import com.facebook.buck.zip.UnzipStep;
import com.google.common.base.Optional;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
class AndroidPrebuiltAarGraphEnhancer {
private static final BuildRuleType UNZIP_AAR_TYPE = ImmutableBuildRuleType.of("unzip_aar");
private static final Flavor AAR_UNZIP_FLAVOR = ImmutableFlavor.of("aar_unzip");
private static final Flavor AAR_CLASSES_JAR_FLAVOR = ImmutableFlavor.of("aar_classes_jar");
private static final Flavor AAR_MANIFEST = ImmutableFlavor.of("aar_manifest");
private static final Flavor AAR_PREBUILT_JAR_FLAVOR = ImmutableFlavor.of("aar_prebuilt_jar");
private static final Flavor AAR_ANDROID_RESOURCE_FLAVOR =
ImmutableFlavor.of("aar_android_resource");
/** Utility class: do not instantiate. */
private AndroidPrebuiltAarGraphEnhancer() {}
/**
* Creates a rooted DAG of build rules:
* <ul>
* <li>{@code unzip_aar} depends on the deps specified to the original {@code android_aar}
* <li>{@code classes_jar} depends on {@code unzip_aar}
* <li>{@code prebuilt_jar} depends on {@code unzip_aar} and {@code classes_jar}
* <li>{@code android_resource} depends on {@code unzip_aar}
* <li>{@code android_library} depends on {@code android_resource}, {@code prebuilt_jar}, and
* {@code unzip_aar}
* </ul>
* Therefore, the return value is an {link AndroidLibrary} with no {@code srcs}.
*/
static AndroidPrebuiltAar enhance(
BuildRuleParams originalBuildRuleParams,
SourcePath aarFile,
BuildRuleResolver ruleResolver,
JavacOptions javacOptions) {
SourcePathResolver pathResolver = new SourcePathResolver(ruleResolver);
UnflavoredBuildTarget originalBuildTarget =
originalBuildRuleParams.getBuildTarget().checkUnflavored();
// unzip_aar
BuildRuleParams unzipAarParams = originalBuildRuleParams.copyWithChanges(
UNZIP_AAR_TYPE,
BuildTargets.createFlavoredBuildTarget(originalBuildTarget, AAR_UNZIP_FLAVOR),
Suppliers.ofInstance(originalBuildRuleParams.getDeclaredDeps()),
Suppliers.ofInstance(originalBuildRuleParams.getExtraDeps()));
UnzipAar unzipAar = new UnzipAar(unzipAarParams, pathResolver, aarFile);
ruleResolver.addToIndex(unzipAar);
// unzip_aar#aar_classes_jar
BuildRuleParams classesJarParams = originalBuildRuleParams.copyWithChanges(
OutputOnlyBuildRule.TYPE,
BuildTargets.createFlavoredBuildTarget(originalBuildTarget, AAR_CLASSES_JAR_FLAVOR),
/* declaredDeps */ Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of(unzipAar)),
/* extraDeps */ Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of()));
OutputOnlyBuildRule classesJar = new OutputOnlyBuildRule(
classesJarParams,
pathResolver,
unzipAar.getPathToClassesJar());
ruleResolver.addToIndex(classesJar);
// prebuilt_jar
BuildRuleParams prebuiltJarParams = originalBuildRuleParams.copyWithChanges(
PrebuiltJarDescription.TYPE,
BuildTargets.createFlavoredBuildTarget(originalBuildTarget, AAR_PREBUILT_JAR_FLAVOR),
/* declaredDeps */
Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of(unzipAar, classesJar)),
/* extraDeps */ Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of()));
PrebuiltJar prebuiltJar = new PrebuiltJar(
/* params */ prebuiltJarParams,
pathResolver,
new BuildTargetSourcePath(classesJar.getProjectFilesystem(), classesJar.getBuildTarget()),
/* sourceJar */ Optional.<SourcePath>absent(),
/* gwtJar */ Optional.<SourcePath>absent(),
/* javadocUrl */ Optional.<String>absent());
ruleResolver.addToIndex(prebuiltJar);
// unzip_aar#aar_manifest
BuildRuleParams manifestParams = originalBuildRuleParams.copyWithChanges(
OutputOnlyBuildRule.TYPE,
BuildTargets.createFlavoredBuildTarget(originalBuildTarget, AAR_MANIFEST),
/* declaredDeps */ Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of(unzipAar)),
/* extraDeps */ Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of()));
OutputOnlyBuildRule manifest = new OutputOnlyBuildRule(
manifestParams,
pathResolver,
unzipAar.getAndroidManifest());
ruleResolver.addToIndex(manifest);
// android_resource
BuildRuleParams androidResourceParams = originalBuildRuleParams.copyWithChanges(
AndroidResourceDescription.TYPE,
BuildTargets.createFlavoredBuildTarget(originalBuildTarget, AAR_ANDROID_RESOURCE_FLAVOR),
/* declaredDeps */ Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of(manifest)),
/* extraDeps */ Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of()));
// Because all resources and assets are generated files, we specify them as empty collections.
ImmutableSortedSet<Path> resSrcs = ImmutableSortedSet.of();
ImmutableSortedSet<Path> assetsSrcs = ImmutableSortedSet.of();
AndroidResource androidResource = new AndroidResource(
androidResourceParams,
pathResolver,
/* deps */ ImmutableSortedSet.<BuildRule>of(unzipAar),
unzipAar.getResDirectory(),
resSrcs,
/* rDotJavaPackage */ null,
/* assets */ unzipAar.getAssetsDirectory(),
assetsSrcs,
new BuildTargetSourcePath(manifest.getProjectFilesystem(), manifest.getBuildTarget()),
/* hasWhitelistedStrings */ false);
ruleResolver.addToIndex(androidResource);
// android_library
BuildRuleParams androidLibraryParams = originalBuildRuleParams.copyWithChanges(
AndroidLibraryDescription.TYPE,
BuildTarget.of(originalBuildTarget),
/* declaredDeps */ Suppliers.ofInstance(
ImmutableSortedSet.<BuildRule>of(
androidResource,
prebuiltJar,
unzipAar)),
/* extraDeps */ Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of()));
return new AndroidPrebuiltAar(
androidLibraryParams,
pathResolver,
unzipAar.getProguardConfig(),
unzipAar.getNativeLibsDirectory(),
prebuiltJar,
androidResource,
javacOptions);
}
private static class UnzipAar extends AbstractBuildRule {
private final SourcePath aarFile;
private final Path unpackDirectory;
private final Path uberClassesJar;
private UnzipAar(
BuildRuleParams buildRuleParams,
SourcePathResolver resolver,
SourcePath aarFile) {
super(buildRuleParams, resolver);
this.aarFile = aarFile;
this.unpackDirectory = BuildTargets.getBinPath(
buildRuleParams.getBuildTarget(),
"__unpack_%s__");
this.uberClassesJar = BuildTargets.getBinPath(
buildRuleParams.getBuildTarget(),
"__uber_classes_%s__/classes.jar");
}
@Override
public ImmutableList<Step> getBuildSteps(BuildContext context,
BuildableContext buildableContext) {
ImmutableList.Builder<Step> steps = ImmutableList.builder();
steps.add(new MakeCleanDirectoryStep(unpackDirectory));
steps.add(new UnzipStep(getResolver().getPath(aarFile), unpackDirectory));
steps.add(new TouchStep(getProguardConfig()));
steps.add(new MkdirStep(getAssetsDirectory()));
steps.add(new MkdirStep(getNativeLibsDirectory()));
// We take the classes.jar file that is required to exist in an .aar and merge it with any
// .jar files under libs/ into an "uber" jar. We do this for simplicity because we do not know
// how many entries there are in libs/ at graph enhancement time, but we need to make sure
// that all of the .class files in the .aar get packaged. As it is implemented today, an
// android_library that depends on an android_prebuilt_aar can compile against anything in the
// .aar's classes.jar or libs/.
steps.add(new MkdirStep(uberClassesJar.getParent()));
steps.add(new AbstractExecutionStep("create_uber_classes_jar") {
@Override
public int execute(ExecutionContext context) {
ProjectFilesystem projectFilesystem = context.getProjectFilesystem();
Path libsDirectory = unpackDirectory.resolve("libs");
boolean dirDoesNotExistOrIsEmpty;
if (!projectFilesystem.exists(libsDirectory)) {
dirDoesNotExistOrIsEmpty = true;
} else {
try {
dirDoesNotExistOrIsEmpty =
projectFilesystem.getDirectoryContents(libsDirectory).isEmpty();
} catch (IOException e) {
context.logError(e, "Failed to get directory contents of %s", libsDirectory);
return 1;
}
}
Path classesJar = unpackDirectory.resolve("classes.jar");
if (dirDoesNotExistOrIsEmpty) {
try {
projectFilesystem.copy(classesJar, uberClassesJar, CopySourceMode.FILE);
} catch (IOException e) {
context.logError(e, "Failed to copy from %s to %s", classesJar, uberClassesJar);
return 1;
}
} else {
// Glob all of the contents from classes.jar and the entries in libs/ into a single JAR.
ImmutableSet.Builder<Path> entriesToJarBuilder = ImmutableSet.builder();
entriesToJarBuilder.add(classesJar);
try {
entriesToJarBuilder.addAll(projectFilesystem.getDirectoryContents(libsDirectory));
} catch (IOException e) {
context.logError(e, "Failed to get directory contents of %s", libsDirectory);
return 1;
}
ImmutableSet<Path> entriesToJar = entriesToJarBuilder.build();
try {
JarDirectoryStepHelper.createJarFile(
uberClassesJar,
entriesToJar,
/* mainClass */ null,
/* manifestFile */ null,
/* mergeManifests */ true,
/* blacklist */ ImmutableList.<Pattern>of(),
context);
} catch (IOException e) {
context.logError(e, "Failed to jar %s into %s", entriesToJar, uberClassesJar);
return 1;
}
}
return 0;
}
});
buildableContext.recordArtifactsInDirectory(unpackDirectory);
buildableContext.recordArtifact(uberClassesJar);
return steps.build();
}
@Override
@Nullable
public Path getPathToOutputFile() {
return null;
}
@Override
protected ImmutableCollection<Path> getInputsToCompareToOutput() {
return getResolver().filterInputsToCompareToOutput(Collections.singleton(aarFile));
}
@Override
protected Builder appendDetailsToRuleKey(Builder builder) {
return builder;
}
Path getPathToClassesJar() {
return uberClassesJar;
}
Path getResDirectory() {
return unpackDirectory.resolve("res");
}
Path getAssetsDirectory() {
return unpackDirectory.resolve("assets");
}
Path getAndroidManifest() {
return unpackDirectory.resolve("AndroidManifest.xml");
}
Path getProguardConfig() {
return unpackDirectory.resolve("proguard.txt");
}
Path getNativeLibsDirectory() {
return unpackDirectory.resolve("jni");
}
}
}