1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.build; 18 19 import com.android.ide.eclipse.adt.AdtPlugin; 20 import com.android.ide.eclipse.adt.AndroidConstants; 21 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 22 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; 23 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 24 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; 25 import com.android.ide.eclipse.adt.internal.sdk.DexWrapper; 26 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 27 import com.android.prefs.AndroidLocation.AndroidLocationException; 28 import com.android.sdklib.IAndroidTarget; 29 import com.android.sdklib.SdkConstants; 30 import com.android.sdklib.build.ApkBuilder; 31 import com.android.sdklib.build.ApkCreationException; 32 import com.android.sdklib.build.DuplicateFileException; 33 import com.android.sdklib.build.SealedApkException; 34 import com.android.sdklib.build.ApkBuilder.JarStatus; 35 import com.android.sdklib.internal.build.DebugKeyProvider; 36 import com.android.sdklib.internal.build.SignedJarBuilder; 37 import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException; 38 39 import org.eclipse.core.resources.IFile; 40 import org.eclipse.core.resources.IFolder; 41 import org.eclipse.core.resources.IMarker; 42 import org.eclipse.core.resources.IProject; 43 import org.eclipse.core.resources.IResource; 44 import org.eclipse.core.resources.IWorkspace; 45 import org.eclipse.core.resources.IWorkspaceRoot; 46 import org.eclipse.core.resources.ResourcesPlugin; 47 import org.eclipse.core.runtime.CoreException; 48 import org.eclipse.core.runtime.IPath; 49 import org.eclipse.core.runtime.IStatus; 50 import org.eclipse.core.runtime.Status; 51 import org.eclipse.jdt.core.IClasspathEntry; 52 import org.eclipse.jdt.core.IJavaProject; 53 import org.eclipse.jdt.core.JavaCore; 54 import org.eclipse.jdt.core.JavaModelException; 55 import org.eclipse.jface.preference.IPreferenceStore; 56 57 import java.io.File; 58 import java.io.IOException; 59 import java.io.PrintStream; 60 import java.util.ArrayList; 61 import java.util.List; 62 63 /** 64 * Helper with methods for the last 3 steps of the generation of an APK. 65 * 66 * {@link #packageResources(IFile, IProject[], String, int, String, String)} packages the 67 * application resources using aapt into a zip file that is ready to be integrated into the apk. 68 * 69 * {@link #executeDx(IJavaProject, String, String, IJavaProject[])} will convert the Java byte 70 * code into the Dalvik bytecode. 71 * 72 * {@link #finalPackage(String, String, String, boolean, IJavaProject, IProject[], IJavaProject[], String, boolean)} 73 * will make the apk from all the previous components. 74 * 75 */ 76 public class PostCompilerHelper { 77 78 private final IProject mProject; 79 private final PrintStream mOutStream; 80 private final PrintStream mErrStream; 81 PostCompilerHelper(IProject project, PrintStream outStream, PrintStream errStream)82 public PostCompilerHelper(IProject project, PrintStream outStream, PrintStream errStream) { 83 mProject = project; 84 mOutStream = outStream; 85 mErrStream = errStream; 86 } 87 88 /** 89 * Packages the resources of the projet into a .ap_ file. 90 * @param manifestFile the manifest of the project. 91 * @param libProjects the list of library projects that this project depends on. 92 * @param resFilter an optional resource filter to be used with the -c option of aapt. If null 93 * no filters are used. 94 * @param versionCode an optional versionCode to be inserted in the manifest during packaging. 95 * If the value is <=0, no values are inserted. 96 * @param outputFolder where to write the resource ap_ file. 97 * @param outputFilename the name of the resource ap_ file. 98 * @return true if success. 99 */ packageResources(IFile manifestFile, IProject[] libProjects, String resFilter, int versionCode, String outputFolder, String outputFilename)100 public boolean packageResources(IFile manifestFile, IProject[] libProjects, String resFilter, 101 int versionCode, String outputFolder, String outputFilename) { 102 // need to figure out some path before we can execute aapt; 103 104 // get the resource folder 105 IFolder resFolder = mProject.getFolder(AndroidConstants.WS_RESOURCES); 106 107 // and the assets folder 108 IFolder assetsFolder = mProject.getFolder(AndroidConstants.WS_ASSETS); 109 110 // we need to make sure this one exists. 111 if (assetsFolder.exists() == false) { 112 assetsFolder = null; 113 } 114 115 IPath resLocation = resFolder.getLocation(); 116 IPath manifestLocation = manifestFile.getLocation(); 117 118 if (resLocation != null && manifestLocation != null) { 119 // list of res folder (main project + maybe libraries) 120 ArrayList<String> osResPaths = new ArrayList<String>(); 121 osResPaths.add(resLocation.toOSString()); //main project 122 123 // libraries? 124 if (libProjects != null) { 125 for (IProject lib : libProjects) { 126 IFolder libResFolder = lib.getFolder(SdkConstants.FD_RES); 127 if (libResFolder.exists()) { 128 osResPaths.add(libResFolder.getLocation().toOSString()); 129 } 130 } 131 } 132 133 String osManifestPath = manifestLocation.toOSString(); 134 135 String osAssetsPath = null; 136 if (assetsFolder != null) { 137 osAssetsPath = assetsFolder.getLocation().toOSString(); 138 } 139 140 // build the default resource package 141 if (executeAapt(osManifestPath, osResPaths, osAssetsPath, 142 outputFolder + File.separator + outputFilename, resFilter, 143 versionCode) == false) { 144 // aapt failed. Whatever files that needed to be marked 145 // have already been marked. We just return. 146 return false; 147 } 148 } 149 150 return true; 151 } 152 153 /** 154 * Makes the final package. Package the dex files, the temporary resource file into the final 155 * package file. 156 * @param intermediateApk The path to the temporary resource file. 157 * @param dex The path to the dex file. 158 * @param output The path to the final package file to create. 159 * @param debugSign whether the apk must be signed with the debug key. 160 * @param javaProject the java project being compiled 161 * @param libProjects an optional list of library projects (can be null) 162 * @param referencedJavaProjects referenced projects. 163 * @param abiFilter an optional filter. If not null, then only the matching ABI is included in 164 * the final archive 165 * @param debuggable whether the project manifest has debuggable==true. If true, any gdbserver 166 * executables will be packaged with the native libraries. 167 * @return true if success, false otherwise. 168 */ finalPackage(String intermediateApk, String dex, String output, boolean debugSign, final IJavaProject javaProject, IProject[] libProjects, IJavaProject[] referencedJavaProjects, String abiFilter, boolean debuggable)169 public boolean finalPackage(String intermediateApk, String dex, String output, 170 boolean debugSign, final IJavaProject javaProject, IProject[] libProjects, 171 IJavaProject[] referencedJavaProjects, String abiFilter, boolean debuggable) { 172 173 IProject project = javaProject.getProject(); 174 175 String keystoreOsPath = null; 176 if (debugSign) { 177 IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); 178 keystoreOsPath = store.getString(AdtPrefs.PREFS_CUSTOM_DEBUG_KEYSTORE); 179 if (keystoreOsPath == null || new File(keystoreOsPath).isFile() == false) { 180 try { 181 keystoreOsPath = DebugKeyProvider.getDefaultKeyStoreOsPath(); 182 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject, 183 Messages.ApkBuilder_Using_Default_Key); 184 } catch (KeytoolException e) { 185 String eMessage = e.getMessage(); 186 187 // mark the project with the standard message 188 String msg = String.format(Messages.Final_Archive_Error_s, eMessage); 189 BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg, 190 IMarker.SEVERITY_ERROR); 191 192 // output more info in the console 193 AdtPlugin.printErrorToConsole(mProject, 194 msg, 195 String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()), 196 Messages.ApkBuilder_Update_or_Execute_manually_s, 197 e.getCommandLine()); 198 199 return false; 200 } catch (AndroidLocationException e) { 201 String eMessage = e.getMessage(); 202 203 // mark the project with the standard message 204 String msg = String.format(Messages.Final_Archive_Error_s, eMessage); 205 BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg, 206 IMarker.SEVERITY_ERROR); 207 208 return false; 209 } 210 } else { 211 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject, 212 String.format(Messages.ApkBuilder_Using_s_To_Sign, keystoreOsPath)); 213 } 214 } 215 216 217 try { 218 ApkBuilder apkBuilder = new ApkBuilder(output, intermediateApk, dex, keystoreOsPath, 219 AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE ? 220 AdtPlugin.getOutPrintStream(project, null): null); 221 apkBuilder.setDebugMode(debuggable); 222 223 // Now we write the standard resources from the project and the referenced projects. 224 writeStandardResources(apkBuilder, javaProject, referencedJavaProjects); 225 226 // Now we write the standard resources from the external jars 227 for (String libraryOsPath : getExternalJars()) { 228 JarStatus status = apkBuilder.addResourcesFromJar(new File(libraryOsPath)); 229 230 // check if we found native libraries in the external library. This 231 // constitutes an error or warning depending on if they are in lib/ 232 if (status.getNativeLibs().size() > 0) { 233 String libName = new File(libraryOsPath).getName(); 234 String msg = String.format( 235 "Native libraries detected in '%1$s'. See console for more information.", 236 libName); 237 238 BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, 239 msg, 240 status.hasNativeLibsConflicts() || 241 AdtPrefs.getPrefs().getBuildForceErrorOnNativeLibInJar() ? 242 IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING); 243 244 ArrayList<String> consoleMsgs = new ArrayList<String>(); 245 consoleMsgs.add(String.format( 246 "The library '%1$s' contains native libraries that will not run on the device.", 247 libName)); 248 if (status.hasNativeLibsConflicts()) { 249 consoleMsgs.add("Additionally some of those libraries will interfer with the installation of the application because of their location in lib/"); 250 consoleMsgs.add("lib/ is reserved for NDK libraries."); 251 } 252 consoleMsgs.add("The following libraries were found:"); 253 for (String lib : status.getNativeLibs()) { 254 consoleMsgs.add(" - " + lib); 255 } 256 AdtPlugin.printErrorToConsole(mProject, 257 consoleMsgs.toArray()); 258 259 return false; 260 } 261 } 262 263 // now write the native libraries. 264 // First look if the lib folder is there. 265 IResource libFolder = mProject.findMember(SdkConstants.FD_NATIVE_LIBS); 266 if (libFolder != null && libFolder.exists() && 267 libFolder.getType() == IResource.FOLDER) { 268 // get a File for the folder. 269 apkBuilder.addNativeLibraries(libFolder.getLocation().toFile(), abiFilter); 270 } 271 272 // write the native libraries for the library projects. 273 if (libProjects != null) { 274 for (IProject lib : libProjects) { 275 libFolder = lib.findMember(SdkConstants.FD_NATIVE_LIBS); 276 if (libFolder != null && libFolder.exists() && 277 libFolder.getType() == IResource.FOLDER) { 278 apkBuilder.addNativeLibraries(libFolder.getLocation().toFile(), abiFilter); 279 } 280 } 281 } 282 283 // seal the APK. 284 apkBuilder.sealApk(); 285 return true; 286 } catch (CoreException e) { 287 // mark project and return 288 String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage()); 289 AdtPlugin.printErrorToConsole(mProject, msg); 290 BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg, 291 IMarker.SEVERITY_ERROR); 292 } catch (ApkCreationException e) { 293 // mark project and return 294 String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage()); 295 AdtPlugin.printErrorToConsole(mProject, msg); 296 BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg, 297 IMarker.SEVERITY_ERROR); 298 } catch (DuplicateFileException e) { 299 String msg1 = String.format( 300 "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s", 301 e.getArchivePath(), e.getFile1(), e.getFile2()); 302 String msg2 = String.format(Messages.Final_Archive_Error_s, msg1); 303 AdtPlugin.printErrorToConsole(mProject, msg2); 304 BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg2, 305 IMarker.SEVERITY_ERROR); 306 } catch (SealedApkException e) { 307 // this won't happen as we control when the apk is sealed. 308 } catch (Exception e) { 309 // try to catch other exception to actually display an error. This will be useful 310 // if we get an NPE or something so that we can at least notify the user that something 311 // went wrong (otherwise the build appears to succeed but the zip archive is not closed 312 // and therefore invalid. 313 String msg = e.getMessage(); 314 if (msg == null) { 315 msg = e.getClass().getCanonicalName(); 316 } 317 318 msg = String.format("Unknown error: %1$s", msg); 319 AdtPlugin.printErrorToConsole(mProject, msg); 320 BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg, 321 IMarker.SEVERITY_ERROR); 322 } 323 324 return false; 325 } 326 327 /** 328 * Execute the Dx tool for dalvik code conversion. 329 * @param javaProject The java project 330 * @param osBinPath the path to the output folder of the project 331 * @param osOutFilePath the path of the dex file to create. 332 * @param referencedJavaProjects the list of referenced projects for this project. 333 * 334 * @throws CoreException 335 */ executeDx(IJavaProject javaProject, String osBinPath, String osOutFilePath, IJavaProject[] referencedJavaProjects)336 boolean executeDx(IJavaProject javaProject, String osBinPath, String osOutFilePath, 337 IJavaProject[] referencedJavaProjects) throws CoreException { 338 339 IAndroidTarget target = Sdk.getCurrent().getTarget(mProject); 340 AndroidTargetData targetData = Sdk.getCurrent().getTargetData(target); 341 if (targetData == null) { 342 throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, 343 Messages.ApkBuilder_UnableBuild_Dex_Not_loaded)); 344 } 345 346 // get the dex wrapper 347 DexWrapper wrapper = targetData.getDexWrapper(); 348 349 if (wrapper == null) { 350 throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, 351 Messages.ApkBuilder_UnableBuild_Dex_Not_loaded)); 352 } 353 354 try { 355 // get the list of libraries to include with the source code 356 String[] libraries = getExternalJars(); 357 358 // get the list of referenced projects output to add 359 String[] projectOutputs = getProjectOutputs(referencedJavaProjects); 360 361 String[] fileNames = new String[1 + projectOutputs.length + libraries.length]; 362 363 // first this project output 364 fileNames[0] = osBinPath; 365 366 // then other project output 367 System.arraycopy(projectOutputs, 0, fileNames, 1, projectOutputs.length); 368 369 // then external jars. 370 System.arraycopy(libraries, 0, fileNames, 1 + projectOutputs.length, libraries.length); 371 372 int res = wrapper.run(osOutFilePath, fileNames, 373 AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE, 374 mOutStream, mErrStream); 375 376 if (res != 0) { 377 // output error message and marker the project. 378 String message = String.format(Messages.Dalvik_Error_d, 379 res); 380 AdtPlugin.printErrorToConsole(mProject, message); 381 BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, 382 message, IMarker.SEVERITY_ERROR); 383 return false; 384 } 385 } catch (Throwable ex) { 386 String message = ex.getMessage(); 387 if (message == null) { 388 message = ex.getClass().getCanonicalName(); 389 } 390 message = String.format(Messages.Dalvik_Error_s, message); 391 AdtPlugin.printErrorToConsole(mProject, message); 392 BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, 393 message, IMarker.SEVERITY_ERROR); 394 if ((ex instanceof NoClassDefFoundError) 395 || (ex instanceof NoSuchMethodError)) { 396 AdtPlugin.printErrorToConsole(mProject, Messages.Incompatible_VM_Warning, 397 Messages.Requires_1_5_Error); 398 } 399 return false; 400 } 401 402 return true; 403 } 404 405 406 /** 407 * Executes aapt. If any error happen, files or the project will be marked. 408 * @param osManifestPath The path to the manifest file 409 * @param osResPath The path to the res folder 410 * @param osAssetsPath The path to the assets folder. This can be null. 411 * @param osOutFilePath The path to the temporary resource file to create. 412 * @param configFilter The configuration filter for the resources to include 413 * (used with -c option, for example "port,en,fr" to include portrait, English and French 414 * resources.) 415 * @param versionCode optional version code to insert in the manifest during packaging. If <=0 416 * then no value is inserted 417 * @return true if success, false otherwise. 418 */ executeAapt(String osManifestPath, List<String> osResPaths, String osAssetsPath, String osOutFilePath, String configFilter, int versionCode)419 private boolean executeAapt(String osManifestPath, 420 List<String> osResPaths, String osAssetsPath, String osOutFilePath, 421 String configFilter, int versionCode) { 422 IAndroidTarget target = Sdk.getCurrent().getTarget(mProject); 423 424 // Create the command line. 425 ArrayList<String> commandArray = new ArrayList<String>(); 426 commandArray.add(target.getPath(IAndroidTarget.AAPT)); 427 commandArray.add("package"); //$NON-NLS-1$ 428 commandArray.add("-f");//$NON-NLS-1$ 429 if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) { 430 commandArray.add("-v"); //$NON-NLS-1$ 431 } 432 433 // if more than one res, this means there's a library (or more) and we need 434 // to activate the auto-add-overlay 435 if (osResPaths.size() > 1) { 436 commandArray.add("--auto-add-overlay"); //$NON-NLS-1$ 437 } 438 439 if (versionCode > 0) { 440 commandArray.add("--version-code"); //$NON-NLS-1$ 441 commandArray.add(Integer.toString(versionCode)); 442 } 443 444 if (configFilter != null) { 445 commandArray.add("-c"); //$NON-NLS-1$ 446 commandArray.add(configFilter); 447 } 448 449 commandArray.add("-M"); //$NON-NLS-1$ 450 commandArray.add(osManifestPath); 451 452 for (String path : osResPaths) { 453 commandArray.add("-S"); //$NON-NLS-1$ 454 commandArray.add(path); 455 } 456 457 if (osAssetsPath != null) { 458 commandArray.add("-A"); //$NON-NLS-1$ 459 commandArray.add(osAssetsPath); 460 } 461 462 commandArray.add("-I"); //$NON-NLS-1$ 463 commandArray.add(target.getPath(IAndroidTarget.ANDROID_JAR)); 464 465 commandArray.add("-F"); //$NON-NLS-1$ 466 commandArray.add(osOutFilePath); 467 468 String command[] = commandArray.toArray( 469 new String[commandArray.size()]); 470 471 if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) { 472 StringBuilder sb = new StringBuilder(); 473 for (String c : command) { 474 sb.append(c); 475 sb.append(' '); 476 } 477 AdtPlugin.printToConsole(mProject, sb.toString()); 478 } 479 480 // launch 481 int execError = 1; 482 try { 483 // launch the command line process 484 Process process = Runtime.getRuntime().exec(command); 485 486 // list to store each line of stderr 487 ArrayList<String> results = new ArrayList<String>(); 488 489 // get the output and return code from the process 490 execError = BaseBuilder.grabProcessOutput(mProject, process, results); 491 492 // attempt to parse the error output 493 boolean parsingError = AaptParser.parseOutput(results, mProject); 494 495 // if we couldn't parse the output we display it in the console. 496 if (parsingError) { 497 if (execError != 0) { 498 AdtPlugin.printErrorToConsole(mProject, results.toArray()); 499 } else { 500 AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, 501 results.toArray()); 502 } 503 } 504 505 // We need to abort if the exec failed. 506 if (execError != 0) { 507 // if the exec failed, and we couldn't parse the error output (and therefore 508 // not all files that should have been marked, were marked), we put a generic 509 // marker on the project and abort. 510 if (parsingError) { 511 BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, 512 Messages.Unparsed_AAPT_Errors, 513 IMarker.SEVERITY_ERROR); 514 } 515 516 // abort if exec failed. 517 return false; 518 } 519 } catch (IOException e1) { 520 String msg = String.format(Messages.AAPT_Exec_Error, command[0]); 521 BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg, 522 IMarker.SEVERITY_ERROR); 523 return false; 524 } catch (InterruptedException e) { 525 String msg = String.format(Messages.AAPT_Exec_Error, command[0]); 526 BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg, 527 IMarker.SEVERITY_ERROR); 528 return false; 529 } 530 531 return true; 532 } 533 534 535 /** 536 * Writes the standard resources of a project and its referenced projects 537 * into a {@link SignedJarBuilder}. 538 * Standard resources are non java/aidl files placed in the java package folders. 539 * @param apkBuilder the {@link ApkBuilder}. 540 * @param javaProject the javaProject object. 541 * @param referencedJavaProjects the java projects that this project references. 542 * @throws ApkCreationException if an error occurred 543 * @throws SealedApkException if the APK is already sealed. 544 * @throws DuplicateFileException if a file conflicts with another already added to the APK 545 * at the same location inside the APK archive. 546 * @throws CoreException 547 */ writeStandardResources(ApkBuilder apkBuilder, IJavaProject javaProject, IJavaProject[] referencedJavaProjects)548 private void writeStandardResources(ApkBuilder apkBuilder, IJavaProject javaProject, 549 IJavaProject[] referencedJavaProjects) 550 throws DuplicateFileException, ApkCreationException, SealedApkException, 551 CoreException { 552 IWorkspace ws = ResourcesPlugin.getWorkspace(); 553 IWorkspaceRoot wsRoot = ws.getRoot(); 554 555 // create a list of path already put into the archive, in order to detect conflict 556 ArrayList<String> list = new ArrayList<String>(); 557 558 writeStandardProjectResources(apkBuilder, javaProject, wsRoot, list); 559 560 for (IJavaProject referencedJavaProject : referencedJavaProjects) { 561 // only include output from non android referenced project 562 // (This is to handle the case of reference Android projects in the context of 563 // instrumentation projects that need to reference the projects to be tested). 564 if (referencedJavaProject.getProject().hasNature( 565 AndroidConstants.NATURE_DEFAULT) == false) { 566 writeStandardProjectResources(apkBuilder, referencedJavaProject, wsRoot, list); 567 } 568 } 569 } 570 571 /** 572 * Writes the standard resources of a {@link IJavaProject} into a {@link SignedJarBuilder}. 573 * Standard resources are non java/aidl files placed in the java package folders. 574 * @param jarBuilder the {@link ApkBuilder}. 575 * @param javaProject the javaProject object. 576 * @param wsRoot the {@link IWorkspaceRoot}. 577 * @param list a list of files already added to the archive, to detect conflicts. 578 * @throws ApkCreationException if an error occurred 579 * @throws SealedApkException if the APK is already sealed. 580 * @throws DuplicateFileException if a file conflicts with another already added to the APK 581 * at the same location inside the APK archive. 582 */ writeStandardProjectResources(ApkBuilder apkBuilder, IJavaProject javaProject, IWorkspaceRoot wsRoot, ArrayList<String> list)583 private void writeStandardProjectResources(ApkBuilder apkBuilder, 584 IJavaProject javaProject, IWorkspaceRoot wsRoot, ArrayList<String> list) 585 throws DuplicateFileException, ApkCreationException, SealedApkException { 586 // get the source pathes 587 ArrayList<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject); 588 589 // loop on them and then recursively go through the content looking for matching files. 590 for (IPath sourcePath : sourceFolders) { 591 IResource sourceResource = wsRoot.findMember(sourcePath); 592 if (sourceResource != null && sourceResource.getType() == IResource.FOLDER) { 593 // get a File from the IResource 594 apkBuilder.addSourceFolder(sourceResource.getLocation().toFile()); 595 } 596 } 597 } 598 599 /** 600 * Returns an array of external jar files used by the project. 601 * @return an array of OS-specific absolute file paths 602 */ getExternalJars()603 private final String[] getExternalJars() { 604 // get a java project from it 605 IJavaProject javaProject = JavaCore.create(mProject); 606 607 IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); 608 609 ArrayList<String> oslibraryList = new ArrayList<String>(); 610 IClasspathEntry[] classpaths = javaProject.readRawClasspath(); 611 if (classpaths != null) { 612 for (IClasspathEntry e : classpaths) { 613 if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY || 614 e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { 615 // if this is a classpath variable reference, we resolve it. 616 if (e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { 617 e = JavaCore.getResolvedClasspathEntry(e); 618 } 619 620 // get the IPath 621 IPath path = e.getPath(); 622 623 // check the name ends with .jar 624 if (AndroidConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) { 625 IResource resource = wsRoot.findMember(path); 626 if (resource != null && resource.exists() && 627 resource.getType() == IResource.FILE) { 628 oslibraryList.add(resource.getLocation().toOSString()); 629 } else { 630 // if the jar path doesn't match a workspace resource, 631 // then we get an OSString and check if this links to a valid file. 632 String osFullPath = path.toOSString(); 633 634 File f = new File(osFullPath); 635 if (f.exists()) { 636 oslibraryList.add(osFullPath); 637 } else { 638 String message = String.format( Messages.Couldnt_Locate_s_Error, 639 path); 640 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, 641 mProject, message); 642 643 // Also put a warning marker on the project 644 BaseProjectHelper.markResource(mProject, 645 AndroidConstants.MARKER_PACKAGING, message, 646 IMarker.SEVERITY_WARNING); 647 } 648 } 649 } 650 } 651 } 652 } 653 654 return oslibraryList.toArray(new String[oslibraryList.size()]); 655 } 656 657 /** 658 * Returns the list of the output folders for the specified {@link IJavaProject} objects, if 659 * they are Android projects. 660 * 661 * @param referencedJavaProjects the java projects. 662 * @return an array, always. Can be empty. 663 * @throws CoreException 664 */ getProjectOutputs(IJavaProject[] referencedJavaProjects)665 private String[] getProjectOutputs(IJavaProject[] referencedJavaProjects) throws CoreException { 666 ArrayList<String> list = new ArrayList<String>(); 667 668 IWorkspace ws = ResourcesPlugin.getWorkspace(); 669 IWorkspaceRoot wsRoot = ws.getRoot(); 670 671 for (IJavaProject javaProject : referencedJavaProjects) { 672 // only include output from non android referenced project 673 // (This is to handle the case of reference Android projects in the context of 674 // instrumentation projects that need to reference the projects to be tested). 675 if (javaProject.getProject().hasNature(AndroidConstants.NATURE_DEFAULT) == false) { 676 // get the output folder 677 IPath path = null; 678 try { 679 path = javaProject.getOutputLocation(); 680 } catch (JavaModelException e) { 681 continue; 682 } 683 684 IResource outputResource = wsRoot.findMember(path); 685 if (outputResource != null && outputResource.getType() == IResource.FOLDER) { 686 String outputOsPath = outputResource.getLocation().toOSString(); 687 688 list.add(outputOsPath); 689 } 690 } 691 } 692 693 return list.toArray(new String[list.size()]); 694 } 695 696 /** 697 * Checks a {@link IFile} to make sure it should be packaged as standard resources. 698 * @param file the IFile representing the file. 699 * @return true if the file should be packaged as standard java resources. 700 */ checkFileForPackaging(IFile file)701 static boolean checkFileForPackaging(IFile file) { 702 String name = file.getName(); 703 704 String ext = file.getFileExtension(); 705 return ApkBuilder.checkFileForPackaging(name, ext); 706 } 707 708 /** 709 * Checks whether an {@link IFolder} and its content is valid for packaging into the .apk as 710 * standard Java resource. 711 * @param folder the {@link IFolder} to check. 712 */ checkFolderForPackaging(IFolder folder)713 static boolean checkFolderForPackaging(IFolder folder) { 714 String name = folder.getName(); 715 return ApkBuilder.checkFolderForPackaging(name); 716 } 717 718 /** 719 * Returns an array of {@link IJavaProject} matching the provided {@link IProject} objects. 720 * @param projects the IProject objects. 721 * @return an array, always. Can be empty. 722 * @throws CoreException 723 */ getJavaProjects(IProject[] projects)724 public static IJavaProject[] getJavaProjects(IProject[] projects) throws CoreException { 725 ArrayList<IJavaProject> list = new ArrayList<IJavaProject>(); 726 727 for (IProject p : projects) { 728 if (p.isOpen() && p.hasNature(JavaCore.NATURE_ID)) { 729 730 list.add(JavaCore.create(p)); 731 } 732 } 733 734 return list.toArray(new IJavaProject[list.size()]); 735 } 736 737 738 } 739