1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package vogar.android; 18 19 import java.io.File; 20 import java.util.LinkedList; 21 22 import vogar.Action; 23 import vogar.Classpath; 24 import vogar.Md5Cache; 25 import vogar.Result; 26 import vogar.Run; 27 import vogar.commands.Command; 28 import vogar.commands.Jack; 29 import vogar.tasks.Task; 30 31 /** 32 * Generates .dex.jar files using Jack. 33 */ 34 public final class JackDexTask extends Task { 35 private final Run run; 36 private final Classpath classpath; 37 private final boolean benchmark; 38 private final File inputFile; 39 private final Action action; 40 private final File localDex; 41 JackDexTask(Run run, Classpath classpath, boolean benchmark, String name, File inputFile, Action action, File localDex)42 public JackDexTask(Run run, Classpath classpath, boolean benchmark, String name, 43 File inputFile, Action action, File localDex) { 44 super("jackdex " + name); 45 this.run = run; 46 this.classpath = classpath; 47 this.benchmark = benchmark; 48 this.inputFile = inputFile; 49 this.action = action; 50 this.localDex = localDex; 51 } 52 execute()53 @Override protected Result execute() throws Exception { 54 run.mkdir.mkdirs(localDex.getParentFile()); 55 56 // What we do depends on the nature of the nature of inputFile. Ultimately we want a 57 // .dex.jar containing a classes.dex and resources. 58 // For the inputFile we might be given: 59 // 1) A .jack file: We must convert it to a dex.jar. Jack will handle including resources 60 // for us. 61 // 2) A .jar file containing .class files: We must ask Jack to convert it to a .dex.jar and 62 // we have to handle resources. 63 64 // There are several things below that seem fishy and could be improved with a different 65 // task workflow: 66 // 1) Having to deal with multiple classpath entries for inclusion: if the purpose is 67 // to convert a .jack or .jar to a .dex.jar file we *may* not need supporting classes. 68 // 2) The resource inclusion behavior is almost certainly incorrect and may need a change in 69 // Jack if we persist with including the entire classpath (2). 70 71 // Check the jack cache first. 72 Md5Cache jackCache = run.jackCache; 73 String classpathSubKey = jackCache.makeKey(classpath); 74 String cacheKey = null; 75 if (classpathSubKey != null) { 76 // If the classpath contains things that are not .jar or .jack files we get null 77 // classpathSubKey and do not cache. 78 79 // cacheKey includes all the arguments that could affect the output. 80 cacheKey = jackCache.makeKey(inputFile.toString(), classpathSubKey, 81 run.language.toString(), Boolean.toString(run.debugging)); 82 83 if (jackCache.getFromCache(localDex, cacheKey)) { 84 run.log.verbose("JackDexTask: Obtained " + localDex + " from jackCache"); 85 return Result.SUCCESS; 86 } 87 } 88 run.log.verbose("JackDexTask: Could not obtain " + localDex + " from jackCache"); 89 90 Jack jack = Jack.getJackCommand(run.log).outputDexZip(localDex.getPath()); 91 jack.sourceVersion(run.language.getJackSourceVersion()); 92 jack.minApiLevel(String.valueOf(run.language.getJackMinApilevel())); 93 if (run.debugging) { 94 jack.setDebug(); 95 } 96 97 // Jack imports resources from .jack files but not .jar files. We keep track of the .jar 98 // files so we can unpack them in reverse order (to maintain classpath ordering). 99 // Unfortunately, the inconsistency between .jack and .jar behavior makes it incorrect 100 // in some cases. 101 LinkedList<File> resourcesReverseClasspath = new LinkedList<>(); 102 addClassPathEntryToJack(jack, resourcesReverseClasspath, inputFile); 103 if (benchmark && action != null) { 104 for (File classpathElement : classpath.getElements()) { 105 addClassPathEntryToJack(jack, resourcesReverseClasspath, classpathElement); 106 } 107 } 108 109 if (!resourcesReverseClasspath.isEmpty()) { 110 File resourcesDir = run.localFile(localDex.getName() + "_resources"); 111 run.mkdir.mkdirs(resourcesDir); 112 // Unpack each classpath entry into resourcesDir. 113 for (File classpathEntry : resourcesReverseClasspath) { 114 unpackJar(classpathEntry, resourcesDir); 115 } 116 jack.importResource(resourcesDir.getPath()); 117 } 118 jack.invoke(); 119 120 if (cacheKey != null) { 121 // Store the result of the jack invocation in the jackCache. 122 jackCache.insert(cacheKey, localDex); 123 } 124 125 return Result.SUCCESS; 126 } 127 addClassPathEntryToJack(Jack jack, LinkedList<File> resourcesReverseClasspath, File classpathElement)128 private static void addClassPathEntryToJack(Jack jack, 129 LinkedList<File> resourcesReverseClasspath, File classpathElement) { 130 jack.importFile(classpathElement.getPath()); 131 if (!classpathElement.getName().endsWith(".jack")) { 132 resourcesReverseClasspath.addFirst(classpathElement); 133 } 134 } 135 unpackJar(File classpathEntry, File resourcesDir)136 private void unpackJar(File classpathEntry, File resourcesDir) { 137 new Command.Builder(run.log) 138 .workingDir(resourcesDir) 139 .args(run.javaPath("jar"), "xf", classpathEntry.getPath()) 140 .execute(); 141 } 142 } 143