aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/android/DexArchiveAspect.java
blob: f7319767227424f25b6e8baa1cf6b0e0e32bc25e (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
// 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.rules.android;

import static com.google.common.base.Preconditions.checkState;
import static com.google.devtools.build.lib.Constants.TOOLS_REPOSITORY;
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 static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST;

import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.ConfiguredAspect;
import com.google.devtools.build.lib.analysis.ConfiguredAspectFactory;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.AspectDefinition;
import com.google.devtools.build.lib.packages.AspectParameters;
import com.google.devtools.build.lib.packages.NativeAspectClass.NativeAspectFactory;
import com.google.devtools.build.lib.rules.java.JavaCommon;
import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider;
import com.google.devtools.build.lib.rules.java.JavaRuntimeJarProvider;

/**
 * Aspect to {@link DexArchiveProvider build .dex Archives} from Jars.
 */
public final class DexArchiveAspect implements NativeAspectFactory, ConfiguredAspectFactory {
  private static final String NAME = "DexArchiveAspect";
  private static final String ASPECT_DEXBUILDER_PREREQ = "$dex_archive_dexbuilder";
  private static final ImmutableList<String> TRANSITIVE_ATTRIBUTES =
      ImmutableList.of("deps", "exports", "runtime_deps");

  @Override
  public AspectDefinition getDefinition(AspectParameters params) {
    AspectDefinition.Builder result = new AspectDefinition.Builder(NAME)
        // Actually we care about JavaRuntimeJarProvider, but rules don't advertise that provider.
        .requireProvider(JavaCompilationArgsProvider.class)
        .add(attr(ASPECT_DEXBUILDER_PREREQ, LABEL).cfg(HOST).exec()
        // Parse label here since we don't have RuleDefinitionEnvironment.getLabel like in a rule
            .value(Label.parseAbsoluteUnchecked(TOOLS_REPOSITORY + "//tools/android:dexbuilder")))
        .requiresConfigurationFragments(AndroidConfiguration.class);
    for (String attr : TRANSITIVE_ATTRIBUTES) {
      result.attributeAspect(attr, DexArchiveAspect.class);
    }
    return result.build();
  }

  @Override
  public ConfiguredAspect create(ConfiguredTarget base, RuleContext ruleContext,
      AspectParameters params) throws InterruptedException {
    if (AndroidCommon.getAndroidConfig(ruleContext).getIncrementalDexingBinaries().isEmpty()) {
      // Dex archives will never be used, so don't bother setting them up.
      return new ConfiguredAspect.Builder(NAME, ruleContext).build();
    }
    checkState(base.getProvider(DexArchiveProvider.class) == null,
        "dex archive natively generated: %s", ruleContext.getLabel());

    if (JavaCommon.isNeverLink(ruleContext)) {
      return new ConfiguredAspect.Builder(NAME, ruleContext)
          .addProvider(DexArchiveProvider.class, DexArchiveProvider.NEVERLINK)
          .build();
    }

    DexArchiveProvider.Builder result = createArchiveProviderBuilderFromDeps(ruleContext);
    JavaRuntimeJarProvider jarProvider = base.getProvider(JavaRuntimeJarProvider.class);
    if (jarProvider != null) {
      for (Artifact jar : jarProvider.getRuntimeJars()) {
        Artifact dexArchive = createDexArchiveAction(ruleContext, jar);
        result.addDexArchive(dexArchive, jar);
      }
    }
    return new ConfiguredAspect.Builder(NAME, ruleContext)
        .addProvider(DexArchiveProvider.class, result.build())
        .build();
  }

  private static DexArchiveProvider.Builder createArchiveProviderBuilderFromDeps(
      RuleContext ruleContext) {
    DexArchiveProvider.Builder result = new DexArchiveProvider.Builder();
    for (String attr : TRANSITIVE_ATTRIBUTES) {
      if (ruleContext.getRule().getRuleClassObject().hasAttr(attr, LABEL_LIST)) {
        result.addTransitiveProviders(
            ruleContext.getPrerequisites(attr, Mode.TARGET, DexArchiveProvider.class));
      }
    }
    return result;
  }

  private static Artifact createDexArchiveAction(RuleContext ruleContext, Artifact jar) {
    Artifact result = AndroidBinary.getDxArtifact(ruleContext, jar.getFilename() + ".dex.zip");
    // Aspect must use attribute name for dexbuilder prereq that's different from the prerequisite
    // declared on AndroidBinaryBaseRule because the two prereq's can otherwise name-clash when
    // android_binary targets are built as part of an android_test: building android_test causes
    // the aspect to apply to the android_binary target, but android_binary itself also declares
    // a $dexbuilder prerequisite, so if the aspect also used $dexbuilder then
    // RuleContext.getExecutablePrerequisite would fail with "$dexbuilder produces multiple prereqs"
    // (note they both resolve to the same artifact but that doesn't seem to prevent the exception
    // from being thrown).
    createDexArchiveAction(ruleContext, ASPECT_DEXBUILDER_PREREQ, jar, result);
    return result;
  }

  /**
   * Creates a dex archive using an executable prerequisite called {@code "$dexbuilder"}.  Rules
   * calling this method must declare the appropriate prerequisite, similar to how
   * {@link #getDefinition} does it for {@link DexArchiveAspect} under a different name.
   */
  // Package-private method for use in AndroidBinary
  static void createDexArchiveAction(RuleContext ruleContext, Artifact jar, Artifact dexArchive) {
    createDexArchiveAction(ruleContext, "$dexbuilder", jar, dexArchive);
  }

  private static void createDexArchiveAction(RuleContext ruleContext, String dexbuilderPrereq,
      Artifact jar, Artifact dexArchive) {
    SpawnAction.Builder dexbuilder = new SpawnAction.Builder()
        .setExecutable(ruleContext.getExecutablePrerequisite(dexbuilderPrereq, Mode.HOST))
        .addArgument("--input_jar")
        .addInputArgument(jar)
        .addArgument("--output_zip")
        .addOutputArgument(dexArchive)
        .setMnemonic("DexBuilder")
        .setProgressMessage("Dexing " + jar.prettyPrint());
    if (ruleContext.getConfiguration().isCodeCoverageEnabled()) {
      // Match what we do in AndroidCommon.createDexAction
      dexbuilder.addArgument("--nolocals"); // TODO(bazel-team): Still needed? See createDexAction
    }
    ruleContext.registerAction(dexbuilder.build(ruleContext));
  }
}