| /* |
| * 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"); |
| } |
| } |
| } |