forked from bazelbuild/bazel
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 bazelbuild#903. RELNOTES: None PiperOrigin-RevId: 155220900
- Loading branch information
1 parent
b89f9fa
commit 4b3f9db
Showing
6 changed files
with
450 additions
and
0 deletions.
There are no files selected for viewing
157 changes: 157 additions & 0 deletions
157
src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
.../java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationInfoProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentationRule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.