// 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;
}
}