aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/android/ApkManifestAction.java
blob: f6a8fbafd88cb02ed29b747dbcd0b1e45cbd8e8b (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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
// Copyright 2015 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.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Ints;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionOwner;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction;
import com.google.devtools.build.lib.collect.CollectionUtils;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.rules.android.apkmanifest.ApkManifestOuterClass;
import com.google.devtools.build.lib.rules.android.apkmanifest.ApkManifestOuterClass.ApkManifest;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.protobuf.ByteString;
import com.google.protobuf.TextFormat;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Map;

@Immutable
public final class ApkManifestAction extends AbstractFileWriteAction {

  private static Iterable<Artifact> makeInputs(
      AndroidSdkProvider sdk,
      Iterable<Artifact> jars,
      ResourceApk resourceApk,
      NativeLibs nativeLibs,
      Artifact debugKeystore) {

    return ImmutableList.<Artifact>builder()
        .add(sdk.getAapt().getExecutable())
        .add(sdk.getAdb().getExecutable())
        .add(sdk.getAidl().getExecutable())
        .add(sdk.getAndroidJar())
        .add(sdk.getAnnotationsJar())
        .add(sdk.getDx().getExecutable())
        .add(sdk.getFrameworkAidl())
        .add(sdk.getJack().getExecutable())
        .add(sdk.getJill().getExecutable())
        .add(sdk.getMainDexClasses())
        .add(sdk.getMainDexListCreator().getExecutable())
        .add(sdk.getProguard().getExecutable())
        .add(sdk.getResourceExtractor().getExecutable())
        .add(sdk.getShrinkedAndroidJar())
        .add(sdk.getZipalign().getExecutable())
        
        .addAll(jars)
        .add(resourceApk.getArtifact())
        .add(resourceApk.getManifest())
        .addAll(nativeLibs.getAllNativeLibs())
        .add(debugKeystore)
        .build();
  }

  private static final String GUID = "7b6f4858-d1f2-11e5-83b0-cf6ddc5a32d9";

  private final boolean textOutput;
  private final AndroidSdkProvider sdk;
  private final Iterable<Artifact> jars;
  private final ResourceApk resourceApk;
  private final NativeLibs nativeLibs;
  private final Artifact debugKeystore;

  /**
   * @param owner The action owner.
   * @param outputFile The artifact to write the proto to.
   * @param textOutput Whether to make the 
   * @param sdk The Android SDK.
   * @param jars All the jars that would be merged and dexed and put into an APK.
   * @param resourceApk The ResourceApk for the .ap_ that contains the resources that would go into
   *     an APK.
   * @param debugKeystore The debug keystore.
   * @param nativeLibs The natives libs that would go into an APK.
   */
  public ApkManifestAction(
      ActionOwner owner,
      Artifact outputFile,
      boolean textOutput,
      AndroidSdkProvider sdk,
      Iterable<Artifact> jars,
      ResourceApk resourceApk,
      NativeLibs nativeLibs,
      Artifact debugKeystore) {
    super(owner, makeInputs(sdk, jars, resourceApk, nativeLibs, debugKeystore), outputFile, false);
    CollectionUtils.checkImmutable(jars);
    this.textOutput = textOutput;
    this.sdk = sdk;
    this.jars = jars;
    this.resourceApk = resourceApk;
    this.nativeLibs = nativeLibs;
    this.debugKeystore = debugKeystore;
  }

  @Override
  public DeterministicWriter newDeterministicWriter(final ActionExecutionContext ctx)
      throws IOException {

    ApkManifestCreator manifestCreator = new ApkManifestCreator(new ArtifactDigester() {
        @Override
        public byte[] getDigest(Artifact artifact) throws IOException {
          return ctx.getMetadataHandler().getMetadata(artifact).digest;
        }
    });

    final ApkManifest manifest = manifestCreator.createManifest();

    return new DeterministicWriter() {
      @Override
      public void writeOutputFile(OutputStream out) throws IOException {
        if (textOutput) {
          TextFormat.print(manifest, new PrintStream(out));
        } else {
          manifest.writeTo(out);
        }
      }
    };
  }

  @Override
  protected String computeKey() {

    // Use fake hashes for the purposes of the action's key, because the hashes are retrieved from
    // the MetadataHandler, which is available at only action-execution time. This should be ok
    // because if an input artifact changes (and hence its hash changes), the action should be rerun
    // anyway. This is more for the purpose of putting the structure of the output data into the
    // key.
    ApkManifestCreator manifestCreator = new ApkManifestCreator(new ArtifactDigester() {
        @Override
        public byte[] getDigest(Artifact artifact) {
          return Ints.toByteArray(artifact.getExecPathString().hashCode());
        }
    });

    ApkManifest manifest;
    try {
      manifest = manifestCreator.createManifest();
    } catch (IOException e) {
      // The ArtifactDigester shouldn't actually throw IOException, that's just for the
      // ArtifactDigester that uses the MetadataHandler.
      throw new IllegalStateException(e);
    }

    return new Fingerprint()
        .addString(GUID)
        .addBoolean(textOutput)
        .addBytes(manifest.toByteArray())
        .hexDigestAndReset();
  }

  private interface ArtifactDigester {
    byte[] getDigest(Artifact artifact) throws IOException;
  }

  private class ApkManifestCreator {

    private final ArtifactDigester artifactDigester;

    private ApkManifestCreator(ArtifactDigester artifactDigester) {
      this.artifactDigester = artifactDigester;
    }

    private ApkManifest createManifest() throws IOException {
      ApkManifest.Builder manifestBuilder = ApkManifest.newBuilder();

      for (Artifact jar : jars) {
        manifestBuilder.addJars(makeArtifactProto(jar));
      }

      manifestBuilder.setResourceApk(makeArtifactProto(resourceApk.getArtifact()));
      manifestBuilder.setAndroidManifest(makeArtifactProto(resourceApk.getManifest()));

      for (Map.Entry<String, Iterable<Artifact>> nativeLib : nativeLibs.getMap().entrySet()) {
        if (!Iterables.isEmpty(nativeLib.getValue())) {
          manifestBuilder.addNativeLibBuilder()
              .setArch(nativeLib.getKey())
              .addAllNativeLibs(makeArtifactProtos(nativeLib.getValue()));
        }
      }

      manifestBuilder.setAndroidSdk(createAndroidSdk(sdk));
      manifestBuilder.setDebugKeystore(makeArtifactProto(debugKeystore));
      return manifestBuilder.build();
    }

    private Iterable<ApkManifestOuterClass.Artifact> makeArtifactProtos(
        Iterable<Artifact> artifacts) throws IOException {

      ImmutableList.Builder<ApkManifestOuterClass.Artifact> protoArtifacts =
          ImmutableList.builder();
      for (Artifact artifact : artifacts) {
        protoArtifacts.add(makeArtifactProto(artifact));
      }
      return protoArtifacts.build();
    }

    private ApkManifestOuterClass.Artifact makeArtifactProto(Artifact artifact) throws IOException {
      byte[] digest = artifactDigester.getDigest(artifact);
      return ApkManifestOuterClass.Artifact.newBuilder()
          .setExecRootPath(artifact.getExecPathString())
          .setHash(ByteString.copyFrom(digest))
          .setLabel(artifact.getOwnerLabel().toString())
          .build();
    }

    private String getArtifactPath(Artifact artifact) {
      return artifact.getExecPathString();
    }
    
    private String getArtifactPath(FilesToRunProvider filesToRunProvider) {
      return filesToRunProvider.getExecutable().getExecPathString();
    }
    
    private ApkManifestOuterClass.AndroidSdk createAndroidSdk(AndroidSdkProvider sdk) {

      ApkManifestOuterClass.AndroidSdk.Builder sdkProto =
          ApkManifestOuterClass.AndroidSdk.newBuilder();

      sdkProto.setAapt(getArtifactPath(sdk.getAapt()));
      sdkProto.setAdb(getArtifactPath(sdk.getAdb()));
      sdkProto.setAidl(getArtifactPath(sdk.getAidl()));
      sdkProto.setAndroidJar(getArtifactPath(sdk.getAndroidJar()));
      sdkProto.setAnnotationsJar(getArtifactPath(sdk.getAnnotationsJar()));
      sdkProto.setDx(getArtifactPath(sdk.getDx()));
      sdkProto.setFrameworkAidl(getArtifactPath(sdk.getFrameworkAidl()));
      sdkProto.setJack(getArtifactPath(sdk.getJack()));
      sdkProto.setJill(getArtifactPath(sdk.getJill()));
      sdkProto.setMainDexClasses(getArtifactPath(sdk.getMainDexClasses()));
      sdkProto.setMainDexListCreator(getArtifactPath(sdk.getMainDexListCreator()));
      sdkProto.setProguard(getArtifactPath(sdk.getProguard()));
      sdkProto.setResourceExtractor(getArtifactPath(sdk.getResourceExtractor()));
      sdkProto.setShrinkedAndroidJar(getArtifactPath(sdk.getShrinkedAndroidJar()));
      sdkProto.setZipalign(getArtifactPath(sdk.getZipalign()));
      sdkProto.setBuildToolsVersion(sdk.getBuildToolsVersion());

      return sdkProto.build();
    }
  }
}