// Copyright 2016 The Bazel Authors. All rights reserved. // // 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.google.devtools.build.lib.rules.android; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.actions.Action; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.FileProvider; import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.RunfilesProvider; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; import com.google.devtools.build.lib.rules.java.JavaCommon; import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider; import com.google.devtools.build.lib.rules.java.JavaCompilationArtifacts; import com.google.devtools.build.lib.rules.java.JavaHelper; import com.google.devtools.build.lib.rules.java.JavaProvider; import com.google.devtools.build.lib.rules.java.JavaRuleOutputJarsProvider; import com.google.devtools.build.lib.rules.java.JavaRuleOutputJarsProvider.OutputJar; import com.google.devtools.build.lib.rules.java.JavaRuntimeJarProvider; import com.google.devtools.build.lib.rules.java.JavaSemantics; import com.google.devtools.build.lib.rules.java.JavaToolchainProvider; import com.google.devtools.build.lib.rules.java.Jvm; import com.google.devtools.build.lib.vfs.PathFragment; /** * An implementation for the aar_import rule. * * AAR files are zip archives that contain an Android Manifest, JARs, resources, assets, native * libraries, Proguard configuration and lint jars. Currently the aar_import rule supports AARs with * an AndroidManifest.xml, classes.jar, libs/, res/ and jni/. Assets are not yet supported. * * @see <a href="http://tools.android.com/tech-docs/new-build-system/aar-format">AAR Format</a> */ public class AarImport implements RuleConfiguredTargetFactory { private static final String ANDROID_MANIFEST = "AndroidManifest.xml"; private static final String MERGED_JAR = "classes_and_libs_merged.jar"; private final JavaSemantics javaSemantics; protected AarImport(JavaSemantics javaSemantics) { this.javaSemantics = javaSemantics; } @Override public ConfiguredTarget create(RuleContext ruleContext) throws InterruptedException, RuleErrorException { if (!AndroidSdkProvider.verifyPresence(ruleContext)) { return null; } RuleConfiguredTargetBuilder ruleBuilder = new RuleConfiguredTargetBuilder(ruleContext); Artifact aar = ruleContext.getPrerequisiteArtifact("aar", Mode.TARGET); Artifact allAarJars = createAarTreeArtifact(ruleContext, "jars"); Artifact jarMergingParams = createAarArtifact(ruleContext, "jar_merging_params"); ruleContext.registerAction( createAarEmbeddedJarsExtractorActions(ruleContext, aar, allAarJars, jarMergingParams)); Artifact mergedJar = createAarArtifact(ruleContext, MERGED_JAR); ruleContext.registerAction( createAarJarsMergingActions(ruleContext, allAarJars, mergedJar, jarMergingParams)); // AndroidManifest.xml is required in every AAR. Artifact androidManifestArtifact = createAarArtifact(ruleContext, ANDROID_MANIFEST); ruleContext.registerAction(createSingleFileExtractorActions( ruleContext, aar, ANDROID_MANIFEST, androidManifestArtifact)); Artifact resourcesManifest = createAarArtifact(ruleContext, "resource_manifest"); ruleContext.registerAction( createManifestExtractorActions(ruleContext, aar, "res/.*", resourcesManifest)); Artifact resources = createAarTreeArtifact(ruleContext, "resources"); ruleContext.registerAction( createManifestFileEntriesExtractorActions(ruleContext, aar, resourcesManifest, resources)); ApplicationManifest androidManifest = ApplicationManifest.fromExplicitManifest(ruleContext, androidManifestArtifact); FileProvider resourcesProvider = new FileProvider( new NestedSetBuilder<Artifact>(Order.NAIVE_LINK_ORDER).add(resources).build()); Artifact resourcesZip = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP); ResourceApk resourceApk = androidManifest.packWithDataAndResources( ruleContext, new LocalResourceContainer.Builder(ruleContext) .withResources(ImmutableList.of(resourcesProvider)) .build(), ResourceDependencies.fromRuleDeps(ruleContext, JavaCommon.isNeverLink(ruleContext)), ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT), ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_LOCAL_SYMBOLS), ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST), resourcesZip, /* alwaysExportManifest = */ true); // There isn't really any use case for building an aar_import target on its own, so the files to // build could be empty. The resources zip and merged jars are added here as a sanity check for // Bazel developers so that `bazel build java/com/my_aar_import` will fail if the resource // processing or jar merging steps fail. NestedSetBuilder<Artifact> filesToBuildBuilder = NestedSetBuilder.<Artifact>stableOrder().add(resourcesZip).add(mergedJar); Artifact nativeLibs = createAarArtifact(ruleContext, "native_libs.zip"); ruleContext.registerAction(createAarNativeLibsFilterActions(ruleContext, aar, nativeLibs)); JavaRuleOutputJarsProvider.Builder jarProviderBuilder = new JavaRuleOutputJarsProvider.Builder() .addOutputJar(mergedJar, null, ImmutableList.<Artifact>of()); for (TransitiveInfoCollection export : ruleContext.getPrerequisites("exports", Mode.TARGET)) { for (OutputJar jar : JavaProvider.getProvider(JavaRuleOutputJarsProvider.class, export).getOutputJars()) { jarProviderBuilder.addOutputJar(jar); filesToBuildBuilder.add(jar.getClassJar()); } } ImmutableList<TransitiveInfoCollection> targets = ImmutableList.<TransitiveInfoCollection>copyOf( ruleContext.getPrerequisites("exports", Mode.TARGET)); JavaCommon common = new JavaCommon( ruleContext, javaSemantics, /* sources = */ ImmutableList.<Artifact>of(), /* compileDeps = */ targets, /* runtimeDeps = */ targets, /* bothDeps = */ targets); common.setJavaCompilationArtifacts( new JavaCompilationArtifacts.Builder() .addRuntimeJar(mergedJar) .addCompileTimeJar(mergedJar) .build()); return ruleBuilder .setFilesToBuild(filesToBuildBuilder.build()) .addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY) .addProvider( AndroidResourcesProvider.class, resourceApk.toResourceProvider(ruleContext.getLabel())) .addProvider( NativeLibsZipsProvider.class, new NativeLibsZipsProvider( AndroidCommon.collectTransitiveNativeLibsZips(ruleContext).add(nativeLibs).build())) .addProvider( JavaRuntimeJarProvider.class, new JavaRuntimeJarProvider(ImmutableList.of(mergedJar))) .addProvider( JavaCompilationArgsProvider.class, JavaCompilationArgsProvider.create( common.collectJavaCompilationArgs( /* recursive = */ false, JavaCommon.isNeverLink(ruleContext), /* srcLessDepsExport = */ false), common.collectJavaCompilationArgs( /* recursive = */ true, JavaCommon.isNeverLink(ruleContext), /* srcLessDepsExport = */ false))) .addProvider(JavaRuleOutputJarsProvider.class, jarProviderBuilder.build()) .build(); } private static Action[] createSingleFileExtractorActions(RuleContext ruleContext, Artifact aar, String filename, Artifact outputArtifact) { return new SpawnAction.Builder() .setExecutable(ruleContext.getExecutablePrerequisite("$zipper", Mode.HOST)) .setMnemonic("AarFileExtractor") .setProgressMessage("Extracting " + filename + " from " + aar.getFilename()) .addArgument("x") .addInputArgument(aar) .addArgument("-d") .addOutput(outputArtifact) .addArgument(outputArtifact.getExecPath().getParentDirectory().getPathString()) .addArgument(filename) .build(ruleContext); } private static Action[] createManifestFileEntriesExtractorActions(RuleContext ruleContext, Artifact aar, Artifact manifest, Artifact outputTree) { return new SpawnAction.Builder() .setExecutable(ruleContext.getExecutablePrerequisite("$zipper", Mode.HOST)) .setMnemonic("AarManifestFileEntriesExtractor") .addArgument("x") .addInputArgument(aar) .addArgument("-d") .addOutputArgument(outputTree) .addArgument("@" + manifest.getExecPathString()) .addInput(manifest) .build(ruleContext); } private static Action[] createAarEmbeddedJarsExtractorActions(RuleContext ruleContext, Artifact aar, Artifact jarsTreeArtifact, Artifact singleJarParamFile) { return new SpawnAction.Builder() .setExecutable( ruleContext.getExecutablePrerequisite("$aar_embedded_jars_extractor", Mode.HOST)) .setMnemonic("AarEmbeddedJarsExtractor") .setProgressMessage("Extracting classes.jar and libs/*.jar from " + aar.getFilename()) .addArgument("--input_aar") .addInputArgument(aar) .addArgument("--output_dir") .addOutputArgument(jarsTreeArtifact) .addArgument("--output_singlejar_param_file") .addOutputArgument(singleJarParamFile) .build(ruleContext); } private static Action[] createAarJarsMergingActions(RuleContext ruleContext, Artifact jarsTreeArtifact, Artifact mergedJar, Artifact paramFile) { return singleJarSpawnActionBuilder(ruleContext) .setMnemonic("AarJarsMerger") .setProgressMessage("Merging AAR embedded jars") .addInput(jarsTreeArtifact) .addArgument("--output") .addOutputArgument(mergedJar) .addArgument("--dont_change_compression") .addInput(paramFile) .addArgument("@" + paramFile.getExecPathString()) .build(ruleContext); } private static Action[] createManifestExtractorActions(RuleContext ruleContext, Artifact aar, String filenameRegexp, Artifact manifest) { return new SpawnAction.Builder() .setExecutable(ruleContext.getExecutablePrerequisite("$zip_manifest_creator", Mode.HOST)) .setMnemonic("ZipManifestCreator") .setProgressMessage( "Creating manifest for " + aar.getFilename() + " matching " + filenameRegexp) .addArgument(filenameRegexp) .addInputArgument(aar) .addOutputArgument(manifest) .build(ruleContext); } private static Action[] createAarNativeLibsFilterActions(RuleContext ruleContext, Artifact aar, Artifact outputZip) { SpawnAction.Builder actionBuilder = new SpawnAction.Builder() .setExecutable( ruleContext.getExecutablePrerequisite("$aar_native_libs_zip_creator", Mode.HOST)) .setMnemonic("AarNativeLibsFilter") .setProgressMessage("Filtering AAR native libs by architecture") .addArgument("--input_aar") .addInputArgument(aar) .addArgument("--cpu") .addArgument(ruleContext.getConfiguration().getCpu()) .addArgument("--output_zip") .addOutputArgument(outputZip); return actionBuilder.build(ruleContext); } private static Artifact createAarArtifact(RuleContext ruleContext, String name) { return ruleContext.getUniqueDirectoryArtifact( "_aar", name, ruleContext.getBinOrGenfilesDirectory()); } private static Artifact createAarTreeArtifact(RuleContext ruleContext, String name) { PathFragment rootRelativePath = ruleContext.getUniqueDirectory("_aar/unzipped/" + name); return ruleContext.getTreeArtifact(rootRelativePath, ruleContext.getBinOrGenfilesDirectory()); } // Adds the appropriate SpawnAction options depending on if SingleJar is a jar or not. private static SpawnAction.Builder singleJarSpawnActionBuilder(RuleContext ruleContext) { SpawnAction.Builder builder = new SpawnAction.Builder(); Artifact singleJar = JavaToolchainProvider.fromRuleContext(ruleContext).getSingleJar(); if (singleJar.getFilename().endsWith(".jar")) { builder .setJarExecutable( ruleContext.getHostConfiguration().getFragment(Jvm.class).getJavaExecutable(), singleJar, JavaToolchainProvider.fromRuleContext(ruleContext).getJvmOptions()) .addTransitiveInputs(JavaHelper.getHostJavabaseInputs(ruleContext)); } else { builder.setExecutable(singleJar); } return builder; } }