PageRenderTime 58ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/appinventor/buildserver/src/com/google/appinventor/buildserver/Compiler.java

https://github.com/E-Hon/appinventor-sources
Java | 1328 lines | 942 code | 124 blank | 262 comment | 122 complexity | fbe80c1fbb03ca5fa95fca29d5a8cd44 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, MIT, LGPL-2.0, CPL-1.0, Apache-2.0, BitTorrent-1.0, JSON

Large files files are truncated, but you can click here to view the full file

  1. // -*- mode: java; c-basic-offset: 2; -*-
  2. // Copyright 2009-2011 Google, All Rights reserved
  3. // Copyright 2011-2012 MIT, All rights reserved
  4. // Released under the Apache License, Version 2.0
  5. // http://www.apache.org/licenses/LICENSE-2.0
  6. package com.google.appinventor.buildserver;
  7. import com.google.common.annotations.VisibleForTesting;
  8. import com.google.common.base.Charsets;
  9. import com.google.common.base.Strings;
  10. import com.google.common.collect.Lists;
  11. import com.google.common.collect.Sets;
  12. import com.google.common.io.Files;
  13. import com.google.common.io.Resources;
  14. import com.android.sdklib.build.ApkBuilder;
  15. import org.codehaus.jettison.json.JSONArray;
  16. import org.codehaus.jettison.json.JSONException;
  17. import org.codehaus.jettison.json.JSONObject;
  18. import java.awt.image.BufferedImage;
  19. import java.io.BufferedWriter;
  20. import java.io.ByteArrayOutputStream;
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.FileOutputStream;
  24. import java.io.OutputStreamWriter;
  25. import java.io.FileReader;
  26. import java.io.FileWriter;
  27. import java.io.IOException;
  28. import java.io.PrintStream;
  29. import java.io.Reader;
  30. import java.util.ArrayList;
  31. import java.util.Collections;
  32. import java.util.HashMap;
  33. import java.util.List;
  34. import java.util.Map;
  35. import java.util.Set;
  36. import java.util.concurrent.ConcurrentHashMap;
  37. import java.util.concurrent.ConcurrentMap;
  38. import java.util.logging.Level;
  39. import java.util.logging.Logger;
  40. import javax.imageio.ImageIO;
  41. /**
  42. * Main entry point for the YAIL compiler.
  43. *
  44. * <p>Supplies entry points for building Young Android projects.
  45. *
  46. * @author markf@google.com (Mark Friedman)
  47. * @author lizlooney@google.com (Liz Looney)
  48. */
  49. public final class Compiler {
  50. public static int currentProgress = 10;
  51. // Kawa and DX processes can use a lot of memory. We only launch one Kawa or DX process at a time.
  52. private static final Object SYNC_KAWA_OR_DX = new Object();
  53. // TODO(sharon): temporary until we add support for new activities
  54. private static final String LIST_ACTIVITY_CLASS =
  55. "com.google.appinventor.components.runtime.ListPickerActivity";
  56. private static final String WEBVIEW_ACTIVITY_CLASS =
  57. "com.google.appinventor.components.runtime.WebViewActivity";
  58. // Copied from SdkLevel.java (which isn't in our class path so we duplicate it here)
  59. private static final String LEVEL_GINGERBREAD_MR1 = "10";
  60. public static final String RUNTIME_FILES_DIR = "/files/";
  61. // Build info constants. Used for permissions, libraries and assets.
  62. private static final String ARMEABI_V7A_DIRECTORY = "armeabi-v7a";
  63. // Must match ComponentProcessor.ARMEABI_V7A_SUFFIX
  64. private static final String ARMEABI_V7A_SUFFIX = "-v7a";
  65. // Must match ComponentListGenerator.PERMISSIONS_TARGET
  66. private static final String PERMISSIONS_TARGET = "permissions";
  67. // Must match ComponentListGenerator.LIBRARIES_TARGET
  68. public static final String LIBRARIES_TARGET = "libraries";
  69. // Must match ComponentListGenerator.NATIVE_TARGET
  70. public static final String NATIVE_TARGET = "native";
  71. // Must match ComponentListGenerator.ASSETS_TARGET
  72. private static final String ASSETS_TARGET = "assets";
  73. // Must match Component.ASSET_DIRECTORY
  74. private static final String ASSET_DIRECTORY = "component";
  75. // Native library directory names
  76. private static final String LIBS_DIR_NAME = "libs";
  77. private static final String APK_LIB_DIR_NAME = "lib";
  78. private static final String ARMEABI_DIR_NAME = "armeabi";
  79. private static final String ARMEABI_V7A_DIR_NAME = "armeabi-v7a";
  80. private static final String DEFAULT_ICON =
  81. RUNTIME_FILES_DIR + "ya.png";
  82. private static final String DEFAULT_VERSION_CODE = "1";
  83. private static final String DEFAULT_VERSION_NAME = "1.0";
  84. private static final String DEFAULT_APP_NAME = "";
  85. private static final String DEFAULT_MIN_SDK = "4";
  86. private static final String COMPONENT_BUILD_INFO =
  87. RUNTIME_FILES_DIR + "simple_components_build_info.json";
  88. /*
  89. * Resource paths to yail runtime, runtime library files and sdk tools.
  90. * To get the real file paths, call getResource() with one of these constants.
  91. */
  92. private static final String SIMPLE_ANDROID_RUNTIME_JAR =
  93. RUNTIME_FILES_DIR + "AndroidRuntime.jar";
  94. private static final String ANDROID_RUNTIME =
  95. RUNTIME_FILES_DIR + "android.jar";
  96. private static final String MAC_AAPT_TOOL =
  97. "/tools/mac/aapt";
  98. private static final String WINDOWS_AAPT_TOOL =
  99. "/tools/windows/aapt";
  100. private static final String LINUX_AAPT_TOOL =
  101. "/tools/linux/aapt";
  102. private static final String KAWA_RUNTIME =
  103. RUNTIME_FILES_DIR + "kawa.jar";
  104. private static final String ACRA_RUNTIME =
  105. RUNTIME_FILES_DIR + "acra-4.4.0.jar";
  106. private static final String DX_JAR =
  107. RUNTIME_FILES_DIR + "dx.jar";
  108. @VisibleForTesting
  109. static final String YAIL_RUNTIME =
  110. RUNTIME_FILES_DIR + "runtime.scm";
  111. private static final String MAC_ZIPALIGN_TOOL =
  112. "/tools/mac/zipalign";
  113. private static final String WINDOWS_ZIPALIGN_TOOL =
  114. "/tools/windows/zipalign";
  115. private static final String LINUX_ZIPALIGN_TOOL =
  116. "/tools/linux/zipalign";
  117. // Logging support
  118. private static final Logger LOG = Logger.getLogger(Compiler.class.getName());
  119. private final ConcurrentMap<String, Set<String>> componentPermissions =
  120. new ConcurrentHashMap<String, Set<String>>();
  121. private final ConcurrentMap<String, Set<String>> componentLibraries =
  122. new ConcurrentHashMap<String, Set<String>>();
  123. private final ConcurrentMap<String, Set<String>> componentNativeLibraries =
  124. new ConcurrentHashMap<String, Set<String>>();
  125. private final ConcurrentMap<String, Set<String>> componentAssets =
  126. new ConcurrentHashMap<String, Set<String>>();
  127. /**
  128. * Map used to hold the names and paths of resources that we've written out
  129. * as temp files.
  130. * Don't use this map directly. Please call getResource() with one of the
  131. * constants above to get the (temp file) path to a resource.
  132. */
  133. private static final ConcurrentMap<String, File> resources =
  134. new ConcurrentHashMap<String, File>();
  135. // TODO(user,lizlooney): i18n here and in lines below that call String.format(...)
  136. private static final String ERROR_IN_STAGE =
  137. "Error: Your build failed due to an error in the %s stage, " +
  138. "not because of an error in your program.\n";
  139. private static final String ICON_ERROR =
  140. "Error: Your build failed because %s cannot be used as the application icon.\n";
  141. private static final String NO_USER_CODE_ERROR =
  142. "Error: No user code exists.\n";
  143. private static final String COMPILATION_ERROR =
  144. "Error: Your build failed due to an error when compiling %s.\n";
  145. private final Project project;
  146. private final Set<String> componentTypes;
  147. private final PrintStream out;
  148. private final PrintStream err;
  149. private final PrintStream userErrors;
  150. private final boolean isForCompanion;
  151. // Maximum ram that can be used by a child processes, in MB.
  152. private final int childProcessRamMb;
  153. private Set<String> librariesNeeded; // Set of component libraries
  154. private Set<String> nativeLibrariesNeeded; // Set of component native libraries
  155. private Set<String> assetsNeeded; // Set of component assets
  156. private File libsDir; // The directory that will contain any native libraries for packaging
  157. private String dexCacheDir;
  158. private boolean hasSecondDex = false; // True if classes2.dex should be added to the APK
  159. /*
  160. * Generate the set of Android permissions needed by this project.
  161. */
  162. @VisibleForTesting
  163. Set<String> generatePermissions() {
  164. // Before we can use componentPermissions, we have to call loadJsonInfo() for permissions.
  165. try {
  166. loadJsonInfo(componentPermissions, PERMISSIONS_TARGET);
  167. if (project != null) { // Only do this if we have a project (testing doesn't provide one :-( ).
  168. LOG.log(Level.INFO, "usesLocation = " + project.getUsesLocation());
  169. if (project.getUsesLocation().equals("True")) { // Add location permissions if any WebViewer requests it
  170. Set<String> locationPermissions = Sets.newHashSet(); // via a Property.
  171. // See ProjectEditor.recordLocationSettings()
  172. locationPermissions.add("android.permission.ACCESS_FINE_LOCATION");
  173. locationPermissions.add("android.permission.ACCESS_COARSE_LOCATION");
  174. locationPermissions.add("android.permission.ACCESS_MOCK_LOCATION");
  175. componentPermissions.put("WebViewer", locationPermissions);
  176. }
  177. }
  178. } catch (IOException e) {
  179. // This is fatal.
  180. e.printStackTrace();
  181. userErrors.print(String.format(ERROR_IN_STAGE, "Permissions"));
  182. return null;
  183. } catch (JSONException e) {
  184. // This is fatal, but shouldn't actually ever happen.
  185. e.printStackTrace();
  186. userErrors.print(String.format(ERROR_IN_STAGE, "Permissions"));
  187. return null;
  188. }
  189. Set<String> permissions = Sets.newHashSet();
  190. for (String componentType : componentTypes) {
  191. permissions.addAll(componentPermissions.get(componentType));
  192. }
  193. if (isForCompanion) { // This is so ACRA can do a logcat on phones older then Jelly Bean
  194. permissions.add("android.permission.READ_LOGS");
  195. }
  196. return permissions;
  197. }
  198. /*
  199. * Generate the set of Android libraries needed by this project.
  200. */
  201. @VisibleForTesting
  202. void generateLibraryNames() {
  203. // Before we can use componentLibraries, we have to call loadComponentLibraries().
  204. try {
  205. loadJsonInfo(componentLibraries, LIBRARIES_TARGET);
  206. } catch (IOException e) {
  207. // This is fatal.
  208. e.printStackTrace();
  209. userErrors.print(String.format(ERROR_IN_STAGE, "Libraries"));
  210. } catch (JSONException e) {
  211. // This is fatal, but shouldn't actually ever happen.
  212. e.printStackTrace();
  213. userErrors.print(String.format(ERROR_IN_STAGE, "Libraries"));
  214. }
  215. librariesNeeded = Sets.newHashSet();
  216. for (String componentType : componentTypes) {
  217. librariesNeeded.addAll(componentLibraries.get(componentType));
  218. }
  219. System.out.println("Libraries needed, n= " + librariesNeeded.size());
  220. }
  221. /*
  222. * Generate the set of conditionally included libraries needed by this project.
  223. */
  224. @VisibleForTesting
  225. void generateNativeLibraryNames() {
  226. // Before we can use componentLibraries, we have to call loadComponentLibraries().
  227. try {
  228. loadJsonInfo(componentNativeLibraries, NATIVE_TARGET);
  229. } catch (IOException e) {
  230. // This is fatal.
  231. e.printStackTrace();
  232. userErrors.print(String.format(ERROR_IN_STAGE, "Native Libraries"));
  233. } catch (JSONException e) {
  234. // This is fatal, but shouldn't actually ever happen.
  235. e.printStackTrace();
  236. userErrors.print(String.format(ERROR_IN_STAGE, "Native Libraries"));
  237. }
  238. nativeLibrariesNeeded = Sets.newHashSet();
  239. for (String componentType : componentTypes) {
  240. nativeLibrariesNeeded.addAll(componentNativeLibraries.get(componentType));
  241. }
  242. System.out.println("Native Libraries needed, n= " + nativeLibrariesNeeded.size());
  243. }
  244. /*
  245. * Generate the set of conditionally included assets needed by this project.
  246. */
  247. @VisibleForTesting
  248. void generateAssets() {
  249. // Before we can use componentAssets, we have to call loadJsonInfo() for assets.
  250. try {
  251. loadJsonInfo(componentAssets, ASSETS_TARGET);
  252. } catch (IOException e) {
  253. // This is fatal.
  254. e.printStackTrace();
  255. userErrors.print(String.format(ERROR_IN_STAGE, "Assets"));
  256. } catch (JSONException e) {
  257. // This is fatal, but shouldn't actually ever happen.
  258. e.printStackTrace();
  259. userErrors.print(String.format(ERROR_IN_STAGE, "Assets"));
  260. }
  261. assetsNeeded = Sets.newHashSet();
  262. for (String componentType : componentTypes) {
  263. assetsNeeded.addAll(componentAssets.get(componentType));
  264. }
  265. System.out.println("Component assets needed, n= " + assetsNeeded.size());
  266. }
  267. // This patches around a bug in AAPT (and other placed in Android)
  268. // where an ampersand in the name string breaks AAPT.
  269. private String cleanName(String name) {
  270. return name.replace("&", "and");
  271. }
  272. /*
  273. * Creates an AndroidManifest.xml file needed for the Android application.
  274. */
  275. private boolean writeAndroidManifest(File manifestFile, Set<String> permissionsNeeded) {
  276. // Create AndroidManifest.xml
  277. String mainClass = project.getMainClass();
  278. String packageName = Signatures.getPackageName(mainClass);
  279. String className = Signatures.getClassName(mainClass);
  280. String projectName = project.getProjectName();
  281. String vCode = (project.getVCode() == null) ? DEFAULT_VERSION_CODE : project.getVCode();
  282. String vName = (project.getVName() == null) ? DEFAULT_VERSION_NAME : cleanName(project.getVName());
  283. String aName = (project.getAName() == null) ? DEFAULT_APP_NAME : cleanName(project.getAName());
  284. String minSDK = DEFAULT_MIN_SDK;
  285. LOG.log(Level.INFO, "VCode: " + project.getVCode());
  286. LOG.log(Level.INFO, "VName: " + project.getVName());
  287. // TODO(user): Use com.google.common.xml.XmlWriter
  288. try {
  289. BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(manifestFile), "UTF-8"));
  290. out.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
  291. // TODO(markf) Allow users to set versionCode and versionName attributes.
  292. // See http://developer.android.com/guide/publishing/publishing.html for
  293. // more info.
  294. out.write("<manifest " +
  295. "xmlns:android=\"http://schemas.android.com/apk/res/android\" " +
  296. "package=\"" + packageName + "\" " +
  297. // TODO(markf): uncomment the following line when we're ready to enable publishing to the
  298. // Android Market.
  299. "android:versionCode=\"" + vCode +"\" " + "android:versionName=\"" + vName + "\" " +
  300. ">\n");
  301. // If we are building the Wireless Debugger (AppInventorDebugger) add the uses-feature tag which
  302. // is used by the Google Play store to determine which devices the app is available for. By adding
  303. // these lines we indicate that we use these features BUT THAT THEY ARE NOT REQUIRED so it is ok
  304. // to make the app available on devices that lack the feature. Without these lines the Play Store
  305. // makes a guess based on permissions and assumes that they are required features.
  306. if (isForCompanion) {
  307. out.write(" <uses-feature android:name=\"android.hardware.bluetooth\" android:required=\"false\" />\n");
  308. out.write(" <uses-feature android:name=\"android.hardware.location\" android:required=\"false\" />\n");
  309. out.write(" <uses-feature android:name=\"android.hardware.telephony\" android:required=\"false\" />\n");
  310. out.write(" <uses-feature android:name=\"android.hardware.location.network\" android:required=\"false\" />\n");
  311. out.write(" <uses-feature android:name=\"android.hardware.location.gps\" android:required=\"false\" />\n");
  312. out.write(" <uses-feature android:name=\"android.hardware.microphone\" android:required=\"false\" />\n");
  313. out.write(" <uses-feature android:name=\"android.hardware.touchscreen\" android:required=\"false\" />\n");
  314. out.write(" <uses-feature android:name=\"android.hardware.camera\" android:required=\"false\" />\n");
  315. out.write(" <uses-feature android:name=\"android.hardware.camera.autofocus\" android:required=\"false\" />\n");
  316. out.write(" <uses-feature android:name=\"android.hardware.wifi\" />\n"); // We actually require wifi
  317. }
  318. // Firebase requires at least API 10 (Gingerbread MR1)
  319. if (componentTypes.contains("FirebaseDB") && !isForCompanion) {
  320. minSDK = LEVEL_GINGERBREAD_MR1;
  321. }
  322. for (String permission : permissionsNeeded) {
  323. out.write(" <uses-permission android:name=\"" + permission + "\" />\n");
  324. }
  325. // The market will use the following to filter apps shown to devices that don't support
  326. // the specified SDK version. We right now support building for minSDK 4.
  327. // We might also want to allow users to specify minSdk version or targetSDK version.
  328. out.write(" <uses-sdk android:minSdkVersion=\"" + minSDK + "\" />\n");
  329. out.write(" <application ");
  330. // TODO(markf): The preparing to publish doc at
  331. // http://developer.android.com/guide/publishing/preparing.html suggests removing the
  332. // 'debuggable=true' but I'm not sure that our users would want that while they're still
  333. // testing their packaged apps. Maybe we should make that an option, somehow.
  334. // TODONE(jis): Turned off debuggable. No one really uses it and it represents a security
  335. // risk for App Inventor App end-users.
  336. out.write("android:debuggable=\"false\" ");
  337. if (aName.equals("")) {
  338. out.write("android:label=\"" + projectName + "\" ");
  339. } else {
  340. out.write("android:label=\"" + aName + "\" ");
  341. }
  342. out.write("android:icon=\"@drawable/ya\" ");
  343. if (isForCompanion) { // This is to hook into ACRA
  344. out.write("android:name=\"com.google.appinventor.components.runtime.ReplApplication\" ");
  345. } else {
  346. out.write("android:name=\"com.google.appinventor.components.runtime.multidex.MultiDexApplication\" ");
  347. }
  348. out.write(">\n");
  349. for (Project.SourceDescriptor source : project.getSources()) {
  350. String formClassName = source.getQualifiedName();
  351. // String screenName = formClassName.substring(formClassName.lastIndexOf('.') + 1);
  352. boolean isMain = formClassName.equals(mainClass);
  353. if (isMain) {
  354. // The main activity of the application.
  355. out.write(" <activity android:name=\"." + className + "\" ");
  356. } else {
  357. // A secondary activity of the application.
  358. out.write(" <activity android:name=\"" + formClassName + "\" ");
  359. }
  360. // This line is here for NearField and NFC. It keeps the activity from
  361. // restarting every time NDEF_DISCOVERED is signaled.
  362. // TODO: Check that this doesn't screw up other components. Also, it might be
  363. // better to do this programmatically when the NearField component is created, rather
  364. // than here in the manifest.
  365. if (componentTypes.contains("NearField") && !isForCompanion && isMain) {
  366. out.write("android:launchMode=\"singleTask\" ");
  367. } else if (isMain && isForCompanion) {
  368. out.write("android:launchMode=\"singleTop\" ");
  369. }
  370. out.write("android:windowSoftInputMode=\"stateHidden\" ");
  371. // The keyboard option prevents the app from stopping when a external (bluetooth)
  372. // keyboard is attached.
  373. out.write("android:configChanges=\"orientation|keyboardHidden|keyboard\">\n");
  374. out.write(" <intent-filter>\n");
  375. out.write(" <action android:name=\"android.intent.action.MAIN\" />\n");
  376. if (isMain) {
  377. out.write(" <category android:name=\"android.intent.category.LAUNCHER\" />\n");
  378. }
  379. out.write(" </intent-filter>\n");
  380. if (componentTypes.contains("NearField") && !isForCompanion && isMain) {
  381. // make the form respond to NDEF_DISCOVERED
  382. // this will trigger the form's onResume method
  383. // For now, we're handling text/plain only,but we can add more and make the Nearfield
  384. // component check the type.
  385. out.write(" <intent-filter>\n");
  386. out.write(" <action android:name=\"android.nfc.action.NDEF_DISCOVERED\" />\n");
  387. out.write(" <category android:name=\"android.intent.category.DEFAULT\" />\n");
  388. out.write(" <data android:mimeType=\"text/plain\" />\n");
  389. out.write(" </intent-filter>\n");
  390. }
  391. out.write(" </activity>\n");
  392. }
  393. // Add ListPickerActivity to the manifest only if a ListPicker component is used in the app
  394. if (componentTypes.contains("ListPicker")){
  395. out.write(" <activity android:name=\"" + LIST_ACTIVITY_CLASS + "\" " +
  396. "android:configChanges=\"orientation|keyboardHidden\" " +
  397. "android:screenOrientation=\"behind\">\n");
  398. out.write(" </activity>\n");
  399. }
  400. // Add WebViewActivity to the manifest only if a Twitter component is used in the app
  401. if (componentTypes.contains("Twitter")){
  402. out.write(" <activity android:name=\"" + WEBVIEW_ACTIVITY_CLASS + "\" " +
  403. "android:configChanges=\"orientation|keyboardHidden\" " +
  404. "android:screenOrientation=\"behind\">\n");
  405. out.write(" <intent-filter>\n");
  406. out.write(" <action android:name=\"android.intent.action.MAIN\" />\n");
  407. out.write(" </intent-filter>\n");
  408. out.write(" </activity>\n");
  409. }
  410. if (componentTypes.contains("BarcodeScanner")) {
  411. // Barcode Activity
  412. out.write(" <activity android:name=\"com.google.zxing.client.android.AppInvCaptureActivity\"\n");
  413. out.write(" android:screenOrientation=\"landscape\"\n");
  414. out.write(" android:stateNotNeeded=\"true\"\n");
  415. out.write(" android:configChanges=\"orientation|keyboardHidden\"\n");
  416. out.write(" android:theme=\"@android:style/Theme.NoTitleBar.Fullscreen\"\n");
  417. out.write(" android:windowSoftInputMode=\"stateAlwaysHidden\" />\n");
  418. }
  419. // BroadcastReceiver for Texting Component
  420. if (componentTypes.contains("Texting")) {
  421. System.out.println("Android Manifest: including <receiver> tag");
  422. out.write(
  423. "<receiver \n" +
  424. "android:name=\"com.google.appinventor.components.runtime.util.SmsBroadcastReceiver\" \n" +
  425. "android:enabled=\"true\" \n" +
  426. "android:exported=\"true\" >\n " +
  427. "<intent-filter> \n" +
  428. "<action android:name=\"android.provider.Telephony.SMS_RECEIVED\" /> \n" +
  429. "<action \n" +
  430. "android:name=\"com.google.android.apps.googlevoice.SMS_RECEIVED\" \n" +
  431. "android:permission=\"com.google.android.apps.googlevoice.permission.RECEIVE_SMS\" /> \n" +
  432. "</intent-filter> \n" +
  433. "</receiver> \n");
  434. }
  435. out.write(" </application>\n");
  436. out.write("</manifest>\n");
  437. out.close();
  438. } catch (IOException e) {
  439. e.printStackTrace();
  440. userErrors.print(String.format(ERROR_IN_STAGE, "manifest"));
  441. return false;
  442. }
  443. return true;
  444. }
  445. /**
  446. * Builds a YAIL project.
  447. *
  448. * @param project project to build
  449. * @param componentTypes component types used in the project
  450. * @param out stdout stream for compiler messages
  451. * @param err stderr stream for compiler messages
  452. * @param userErrors stream to write user-visible error messages
  453. * @param keystoreFilePath
  454. * @param childProcessRam maximum RAM for child processes, in MBs.
  455. * @return {@code true} if the compilation succeeds, {@code false} otherwise
  456. * @throws JSONException
  457. * @throws IOException
  458. */
  459. public static boolean compile(Project project, Set<String> componentTypes,
  460. PrintStream out, PrintStream err, PrintStream userErrors,
  461. boolean isForCompanion, String keystoreFilePath,
  462. int childProcessRam, String dexCacheDir) throws IOException, JSONException {
  463. long start = System.currentTimeMillis();
  464. // Create a new compiler instance for the compilation
  465. Compiler compiler = new Compiler(project, componentTypes, out, err, userErrors, isForCompanion,
  466. childProcessRam, dexCacheDir);
  467. // Get names of component-required libraries and assets.
  468. compiler.generateLibraryNames();
  469. compiler.generateNativeLibraryNames();
  470. compiler.generateAssets();
  471. // Create build directory.
  472. File buildDir = createDirectory(project.getBuildDirectory());
  473. // Prepare application icon.
  474. out.println("________Preparing application icon");
  475. File resDir = createDirectory(buildDir, "res");
  476. File drawableDir = createDirectory(resDir, "drawable");
  477. if (!compiler.prepareApplicationIcon(new File(drawableDir, "ya.png"))) {
  478. return false;
  479. }
  480. setProgress(10);
  481. // Create anim directory and animation xml files
  482. out.println("________Creating animation xml");
  483. File animDir = createDirectory(resDir, "anim");
  484. if (!compiler.createAnimationXml(animDir)) {
  485. return false;
  486. }
  487. // Determine android permissions.
  488. out.println("________Determining permissions");
  489. Set<String> permissionsNeeded = compiler.generatePermissions();
  490. if (permissionsNeeded == null) {
  491. return false;
  492. }
  493. setProgress(15);
  494. // Generate AndroidManifest.xml
  495. out.println("________Generating manifest file");
  496. File manifestFile = new File(buildDir, "AndroidManifest.xml");
  497. if (!compiler.writeAndroidManifest(manifestFile, permissionsNeeded)) {
  498. return false;
  499. }
  500. setProgress(20);
  501. // Insert native libraries
  502. out.println("________Attaching native libraries");
  503. if (!compiler.insertNativeLibraries(buildDir)) {
  504. return false;
  505. }
  506. // Add raw assets to sub-directory of project assets.
  507. out.println("________Attaching component assets");
  508. if (!compiler.attachComponentAssets()) {
  509. return false;
  510. }
  511. // Create class files.
  512. out.println("________Compiling source files");
  513. File classesDir = createDirectory(buildDir, "classes");
  514. if (!compiler.generateClasses(classesDir)) {
  515. return false;
  516. }
  517. setProgress(35);
  518. // Invoke dx on class files
  519. out.println("________Invoking DX");
  520. // TODO(markf): Running DX is now pretty slow (~25 sec overhead the first time and ~15 sec
  521. // overhead for subsequent runs). I think it's because of the need to dx the entire
  522. // kawa runtime every time. We should probably only do that once and then copy all the
  523. // kawa runtime dx files into the generated classes.dex (which would only contain the
  524. // files compiled for this project).
  525. // Aargh. It turns out that there's no way to manipulate .dex files to do the above. An
  526. // Android guy suggested an alternate approach of shipping the kawa runtime .dex file as
  527. // data with the application and then creating a new DexClassLoader using that .dex file
  528. // and with the original app class loader as the parent of the new one.
  529. // TODONE(zhuowei): Now using the new Android DX tool to merge dex files
  530. // Needs to specify a writable cache dir on the command line that persists after shutdown
  531. // Each pre-dexed file is identified via its MD5 hash (since the standard Android SDK's
  532. // method of identifying via a hash of the path won't work when files
  533. // are copied into temporary storage) and processed via a hacked up version of
  534. // Android SDK's Dex Ant task
  535. File tmpDir = createDirectory(buildDir, "tmp");
  536. String dexedClassesDir = tmpDir.getAbsolutePath();
  537. if (!compiler.runDx(classesDir, dexedClassesDir, false)) {
  538. return false;
  539. }
  540. setProgress(85);
  541. // Invoke aapt to package everything up
  542. out.println("________Invoking AAPT");
  543. File deployDir = createDirectory(buildDir, "deploy");
  544. String tmpPackageName = deployDir.getAbsolutePath() + File.separatorChar +
  545. project.getProjectName() + ".ap_";
  546. if (!compiler.runAaptPackage(manifestFile, resDir, tmpPackageName)) {
  547. return false;
  548. }
  549. setProgress(90);
  550. // Seal the apk with ApkBuilder
  551. out.println("________Invoking ApkBuilder");
  552. String apkAbsolutePath = deployDir.getAbsolutePath() + File.separatorChar +
  553. project.getProjectName() + ".apk";
  554. if (!compiler.runApkBuilder(apkAbsolutePath, tmpPackageName, dexedClassesDir)) {
  555. return false;
  556. }
  557. setProgress(95);
  558. // Sign the apk file
  559. out.println("________Signing the apk file");
  560. if (!compiler.runJarSigner(apkAbsolutePath, keystoreFilePath)) {
  561. return false;
  562. }
  563. // ZipAlign the apk file
  564. out.println("________ZipAligning the apk file");
  565. if (!compiler.runZipAlign(apkAbsolutePath, tmpDir)) {
  566. return false;
  567. }
  568. setProgress(100);
  569. out.println("Build finished in " +
  570. ((System.currentTimeMillis() - start) / 1000.0) + " seconds");
  571. return true;
  572. }
  573. /*
  574. * Creates all the animation xml files.
  575. */
  576. private boolean createAnimationXml(File animDir) {
  577. // Store the filenames, and their contents into a HashMap
  578. // so that we can easily add more, and also to iterate
  579. // through creating the files.
  580. Map<String, String> files = new HashMap<String, String>();
  581. files.put("fadein.xml", AnimationXmlConstants.FADE_IN_XML);
  582. files.put("fadeout.xml", AnimationXmlConstants.FADE_OUT_XML);
  583. files.put("hold.xml", AnimationXmlConstants.HOLD_XML);
  584. files.put("zoom_enter.xml", AnimationXmlConstants.ZOOM_ENTER);
  585. files.put("zoom_exit.xml", AnimationXmlConstants.ZOOM_EXIT);
  586. files.put("zoom_enter_reverse.xml", AnimationXmlConstants.ZOOM_ENTER_REVERSE);
  587. files.put("zoom_exit_reverse.xml", AnimationXmlConstants.ZOOM_EXIT_REVERSE);
  588. files.put("slide_exit.xml", AnimationXmlConstants.SLIDE_EXIT);
  589. files.put("slide_enter.xml", AnimationXmlConstants.SLIDE_ENTER);
  590. files.put("slide_exit_reverse.xml", AnimationXmlConstants.SLIDE_EXIT_REVERSE);
  591. files.put("slide_enter_reverse.xml", AnimationXmlConstants.SLIDE_ENTER_REVERSE);
  592. files.put("slide_v_exit.xml", AnimationXmlConstants.SLIDE_V_EXIT);
  593. files.put("slide_v_enter.xml", AnimationXmlConstants.SLIDE_V_ENTER);
  594. files.put("slide_v_exit_reverse.xml", AnimationXmlConstants.SLIDE_V_EXIT_REVERSE);
  595. files.put("slide_v_enter_reverse.xml", AnimationXmlConstants.SLIDE_V_ENTER_REVERSE);
  596. for (String filename : files.keySet()) {
  597. File file = new File(animDir, filename);
  598. if (!writeXmlFile(file, files.get(filename))) {
  599. return false;
  600. }
  601. }
  602. return true;
  603. }
  604. /*
  605. * Writes the given string input to the provided file.
  606. */
  607. private boolean writeXmlFile(File file, String input) {
  608. try {
  609. BufferedWriter writer = new BufferedWriter(new FileWriter(file));
  610. writer.write(input);
  611. writer.close();
  612. } catch (IOException e) {
  613. e.printStackTrace();
  614. return false;
  615. }
  616. return true;
  617. }
  618. /*
  619. * Runs ApkBuilder by using the API instead of calling its main method because the main method
  620. * can call System.exit(1), which will bring down our server.
  621. */
  622. private boolean runApkBuilder(String apkAbsolutePath, String zipArchive, String dexedClassesDir) {
  623. try {
  624. ApkBuilder apkBuilder =
  625. new ApkBuilder(apkAbsolutePath, zipArchive,
  626. dexedClassesDir + File.separator + "classes.dex", null, System.out);
  627. if (hasSecondDex) {
  628. apkBuilder.addFile(new File(dexedClassesDir + File.separator + "classes2.dex"),
  629. "classes2.dex");
  630. }
  631. apkBuilder.sealApk();
  632. return true;
  633. } catch (Exception e) {
  634. // This is fatal.
  635. e.printStackTrace();
  636. LOG.warning("YAIL compiler - ApkBuilder failed.");
  637. err.println("YAIL compiler - ApkBuilder failed.");
  638. userErrors.print(String.format(ERROR_IN_STAGE, "ApkBuilder"));
  639. return false;
  640. }
  641. }
  642. /**
  643. * Creates a new YAIL compiler.
  644. *
  645. * @param project project to build
  646. * @param componentTypes component types used in the project
  647. * @param out stdout stream for compiler messages
  648. * @param err stderr stream for compiler messages
  649. * @param userErrors stream to write user-visible error messages
  650. * @param childProcessMaxRam maximum RAM for child processes, in MBs.
  651. */
  652. @VisibleForTesting
  653. Compiler(Project project, Set<String> componentTypes, PrintStream out, PrintStream err,
  654. PrintStream userErrors, boolean isForCompanion,
  655. int childProcessMaxRam, String dexCacheDir) {
  656. this.project = project;
  657. this.componentTypes = componentTypes;
  658. this.out = out;
  659. this.err = err;
  660. this.userErrors = userErrors;
  661. this.isForCompanion = isForCompanion;
  662. this.childProcessRamMb = childProcessMaxRam;
  663. this.dexCacheDir = dexCacheDir;
  664. }
  665. /*
  666. * Runs the Kawa compiler in a separate process to generate classes. Returns false if not able to
  667. * create a class file for every source file in the project.
  668. */
  669. private boolean generateClasses(File classesDir) {
  670. try {
  671. List<Project.SourceDescriptor> sources = project.getSources();
  672. List<String> sourceFileNames = Lists.newArrayListWithCapacity(sources.size());
  673. List<String> classFileNames = Lists.newArrayListWithCapacity(sources.size());
  674. boolean userCodeExists = false;
  675. for (Project.SourceDescriptor source : sources) {
  676. String sourceFileName = source.getFile().getAbsolutePath();
  677. LOG.log(Level.INFO, "source file: " + sourceFileName);
  678. int srcIndex = sourceFileName.indexOf("/../src/");
  679. String sourceFileRelativePath = sourceFileName.substring(srcIndex + 8);
  680. String classFileName = (classesDir.getAbsolutePath() + "/" + sourceFileRelativePath)
  681. .replace(YoungAndroidConstants.YAIL_EXTENSION, ".class");
  682. if (System.getProperty("os.name").startsWith("Windows")) {
  683. classFileName = classesDir.getAbsolutePath()
  684. .replace(YoungAndroidConstants.YAIL_EXTENSION, ".class");
  685. }
  686. // Check whether user code exists by seeing if a left parenthesis exists at the beginning of
  687. // a line in the file
  688. // TODO(user): Replace with more robust test of empty source file.
  689. if (!userCodeExists) {
  690. Reader fileReader = new FileReader(sourceFileName);
  691. try {
  692. while (fileReader.ready()) {
  693. int c = fileReader.read();
  694. if (c == '(') {
  695. userCodeExists = true;
  696. break;
  697. }
  698. }
  699. } finally {
  700. fileReader.close();
  701. }
  702. }
  703. sourceFileNames.add(sourceFileName);
  704. classFileNames.add(classFileName);
  705. }
  706. if (!userCodeExists) {
  707. userErrors.print(NO_USER_CODE_ERROR);
  708. return false;
  709. }
  710. // Construct the class path including component libraries (jars)
  711. String classpath =
  712. getResource(KAWA_RUNTIME) + File.pathSeparator +
  713. getResource(ACRA_RUNTIME) + File.pathSeparator +
  714. getResource(SIMPLE_ANDROID_RUNTIME_JAR) + File.pathSeparator;
  715. // Add component library names to classpath
  716. System.out.println("Libraries Classpath, n " + librariesNeeded.size());
  717. for (String library : librariesNeeded) {
  718. classpath += getResource(RUNTIME_FILES_DIR + library) + File.pathSeparator;
  719. }
  720. classpath +=
  721. getResource(ANDROID_RUNTIME);
  722. System.out.println("Libraries Classpath = " + classpath);
  723. String yailRuntime = getResource(YAIL_RUNTIME);
  724. List<String> kawaCommandArgs = Lists.newArrayList();
  725. int mx = childProcessRamMb - 200;
  726. Collections.addAll(kawaCommandArgs,
  727. System.getProperty("java.home") + "/bin/java",
  728. "-Dfile.encoding=UTF-8",
  729. "-mx" + mx + "M",
  730. "-cp", classpath,
  731. "kawa.repl",
  732. "-f", yailRuntime,
  733. "-d", classesDir.getAbsolutePath(),
  734. "-P", Signatures.getPackageName(project.getMainClass()) + ".",
  735. "-C");
  736. // TODO(lizlooney) - we are currently using (and have always used) absolute paths for the
  737. // source file names. The resulting .class files contain references to the source file names,
  738. // including the name of the tmp directory that contains them. We may be able to avoid that
  739. // by using source file names that are relative to the project root and using the project
  740. // root as the working directory for the Kawa compiler process.
  741. kawaCommandArgs.addAll(sourceFileNames);
  742. kawaCommandArgs.add(yailRuntime);
  743. String[] kawaCommandLine = kawaCommandArgs.toArray(new String[kawaCommandArgs.size()]);
  744. long start = System.currentTimeMillis();
  745. // Capture Kawa compiler stderr. The ODE server parses out the warnings and errors and adds
  746. // them to the protocol buffer for logging purposes. (See
  747. // buildserver/ProjectBuilder.processCompilerOutout.
  748. ByteArrayOutputStream kawaOutputStream = new ByteArrayOutputStream();
  749. boolean kawaSuccess;
  750. synchronized (SYNC_KAWA_OR_DX) {
  751. kawaSuccess = Execution.execute(null, kawaCommandLine,
  752. System.out, new PrintStream(kawaOutputStream));
  753. }
  754. if (!kawaSuccess) {
  755. LOG.log(Level.SEVERE, "Kawa compile has failed.");
  756. }
  757. String kawaOutput = kawaOutputStream.toString();
  758. out.print(kawaOutput);
  759. String kawaCompileTimeMessage = "Kawa compile time: " +
  760. ((System.currentTimeMillis() - start) / 1000.0) + " seconds";
  761. out.println(kawaCompileTimeMessage);
  762. LOG.info(kawaCompileTimeMessage);
  763. // Check that all of the class files were created.
  764. // If they weren't, return with an error.
  765. for (String classFileName : classFileNames) {
  766. File classFile = new File(classFileName);
  767. if (!classFile.exists()) {
  768. LOG.log(Level.INFO, "Can't find class file: " + classFileName);
  769. String screenName = classFileName.substring(classFileName.lastIndexOf('/') + 1,
  770. classFileName.lastIndexOf('.'));
  771. userErrors.print(String.format(COMPILATION_ERROR, screenName));
  772. return false;
  773. }
  774. }
  775. } catch (IOException e) {
  776. e.printStackTrace();
  777. userErrors.print(String.format(ERROR_IN_STAGE, "compile"));
  778. return false;
  779. }
  780. return true;
  781. }
  782. private boolean runJarSigner(String apkAbsolutePath, String keystoreAbsolutePath) {
  783. // TODO(user): maybe make a command line flag for the jarsigner location
  784. String javaHome = System.getProperty("java.home");
  785. // This works on Mac OS X.
  786. File jarsignerFile = new File(javaHome + File.separator + "bin" +
  787. File.separator + "jarsigner");
  788. if (!jarsignerFile.exists()) {
  789. // This works when a JDK is installed with the JRE.
  790. jarsignerFile = new File(javaHome + File.separator + ".." + File.separator + "bin" +
  791. File.separator + "jarsigner");
  792. if (System.getProperty("os.name").startsWith("Windows")) {
  793. jarsignerFile = new File(javaHome + File.separator + ".." + File.separator + "bin" +
  794. File.separator + "jarsigner.exe");
  795. }
  796. if (!jarsignerFile.exists()) {
  797. LOG.warning("YAIL compiler - could not find jarsigner.");
  798. err.println("YAIL compiler - could not find jarsigner.");
  799. userErrors.print(String.format(ERROR_IN_STAGE, "JarSigner"));
  800. return false;
  801. }
  802. }
  803. String[] jarsignerCommandLine = {
  804. jarsignerFile.getAbsolutePath(),
  805. "-digestalg", "SHA1",
  806. "-sigalg", "MD5withRSA",
  807. "-keystore", keystoreAbsolutePath,
  808. "-storepass", "android",
  809. apkAbsolutePath,
  810. "AndroidKey"
  811. };
  812. if (!Execution.execute(null, jarsignerCommandLine, System.out, System.err)) {
  813. LOG.warning("YAIL compiler - jarsigner execution failed.");
  814. err.println("YAIL compiler - jarsigner execution failed.");
  815. userErrors.print(String.format(ERROR_IN_STAGE, "JarSigner"));
  816. return false;
  817. }
  818. return true;
  819. }
  820. private boolean runZipAlign(String apkAbsolutePath, File tmpDir) {
  821. // TODO(user): add zipalign tool appinventor->lib->android->tools->linux and windows
  822. // Need to make sure assets directory exists otherwise zipalign will fail.
  823. createDirectory(project.getAssetsDirectory());
  824. String zipAlignTool;
  825. String osName = System.getProperty("os.name");
  826. if (osName.equals("Mac OS X")) {
  827. zipAlignTool = MAC_ZIPALIGN_TOOL;
  828. } else if (osName.equals("Linux")) {
  829. zipAlignTool = LINUX_ZIPALIGN_TOOL;
  830. } else if (osName.startsWith("Windows")) {
  831. zipAlignTool = WINDOWS_ZIPALIGN_TOOL;
  832. } else {
  833. LOG.warning("YAIL compiler - cannot run ZIPALIGN on OS " + osName);
  834. err.println("YAIL compiler - cannot run ZIPALIGN on OS " + osName);
  835. userErrors.print(String.format(ERROR_IN_STAGE, "ZIPALIGN"));
  836. return false;
  837. }
  838. // TODO: create tmp file for zipaling result
  839. String zipAlignedPath = tmpDir.getAbsolutePath() + File.separator + "zipaligned.apk";
  840. // zipalign -f -v 4 infile.zip outfile.zip
  841. String[] zipAlignCommandLine = {
  842. getResource(zipAlignTool),
  843. "-f",
  844. "4",
  845. apkAbsolutePath,
  846. zipAlignedPath
  847. };
  848. long startZipAlign = System.currentTimeMillis();
  849. // Using System.err and System.out on purpose. Don't want to pollute build messages with
  850. // tools output
  851. if (!Execution.execute(null, zipAlignCommandLine, System.out, System.err)) {
  852. LOG.warning("YAIL compiler - ZIPALIGN execution failed.");
  853. err.println("YAIL compiler - ZIPALIGN execution failed.");
  854. userErrors.print(String.format(ERROR_IN_STAGE, "ZIPALIGN"));
  855. return false;
  856. }
  857. if (!copyFile(zipAlignedPath, apkAbsolutePath)) {
  858. LOG.warning("YAIL compiler - ZIPALIGN file copy failed.");
  859. err.println("YAIL compiler - ZIPALIGN file copy failed.");
  860. userErrors.print(String.format(ERROR_IN_STAGE, "ZIPALIGN"));
  861. return false;
  862. }
  863. String zipALignTimeMessage = "ZIPALIGN time: " +
  864. ((System.currentTimeMillis() - startZipAlign) / 1000.0) + " seconds";
  865. out.println(zipALignTimeMessage);
  866. LOG.info(zipALignTimeMessage);
  867. return true;
  868. }
  869. /*
  870. * Loads the icon for the application, either a user provided one or the default one.
  871. */
  872. private boolean prepareApplicationIcon(File outputPngFile) {
  873. String userSpecifiedIcon = Strings.nullToEmpty(project.getIcon());
  874. try {
  875. BufferedImage icon;
  876. if (!userSpecifiedIcon.isEmpty()) {
  877. File iconFile = new File(project.getAssetsDirectory(), userSpecifiedIcon);
  878. icon = ImageIO.read(iconFile);
  879. if (icon == null) {
  880. // This can happen if the iconFile isn't an image file.
  881. // For example, icon is null if the file is a .wav file.
  882. // TODO(lizlooney) - This happens if the user specifies a .ico file. We should fix that.
  883. userErrors.print(String.format(ICON_ERROR, userSpecifiedIcon));
  884. return false;
  885. }
  886. } else {
  887. // Load the default image.
  888. icon = ImageIO.read(Compiler.class.getResource(DEFAULT_ICON));
  889. }
  890. ImageIO.write(icon, "png", outputPngFile);
  891. } catch (Exception e) {
  892. e.printStackTrace();
  893. // If the user specified the icon, this is fatal.
  894. if (!userSpecifiedIcon.isEmpty()) {
  895. userErrors.print(String.format(ICON_ERROR, userSpecifiedIcon));
  896. return false;
  897. }
  898. }
  899. return true;
  900. }
  901. private boolean runDx(File classesDir, String dexedClassesDir, boolean secondTry) {
  902. List<File> libList = new ArrayList<File>();
  903. List<File> inputList = new ArrayList<File>();
  904. List<File> class2List = new ArrayList<File>();
  905. inputList.add(classesDir); //this is a directory, and won't be cached into the dex cache
  906. inputList.add(new File(getResource(SIMPLE_ANDROID_RUNTIME_JAR)));
  907. inputList.add(new File(getResource(KAWA_RUNTIME)));
  908. inputList.add(new File(getResource(ACRA_RUNTIME)));
  909. for (String library : librariesNeeded) {
  910. libList.add(new File(getResource(RUNTIME_FILES_DIR + library)));
  911. }
  912. int offset = libList.size();
  913. // Note: The choice of 12 libraries is arbitrary. We note that things
  914. // worked to put all libraries into the first classes.dex file when we
  915. // had 16 libraries and broke at 17. So this is a conservative number
  916. // to try.
  917. if (!secondTry) { // First time through, try base + 12 libraries
  918. if (offset > 12)
  919. offset = 12;
  920. } else {
  921. offset = 0; // Add NO libraries the second time through!
  922. }
  923. for (int i = 0; i < offset; i++) {
  924. inputList.add(libList.get(i));
  925. }
  926. if (libList.size() - offset > 0) { // Any left over for classes2?
  927. for (int i = offset; i < libList.size(); i++) {
  928. class2List.add(libList.get(i));
  929. }
  930. }
  931. DexExecTask dexTask = new DexExecTask();
  932. dexTask.setExecutable(getResource(DX_JAR));
  933. dexTask.setOutput(dexedClassesDir + File.separator + "classes.dex");
  934. dexTask.setChildProcessRamMb(childProcessRamMb);
  935. if (dexCacheDir == null) {
  936. dexTask.setDisableDexMerger(true);
  937. } else {
  938. createDirectory(new File(dexCacheDir));
  939. dexTask.setDexedLibs(dexCacheDir);
  940. }
  941. long startDx = System.currentTimeMillis();
  942. // Using System.err and System.out on purpose. Don't want to pollute build messages with
  943. // tools output
  944. boolean dxSuccess;
  945. synchronized (SYNC_KAWA_OR_DX) {
  946. setProgress(50);
  947. dxSuccess = dexTask.execute(inputList);
  948. if (dxSuccess && (class2List.size() > 0)) {
  949. setProgress(60);
  950. dexTask.setOutput(dexedClassesDir + File.separator + "classes2.dex");
  951. inputList = new ArrayList<File>();
  952. dxSuccess = dexTask.execute(class2List);
  953. setProgress(75);
  954. hasSecondDex = true;
  955. } else if (!dxSuccess) {
  956. // If we get into this block of code, it means that the Dexer
  957. // returned an error. It *might* be because of overflowing the
  958. // the fixed table of methods, but we cannot know that for
  959. // sure so we try Dexing again, but this time we put all
  960. // support libraries into classes2.dex. If this second pass
  961. // fails, we return the error to the user.
  962. LOG.info("DX execution failed, trying with fewer libraries.");
  963. if (secondTry) { // Already tried the more conservative approach!
  964. LOG.warning("YAIL compiler - DX execution failed (secondTry!).");
  965. err.println("YAIL compiler - DX execution failed.");
  966. userErrors.print(String.format(ERROR_IN_STAGE, "DX"));
  967. return false;
  968. } else {
  969. return runDx(classesDir, dexedClassesDir, true);
  970. }
  971. }
  972. }
  973. if (!dxSuccess) {
  974. LOG.warning("YAIL compiler - DX execution failed.");
  975. err.println("YAIL compiler - DX execution failed.");
  976. userErrors.print(String.format(ERROR_IN_STAGE, "DX"));
  977. return false;
  978. }
  979. String dxTimeMessage = "DX time: " +
  980. ((System.currentTimeMillis() - startDx) / 1000.0) + " seconds";
  981. out.println(dxTimeMessage);
  982. LOG.info(dxTimeMessage);
  983. return true;
  984. }
  985. private boolean runAaptPackage(File manifestFile, File resDir, String tmpPackageName) {
  986. // Need to make sure assets directory exists otherwise aapt will fail.
  987. createDirectory(project.getAssetsDirectory());
  988. String aaptTool;
  989. String osName = System.getProperty("os.name");
  990. if (osName.equals("Mac OS X")) {
  991. aaptTool = MAC_AAPT_TOOL;
  992. } else if (osName.equals("Linux")) {
  993. aaptTool = LINUX_AAPT_TOOL;
  994. } else if (osName.startsWith("Windows")) {
  995. aaptTool = WINDOWS_AAPT_TOOL;
  996. } else {
  997. LOG.warning("YAIL compiler - cannot run AAPT on OS " + osName);
  998. err.println("YAIL compiler - cannot run AAPT on OS " + osName);
  999. userErrors.print(String.format(ERROR_IN_STAGE, "AAPT"));
  1000. return false;
  1001. }
  1002. String[] aaptPackageCommandLine = {
  1003. getResource(aaptTool),
  1004. "package",
  1005. "-v",
  1006. "-f",
  1007. "-M", manifestFile.getAbsolutePath(),
  1008. "-S", resDir.getAbsolutePath(),
  1009. "-A", project.getAssetsDirectory().getAbsolutePath(),
  1010. "-I", getResource(ANDROID_RUNTIME),
  1011. "-F", tmpPackageName,
  1012. libsDir.getAbsolutePath()
  1013. };
  1014. long startAapt = System.currentTimeMillis();
  1015. // Using System.err and System.out on purpose. Don't want to pollute build messages with
  1016. // tools output
  1017. if (!Execution.execute(null, aaptPackageCommandLine, System.out, System.err)) {
  1018. LOG.warning("YAIL compiler - AAPT execution failed.");
  1019. err.println("YAIL compiler - AAPT execution failed.");
  1020. userErrors.print(String.format(ERROR_IN_STAGE, "AAPT"));
  1021. return false;
  1022. }
  1023. String aaptTimeMessage = "AAPT time: " +
  1024. ((System.currentTimeMillis() - startAapt) / 1000.0) + " seconds";
  1025. out.println(aaptTimeMessage);
  1026. LOG.info(aaptTimeMessage);
  1027. return true;
  1028. }
  1029. private boolean insertNativeLibraries(File buildDir){
  1030. out.println("________Copying native libraries");
  1031. libsDir = createDirectory(buildDir, LIBS_DIR_NAME);
  1032. File apkLibDir = createDirectory(libsDir, APK_LIB_DIR_NAME); // This dir will be copied to apk.
  1033. File armeabiDir = createDirectory(apkLibDir, ARMEABI_DIR_NAME);
  1034. File armeabiV7aDir = createDirectory(apkLibDir, ARMEABI_V7A_DIR_NAME);
  1035. /*
  1036. * Native libraries are targeted for particular processor architectures.
  1037. * Here, non-default architectures (ARMv5TE is default) are identified with suffixes
  1038. * before being placed in the appropriate directory with their suffix removed.
  1039. */
  1040. try {
  1041. for (String library : nativeLibrariesNeeded) {
  1042. if (library.endsWith(ARMEABI_V7A_SUFFIX)) { // Remove suffix and copy.
  1043. library = library.substring(0, library.length() - ARMEABI_V7A_SUFFIX.length());
  1044. Files.copy(new File(getResource(RUNTIME_FILES_DIR + ARMEABI_V7A_DIRECTORY +
  1045. "/" + library)), new File(armeabiV7aDir, library));
  1046. } else {
  1047. Files.copy(new File(getResource(RUNTIME_FILES_DIR + ARMEABI_DIR_NAME +
  1048. "/" + library)), new File(armeabiDir, library));
  1049. }
  1050. }
  1051. return true;
  1052. } catch (IOException e) {
  1053. e.printStackTrace();
  1054. userErrors.print(String.format(ERROR_IN_STAGE, "Native Code"));
  1055. return false;
  1056. }
  1057. }
  1058. private boolean attachComponentAssets() {
  1059. createDirectory(project.getAssetsDirectory()); // Needed to insert resources.
  1060. try {
  1061. // Gather non-library assets to be added t…

Large files files are truncated, but you can click here to view the full file