Skip to content

Commit

Permalink
Adds aar_import rule to Bazel along with tools needed to implement it…
Browse files Browse the repository at this point in the history
…. Currently only uses AndroidManifest.xml, classes.jar and res/ from AARs. This is sufficient for many of the AARs of Google Play Services and Android Support Repository.

The next step will be for AndroidSdkRepositoryRule to scan the SDK and generate aar_import rules for the AARs within.

The rule is not yet documented because it is not intended for end users to use it yet. We should probably support more of the features of AARs before that time. See http://tools.android.com/tech-docs/new-build-system/aar-format for all of the files that can be included in AARs.

Also note that R.txt from the AAR is intentionally ignored and regenerated based on the contents of res/. This is more correct, because the R.txt inside of an AAR can contain ids for dependencies of the AAR that are not included in res/.

See #564 for discussion of supporting AARs and #1745 for motivation to get it done soon.

--
MOS_MIGRATED_REVID=133127933
  • Loading branch information
aj-michael authored and dslomov committed Sep 14, 2016
1 parent 03b9cfd commit f8a6752
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.google.devtools.build.lib.analysis.config.ConfigurationEnvironment;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.analysis.constraints.EnvironmentRule;
import com.google.devtools.build.lib.bazel.rules.android.AarImportRule;
import com.google.devtools.build.lib.bazel.rules.android.AndroidNdkRepositoryRule;
import com.google.devtools.build.lib.bazel.rules.android.AndroidSdkRepositoryRule;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidBinaryRule;
Expand Down Expand Up @@ -360,6 +361,7 @@ private static void initAndroid(ConfiguredRuleClassProvider.Builder builder) {
builder.addRuleDefinition(new AndroidLibraryBaseRule(androidNeverlinkAspect, jackAspect));
builder.addRuleDefinition(new BazelAndroidLibraryRule());
builder.addRuleDefinition(new BazelAndroidBinaryRule());
builder.addRuleDefinition(new AarImportRule());

builder.addSkylarkAccessibleTopLevels("android_common", new AndroidSkylarkCommon());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// 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.bazel.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.actions.PopulateTreeArtifactAction;
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.android.AndroidCommon;
import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider;
import com.google.devtools.build.lib.rules.android.ApplicationManifest;
import com.google.devtools.build.lib.rules.android.LocalResourceContainer;
import com.google.devtools.build.lib.rules.android.ResourceApk;
import com.google.devtools.build.lib.rules.android.ResourceDependencies;
import com.google.devtools.build.lib.rules.java.JavaCommon;
import com.google.devtools.build.lib.rules.java.JavaRuleOutputJarsProvider;
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 and res/. Assets, native libraries and additional embedded
* jars 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 CLASSES_JAR = "classes.jar";

@Override
public ConfiguredTarget create(RuleContext ruleContext)
throws InterruptedException, RuleErrorException {
RuleConfiguredTargetBuilder ruleBuilder = new RuleConfiguredTargetBuilder(ruleContext);
Artifact aar = ruleContext.getPrerequisiteArtifact("aar", Mode.TARGET);

// classes.jar is required in every AAR.
Artifact classesJar = createAarArtifact(ruleContext, CLASSES_JAR);
ruleContext.registerAction(
createSingleFileExtractor(ruleContext, aar, CLASSES_JAR, classesJar));

// AndroidManifest.xml is required in every AAR.
Artifact androidManifestArtifact = createAarArtifact(ruleContext, ANDROID_MANIFEST);
ruleContext.registerAction(
createSingleFileExtractor(ruleContext, aar, ANDROID_MANIFEST, androidManifestArtifact));

Artifact resourcesManifest = createAarArtifact(ruleContext, "resource_manifest");
ruleContext.registerAction(
createManifestExtractor(ruleContext, aar, "res/.*", resourcesManifest));

Artifact resources = createResourcesTreeArtifact(ruleContext);
ruleContext.registerAction(createTreePopulater(ruleContext, aar, resourcesManifest, resources));

ApplicationManifest androidManifest =
ApplicationManifest.fromExplicitManifest(ruleContext, androidManifestArtifact)
.renamePackage(ruleContext, AndroidCommon.getJavaPackage(ruleContext));

FileProvider resourcesProvider = new FileProvider(
new NestedSetBuilder<Artifact>(Order.NAIVE_LINK_ORDER).add(resources).build());

ResourceApk resourceApk = androidManifest.packWithDataAndResources(
ruleContext,
new LocalResourceContainer.Builder(ruleContext)
.withResources(ImmutableList.of(resourcesProvider))
.build(),
ResourceDependencies.fromRuleDeps(ruleContext, JavaCommon.isNeverLink(ruleContext)));

return ruleBuilder
.setFilesToBuild(
NestedSetBuilder
.<Artifact>stableOrder()
.add(resources)
.add(classesJar)
.build())
.add(RunfilesProvider.class, RunfilesProvider.EMPTY)
.add(AndroidResourcesProvider.class, resourceApk.toResourceProvider(ruleContext.getLabel()))
.add(
JavaRuleOutputJarsProvider.class,
new JavaRuleOutputJarsProvider.Builder()
.addOutputJar(classesJar, null, null)
.build())
.build();
}

private static Action[] createSingleFileExtractor(RuleContext ruleContext, Artifact aar,
String filename, Artifact outputArtifact) {
return new SpawnAction.Builder()
.setExecutable(ruleContext.getExecutablePrerequisite("$unzip", Mode.HOST))
.addArgument("-j")
.addInputArgument(aar)
.addArgument(filename)
.addArgument("-d")
.addOutput(outputArtifact)
.addArgument(outputArtifact.getExecPath().getParentDirectory().getPathString())
.build(ruleContext);
}

private static Action createTreePopulater(RuleContext ruleContext, Artifact aar,
Artifact manifest, Artifact outputTree) {
return new PopulateTreeArtifactAction(
ruleContext.getActionOwner(),
aar,
manifest,
outputTree,
ruleContext.getExecutablePrerequisite("$zipper", Mode.HOST));
}

private static Action[] createManifestExtractor(RuleContext ruleContext, Artifact aar,
String filenameRegexp, Artifact manifest) {
return new SpawnAction.Builder()
.setExecutable(ruleContext.getExecutablePrerequisite("$zip_manifest_creator", Mode.HOST))
.addArgument(filenameRegexp)
.addInputArgument(aar)
.addOutputArgument(manifest)
.build(ruleContext);
}

private static Artifact createAarArtifact(RuleContext ruleContext, String name) {
return ruleContext.getUniqueDirectoryArtifact(
"_aar", name, ruleContext.getBinOrGenfilesDirectory());
}

private static Artifact createResourcesTreeArtifact(RuleContext ruleContext) {
PathFragment rootRelativePath = ruleContext.getUniqueDirectory("_aar/unzipped");
return ruleContext.getTreeArtifact(rootRelativePath, ruleContext.getBinOrGenfilesDirectory());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// 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.bazel.rules.android;

import static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST;
import static com.google.devtools.build.lib.packages.Attribute.attr;
import static com.google.devtools.build.lib.packages.BuildType.LABEL;

import com.google.devtools.build.lib.analysis.BaseRuleClasses;
import com.google.devtools.build.lib.analysis.RuleDefinition;
import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleClass.Builder;
import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.AndroidAaptBaseRule;
import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider;
import com.google.devtools.build.lib.util.FileType;
import com.google.devtools.build.lib.util.FileTypeSet;

/** Rule definition for the aar_import rule. */
public class AarImportRule implements RuleDefinition {

@Override
public RuleClass build(Builder builder, RuleDefinitionEnvironment environment) {
return builder
.setUndocumented()
.add(attr("aar", LABEL)
.mandatory()
.allowedFileTypes(FileType.of(".aar")))
.add(attr("$zip_manifest_creator", LABEL)
.cfg(HOST)
.exec()
.value(Label.parseAbsoluteUnchecked(
environment.getToolsRepository() + "//tools/zip:zip_manifest_creator")))
.add(attr("$unzip", LABEL)
.cfg(HOST)
.exec()
.value(Label.parseAbsoluteUnchecked(
environment.getToolsRepository() + "//tools/zip:unzip"))
.allowedFileTypes(FileTypeSet.ANY_FILE))
.add(attr("$zipper", LABEL)
.cfg(HOST)
.exec()
.value(Label.parseAbsoluteUnchecked(
environment.getToolsRepository() + "//tools/zip:zipper")))
.advertiseProvider(JavaCompilationArgsProvider.class)
.build();
}

@Override
public Metadata getMetadata() {
return RuleDefinition.Metadata.builder()
.name("aar_import")
// AndroidAaptBaseRule is needed for $android_manifest_merger which is used by the
// ApplicationManifest class.
.ancestors(BaseRuleClasses.RuleBase.class, AndroidAaptBaseRule.class)
.factoryClass(AarImport.class)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ public List<BuildOptions> split(BuildOptions buildOptions) {
public static final FileType ANDROID_IDL = FileType.of(".aidl");

public static final String[] ALLOWED_DEPENDENCIES = {
"aar_import",
"android_library",
"cc_library",
"java_import",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,32 @@ public ResourceApk packWithAssets(
null /* Artifact mergedResources */);
}

/** Packages up the manifest with resource and assets from the LocalResourceContainer. */
public ResourceApk packWithDataAndResources(
RuleContext ruleContext, LocalResourceContainer data, ResourceDependencies resourceDeps)
throws InterruptedException {
if (ruleContext.hasErrors()) {
return null;
}
return createApk(
null, /* Artifact resourceApk */
ruleContext,
true, /* isLibrary */
resourceDeps,
ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT),
ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_SYMBOLS_TXT),
ImmutableList.<String>of(), /* List<String> configurationFilters */
ImmutableList.<String>of(), /* List<String> uncompressedExtensions */
false, /* crunchPng */
ImmutableList.<String>of(), /* List<String> densities */
false, /* incremental */
data,
null, /* Artifact proguardCfg */
null, /* Artifact mainDexProguardCfg */
ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST),
ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP));
}

/** Packages up the manifest with resource and assets from the rule and dependent resources. */
public ResourceApk packWithDataAndResources(
@Nullable Artifact resourceApk,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public static PathFragment findResourceDir(Artifact artifact) {
return null;
}
// TODO(bazel-team): Expand Fileset to verify, or remove Fileset as an option for resources.
if (artifact.isFileset()) {
if (artifact.isFileset() || artifact.isTreeArtifact()) {
return fragment.subFragment(segmentCount - 1, segmentCount);
}

Expand Down
12 changes: 11 additions & 1 deletion tools/zip/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@ package(default_visibility = ["//visibility:public"])

filegroup(
name = "srcs",
srcs = ["BUILD"],
srcs = glob(["**"]),
)

# zipper will be added when creating the @bazel_tools repository.
filegroup(
name = "zipper",
srcs = glob(["zipper/*"]),
)

sh_binary(
name = "unzip",
srcs = ["unzip.sh"],
)

sh_binary(
name = "zip_manifest_creator",
srcs = ["zip_manifest_creator.sh"],
)
20 changes: 20 additions & 0 deletions tools/zip/unzip.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash
#
# 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.

# This exists so that unzip can be invoked by a SpawnAction. It relies on unzip
# existing in the user's path.

unzip "$@"
30 changes: 30 additions & 0 deletions tools/zip/zip_manifest_creator.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash
#
# 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.

# This script takes in a regular expression and a zip file and writes a file
# containing the names of all files in the zip file that match the regular
# expression with one per line. Names of directories are not included.

if [ "$#" -ne 3 ]; then
echo "Usage: zip_manifest_creator.sh <regexp> <input zip> <output manifest>"
exit 1
fi

REGEX="$1"
INPUT_ZIP="$2"
OUTPUT_MANIFEST="$3"

zipinfo -1 "$INPUT_ZIP" -x "*/" | grep -x "$REGEX" > "$OUTPUT_MANIFEST"

0 comments on commit f8a6752

Please sign in to comment.