aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com
diff options
context:
space:
mode:
authorGravatar ajmichael <ajmichael@google.com>2017-05-05 20:32:18 +0200
committerGravatar Damien Martin-Guillerez <dmarting@google.com>2017-05-05 23:19:24 +0200
commit4b3f9dbf813d7a5def8aac844a3a1023f04fd61a (patch)
tree8c1aa3880354600da1cc6ac8d807dfd9fb639f73 /src/main/java/com
parentb89f9faf3c064f3f2f8f0959dda5687f7ca10221 (diff)
Create new android_instrumentation rule.
This rule is responsible for building the target and instrumentation APKs used by an Android instrumentation test. If they are provided as APKs (e.g. from an android_binary or a genrule) they will be used as is. If they are provided as libraries, APKs will be created. This CL does not actually implement building target and instrumentation APKs from libraries, that will come in a follow-up CL as it will require some heavy refactoring of AndroidBinary.java. Follow-up CLs will add features such as repackaging the APKs to remove duplicate classes, reproguarding the target APK with the test code, validating that the target and instrumentation APKs were signed with the same debug key and verifying that instrumentation stanza appears in the instrumentation APKs manifest. Note that this CL does _not_ install the rule in the BazelRuleClassProvider, so this CL does not make it usable by anyone. Once the other android testing rules are ready, I will install them all. One small step towards https://github.com/bazelbuild/bazel/issues/903. RELNOTES: None PiperOrigin-RevId: 155220900
Diffstat (limited to 'src/main/java/com')
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentation.java157
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationInfoProvider.java52
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationRule.java59
3 files changed, 268 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentation.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentation.java
new file mode 100644
index 0000000000..3f29e1f7c7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentation.java
@@ -0,0 +1,157 @@
+// Copyright 2017 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.Iterables;
+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.SymlinkAction;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
+import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+import com.google.devtools.build.lib.util.FileType;
+
+/**
+ * An implementation of the {@code android_instrumentation} rule.
+ */
+public class AndroidInstrumentation implements RuleConfiguredTargetFactory {
+
+ private static final SafeImplicitOutputsFunction TARGET_APK = ImplicitOutputsFunction
+ .fromTemplates("%{name}-target.apk");
+ private static final SafeImplicitOutputsFunction INSTRUMENTATION_APK =
+ ImplicitOutputsFunction.fromTemplates("%{name}-instrumentation.apk");
+ static final SafeImplicitOutputsFunction IMPLICIT_OUTPUTS_FUNCTION =
+ ImplicitOutputsFunction.fromFunctions(TARGET_APK, INSTRUMENTATION_APK);
+
+ @Override
+ public ConfiguredTarget create(RuleContext ruleContext)
+ throws InterruptedException, RuleErrorException {
+
+ Artifact targetApk = getTargetApk(ruleContext);
+ Artifact instrumentationApk = createInstrumentationApk(ruleContext);
+
+ RuleConfiguredTargetBuilder ruleBuilder = new RuleConfiguredTargetBuilder(ruleContext);
+ return ruleBuilder
+ .setFilesToBuild(
+ NestedSetBuilder.<Artifact>stableOrder().add(targetApk).add(instrumentationApk).build())
+ .addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY)
+ .addNativeDeclaredProvider(
+ new AndroidInstrumentationInfoProvider(targetApk, instrumentationApk))
+ .build();
+ }
+
+ private static boolean exactlyOneOf(boolean expression1, boolean expression2) {
+ return (expression1 && !expression2) || (!expression1 && expression2);
+ }
+
+ /**
+ * Returns the APK from the {@code target} attribute or creates one from the {@code
+ * target_library} attribute.
+ */
+ private static Artifact getTargetApk(RuleContext ruleContext)
+ throws RuleErrorException, InterruptedException {
+ Artifact apk = ruleContext.getImplicitOutputArtifact(TARGET_APK);
+ TransitiveInfoCollection target = ruleContext.getPrerequisite("target", Mode.TARGET);
+ TransitiveInfoCollection targetLibrary =
+ ruleContext.getPrerequisite("target_library", Mode.TARGET);
+
+ if (!exactlyOneOf(target == null, targetLibrary == null)) {
+ ruleContext.throwWithRuleError(
+ "android_instrumentation requires that exactly one of the target and target_library "
+ + "attributes be specified.");
+ }
+
+ if (target != null) {
+ // target attribute is specified
+ symlinkApkFromApkProviderOrFile(ruleContext, target, apk, "Symlinking target APK");
+ } else {
+ // target_library attribute is specified
+ createApkFromLibrary(ruleContext, targetLibrary, apk);
+ }
+
+ return apk;
+ }
+
+ /**
+ * Returns the APK from the {@code instrumentation} attribute or creates one from the {@code
+ * instrumentation_library} attribute.
+ */
+ private static Artifact createInstrumentationApk(RuleContext ruleContext)
+ throws RuleErrorException, InterruptedException {
+ Artifact apk = ruleContext.getImplicitOutputArtifact(INSTRUMENTATION_APK);
+ TransitiveInfoCollection instrumentation =
+ ruleContext.getPrerequisite("instrumentation", Mode.TARGET);
+ TransitiveInfoCollection instrumentationLibrary =
+ ruleContext.getPrerequisite("instrumentation_library", Mode.TARGET);
+
+ if (!exactlyOneOf(instrumentation == null, instrumentationLibrary == null)) {
+ ruleContext.throwWithRuleError(
+ "android_instrumentation requires that exactly one of the instrumentation and "
+ + "instrumentation_library attributes be specified.");
+ }
+
+ if (instrumentation != null) {
+ // instrumentation attribute is specified
+ symlinkApkFromApkProviderOrFile(
+ ruleContext, instrumentation, apk, "Symlinking instrumentation APK");
+ } else {
+ // instrumentation_library attribute is specified
+ createApkFromLibrary(ruleContext, instrumentationLibrary, apk);
+ }
+
+ return apk;
+ }
+
+ // We symlink instead of simply providing the artifact as is to satisfy the implicit outputs
+ // function. This allows user to refer to the APK outputs of the android_instrumentation rule by
+ // the same name, whether they were built from libraries or simply symlinked from the output of
+ // an android_binary rule.
+ private static void symlinkApkFromApkProviderOrFile(
+ RuleContext ruleContext,
+ TransitiveInfoCollection transitiveInfoCollection,
+ Artifact apk,
+ String message) {
+ Artifact existingApk;
+ ApkProvider apkProvider = transitiveInfoCollection.getProvider(ApkProvider.class);
+ if (apkProvider != null) {
+ existingApk = Iterables.getOnlyElement(apkProvider.getTransitiveApks());
+ } else {
+ existingApk =
+ Iterables.getOnlyElement(
+ FileType.filter(
+ transitiveInfoCollection.getProvider(FileProvider.class).getFilesToBuild(),
+ AndroidRuleClasses.APK));
+ }
+
+ ruleContext.registerAction(
+ new SymlinkAction(ruleContext.getActionOwner(), existingApk, apk, message));
+ }
+
+ @SuppressWarnings("unused") // TODO(b/37856762): Implement APK building from libraries.
+ private static Artifact createApkFromLibrary(
+ RuleContext ruleContext, TransitiveInfoCollection library, Artifact apk)
+ throws RuleErrorException {
+ // TODO(b/37856762): Cleanup AndroidBinary#createAndroidBinary and use it here.
+ ruleContext.throwWithRuleError(
+ "android_instrumentation dependencies on android_library rules are not yet supported");
+ return null;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationInfoProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationInfoProvider.java
new file mode 100644
index 0000000000..63a8fb353e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationInfoProvider.java
@@ -0,0 +1,52 @@
+// Copyright 2017 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.ImmutableMap;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.packages.NativeClassObjectConstructor;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
+
+/**
+ * A provider for targets that create Android instrumentations. Consumed by {@link
+ * AndroidInstrumentationTest}.
+ */
+@Immutable
+public class AndroidInstrumentationInfoProvider extends SkylarkClassObject
+ implements TransitiveInfoProvider {
+
+ private static final String SKYLARK_NAME = "AndroidInstrumentationInfo";
+ static final NativeClassObjectConstructor ANDROID_INSTRUMENTATION_INFO =
+ new NativeClassObjectConstructor(SKYLARK_NAME) {
+ };
+
+ private final Artifact targetApk;
+ private final Artifact instrumentationApk;
+
+ public AndroidInstrumentationInfoProvider(Artifact targetApk, Artifact instrumentationApk) {
+ super(ANDROID_INSTRUMENTATION_INFO, ImmutableMap.<String, Object>of());
+ this.targetApk = targetApk;
+ this.instrumentationApk = instrumentationApk;
+ }
+
+ public Artifact getTargetApk() {
+ return targetApk;
+ }
+
+ public Artifact getInstrumentationApk() {
+ return instrumentationApk;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationRule.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationRule.java
new file mode 100644
index 0000000000..b05c3060a6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationRule.java
@@ -0,0 +1,59 @@
+// Copyright 2017 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 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.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder;
+
+/** Rule definition for the {@code android_instrumentation} rule. */
+public class AndroidInstrumentationRule implements RuleDefinition {
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment environment) {
+ return builder
+ .setUndocumented()
+ .add(
+ attr("target", LABEL)
+ .allowedFileTypes(AndroidRuleClasses.APK)
+ .allowedRuleClasses("android_binary"))
+ .add(
+ attr("target_library", LABEL)
+ .allowedFileTypes()
+ .allowedRuleClasses("android_library"))
+ .add(
+ attr("instrumentation", LABEL)
+ .allowedFileTypes(AndroidRuleClasses.APK)
+ .allowedRuleClasses("android_binary"))
+ .add(
+ attr("instrumentation_library", LABEL)
+ .allowedFileTypes()
+ .allowedRuleClasses("android_library"))
+ .setImplicitOutputsFunction(AndroidInstrumentation.IMPLICIT_OUTPUTS_FUNCTION)
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("android_instrumentation")
+ .ancestors(BaseRuleClasses.RuleBase.class)
+ .factoryClass(AndroidInstrumentation.class)
+ .build();
+ }
+}