1package org.robolectric.internal;
2
3import java.io.File;
4import java.net.URL;
5import java.nio.file.Files;
6import java.nio.file.Path;
7import java.nio.file.Paths;
8import org.robolectric.annotation.Config;
9import org.robolectric.res.FileFsFile;
10import org.robolectric.util.Logger;
11import org.robolectric.util.ReflectionHelpers;
12
13public class GradleManifestFactory implements ManifestFactory {
14  @Override
15  public ManifestIdentifier identify(Config config) {
16    if (config.constants() == Void.class) {
17      Logger.error("Field 'constants' not specified in @Config annotation");
18      Logger.error("This is required when using Robolectric with Gradle!");
19      throw new RuntimeException("No 'constants' field in @Config annotation!");
20    }
21
22    final String buildOutputDir = getBuildOutputDir(config);
23    final String type = getType(config);
24    final String flavor = getFlavor(config);
25    final String abiSplit = getAbiSplit(config);
26    final String packageName = config.packageName().isEmpty()
27        ? config.constants().getPackage().getName()
28        : config.packageName();
29
30    final FileFsFile res;
31    final FileFsFile assets;
32    final FileFsFile manifest;
33
34    if (FileFsFile.from(buildOutputDir, "data-binding-layout-out").exists()) {
35      // Android gradle plugin 1.5.0+ puts the merged layouts in data-binding-layout-out.
36      // https://github.com/robolectric/robolectric/issues/2143
37      res = FileFsFile.from(buildOutputDir, "data-binding-layout-out", flavor, type);
38    } else if (FileFsFile.from(buildOutputDir, "res", "merged").exists()) {
39      // res/merged added in Android Gradle plugin 1.3-beta1
40      res = FileFsFile.from(buildOutputDir, "res", "merged", flavor, type);
41    } else if (FileFsFile.from(buildOutputDir, "res").exists()) {
42      res = FileFsFile.from(buildOutputDir, "res", flavor, type);
43    } else {
44      res = FileFsFile.from(buildOutputDir, "bundles", flavor, type, "res");
45    }
46
47    if (!Config.DEFAULT_ASSET_FOLDER.equals(config.assetDir())
48            && FileFsFile.from(buildOutputDir, config.assetDir()).exists()) {
49      assets = FileFsFile.from(buildOutputDir, config.assetDir());
50    } else if (FileFsFile.from(buildOutputDir, "assets").exists()) {
51      assets = FileFsFile.from(buildOutputDir, "assets", flavor, type);
52    } else {
53      assets = FileFsFile.from(buildOutputDir, "bundles", flavor, type, "assets");
54    }
55
56    String manifestName = config.manifest();
57    URL manifestUrl = getClass().getClassLoader().getResource(manifestName);
58    if (manifestUrl != null && manifestUrl.getProtocol().equals("file")) {
59      manifest = FileFsFile.from(manifestUrl.getPath());
60    } else if (FileFsFile.from(buildOutputDir, "manifests", "full").exists()) {
61      manifest = FileFsFile.from(buildOutputDir, "manifests", "full", flavor, abiSplit, type, manifestName);
62    } else if (FileFsFile.from(buildOutputDir, "manifests", "aapt").exists()) {
63      // Android gradle plugin 2.2.0+ can put library manifest files inside of "aapt" instead of "full"
64      manifest = FileFsFile.from(buildOutputDir, "manifests", "aapt", flavor, abiSplit, type, manifestName);
65    } else {
66      manifest = FileFsFile.from(buildOutputDir, "bundles", flavor, abiSplit, type, manifestName);
67    }
68
69    return new ManifestIdentifier(manifest, res, assets, packageName, null);
70  }
71
72  private static String getBuildOutputDir(Config config) {
73    Path buildDir = Paths.get(config.buildDir(), "intermediates");
74    if (!Files.exists(buildDir)) {
75      // By default build dir is a relative path. However, the build dir lookup may fail if the
76      // working directory of the test configuration in Android Studio is not set to the module
77      // root directory (e.g it is set to the entire project root directory). Attempt to locate it
78      // relative to the constants class, which is generated in the build output directory.
79      String moduleRoot = config.constants().getResource("").toString().replace("file:", "");
80      int idx = moduleRoot.lastIndexOf(File.separator + "intermediates");
81      if (idx > 0) {
82        buildDir = Paths.get(moduleRoot.substring(0, idx), "intermediates");
83      } else {
84        Logger.error("Failed to locate build dir");
85      }
86    }
87    return buildDir.toString();
88  }
89
90  private static String getType(Config config) {
91    try {
92      return ReflectionHelpers.getStaticField(config.constants(), "BUILD_TYPE");
93    } catch (Throwable e) {
94      return null;
95    }
96  }
97
98  private static String getFlavor(Config config) {
99    try {
100      return ReflectionHelpers.getStaticField(config.constants(), "FLAVOR");
101    } catch (Throwable e) {
102      return null;
103    }
104  }
105
106  private static String getAbiSplit(Config config) {
107    try {
108      return config.abiSplit();
109    } catch (Throwable e) {
110      return null;
111    }
112  }
113}
114