PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

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

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

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