aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/android/AndroidInstrumentation.java
blob: 3f29e1f7c7c48ead9ddbcd479d14d1f77a234ac8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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;
  }
}