1 /* 2 * Copyright (C) 2007 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.AdtConstants; 20 import com.android.ide.eclipse.adt.AdtPlugin; 21 import com.android.ide.eclipse.adt.AndroidConstants; 22 import com.android.ide.eclipse.adt.internal.project.ApkInstallManager; 23 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 24 import com.android.ide.eclipse.adt.internal.project.ProjectHelper; 25 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; 26 import com.android.ide.eclipse.adt.internal.sdk.DexWrapper; 27 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 28 import com.android.jarutils.DebugKeyProvider; 29 import com.android.jarutils.JavaResourceFilter; 30 import com.android.jarutils.SignedJarBuilder; 31 import com.android.jarutils.DebugKeyProvider.IKeyGenOutput; 32 import com.android.jarutils.DebugKeyProvider.KeytoolException; 33 import com.android.jarutils.SignedJarBuilder.IZipEntryFilter; 34 import com.android.prefs.AndroidLocation.AndroidLocationException; 35 import com.android.sdklib.IAndroidTarget; 36 import com.android.sdklib.SdkConstants; 37 import com.android.sdklib.internal.project.ApkSettings; 38 39 import org.eclipse.core.resources.IContainer; 40 import org.eclipse.core.resources.IFile; 41 import org.eclipse.core.resources.IFolder; 42 import org.eclipse.core.resources.IMarker; 43 import org.eclipse.core.resources.IProject; 44 import org.eclipse.core.resources.IResource; 45 import org.eclipse.core.resources.IResourceDelta; 46 import org.eclipse.core.resources.IResourceDeltaVisitor; 47 import org.eclipse.core.resources.IWorkspace; 48 import org.eclipse.core.resources.IWorkspaceRoot; 49 import org.eclipse.core.resources.ResourcesPlugin; 50 import org.eclipse.core.runtime.CoreException; 51 import org.eclipse.core.runtime.IPath; 52 import org.eclipse.core.runtime.IProgressMonitor; 53 import org.eclipse.core.runtime.IStatus; 54 import org.eclipse.core.runtime.Path; 55 import org.eclipse.core.runtime.Status; 56 import org.eclipse.jdt.core.IJavaProject; 57 import org.eclipse.jdt.core.JavaCore; 58 import org.eclipse.jdt.core.JavaModelException; 59 import org.eclipse.jface.preference.IPreferenceStore; 60 61 import java.io.File; 62 import java.io.FileInputStream; 63 import java.io.FileOutputStream; 64 import java.io.IOException; 65 import java.io.PrintStream; 66 import java.security.GeneralSecurityException; 67 import java.security.PrivateKey; 68 import java.security.cert.X509Certificate; 69 import java.text.DateFormat; 70 import java.util.ArrayList; 71 import java.util.Date; 72 import java.util.Map; 73 import java.util.Set; 74 import java.util.Map.Entry; 75 76 public class ApkBuilder extends BaseBuilder { 77 78 public static final String ID = "com.android.ide.eclipse.adt.ApkBuilder"; //$NON-NLS-1$ 79 80 private static final String PROPERTY_CONVERT_TO_DEX = "convertToDex"; //$NON-NLS-1$ 81 private static final String PROPERTY_PACKAGE_RESOURCES = "packageResources"; //$NON-NLS-1$ 82 private static final String PROPERTY_BUILD_APK = "buildApk"; //$NON-NLS-1$ 83 84 private static final String DX_PREFIX = "Dx"; //$NON-NLS-1$ 85 86 /** 87 * Dex conversion flag. This is set to true if one of the changed/added/removed 88 * file is a .class file. Upon visiting all the delta resource, if this 89 * flag is true, then we know we'll have to make the "classes.dex" file. 90 */ 91 private boolean mConvertToDex = false; 92 93 /** 94 * Package resources flag. This is set to true if one of the changed/added/removed 95 * file is a resource file. Upon visiting all the delta resource, if 96 * this flag is true, then we know we'll have to repackage the resources. 97 */ 98 private boolean mPackageResources = false; 99 100 /** 101 * Final package build flag. 102 */ 103 private boolean mBuildFinalPackage = false; 104 105 private PrintStream mOutStream = null; 106 private PrintStream mErrStream = null; 107 108 /** 109 * Basic Resource Delta Visitor class to check if a referenced project had a change in its 110 * compiled java files. 111 */ 112 private static class ReferencedProjectDeltaVisitor implements IResourceDeltaVisitor { 113 114 private boolean mConvertToDex = false; 115 private boolean mMakeFinalPackage; 116 117 private IPath mOutputFolder; 118 private ArrayList<IPath> mSourceFolders; 119 ReferencedProjectDeltaVisitor(IJavaProject javaProject)120 private ReferencedProjectDeltaVisitor(IJavaProject javaProject) { 121 try { 122 mOutputFolder = javaProject.getOutputLocation(); 123 mSourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject); 124 } catch (JavaModelException e) { 125 } finally { 126 } 127 } 128 129 /** 130 * {@inheritDoc} 131 * @throws CoreException 132 */ visit(IResourceDelta delta)133 public boolean visit(IResourceDelta delta) throws CoreException { 134 // no need to keep looking if we already know we need to convert 135 // to dex and make the final package. 136 if (mConvertToDex && mMakeFinalPackage) { 137 return false; 138 } 139 140 // get the resource and the path segments. 141 IResource resource = delta.getResource(); 142 IPath resourceFullPath = resource.getFullPath(); 143 144 if (mOutputFolder.isPrefixOf(resourceFullPath)) { 145 int type = resource.getType(); 146 if (type == IResource.FILE) { 147 String ext = resource.getFileExtension(); 148 if (AndroidConstants.EXT_CLASS.equals(ext)) { 149 mConvertToDex = true; 150 } 151 } 152 return true; 153 } else { 154 for (IPath sourceFullPath : mSourceFolders) { 155 if (sourceFullPath.isPrefixOf(resourceFullPath)) { 156 int type = resource.getType(); 157 if (type == IResource.FILE) { 158 // check if the file is a valid file that would be 159 // included during the final packaging. 160 if (checkFileForPackaging((IFile)resource)) { 161 mMakeFinalPackage = true; 162 } 163 164 return false; 165 } else if (type == IResource.FOLDER) { 166 // if this is a folder, we check if this is a valid folder as well. 167 // If this is a folder that needs to be ignored, we must return false, 168 // so that we ignore its content. 169 return checkFolderForPackaging((IFolder)resource); 170 } 171 } 172 } 173 } 174 175 return true; 176 } 177 178 /** 179 * Returns if one of the .class file was modified. 180 */ needDexConvertion()181 boolean needDexConvertion() { 182 return mConvertToDex; 183 } 184 needMakeFinalPackage()185 boolean needMakeFinalPackage() { 186 return mMakeFinalPackage; 187 } 188 } 189 190 /** 191 * {@link IZipEntryFilter} to filter out everything that is not a standard java resources. 192 * <p/>Used in {@link SignedJarBuilder#writeZip(java.io.InputStream, IZipEntryFilter)} when 193 * we only want the java resources from external jars. 194 */ 195 private final IZipEntryFilter mJavaResourcesFilter = new JavaResourceFilter(); 196 ApkBuilder()197 public ApkBuilder() { 198 super(); 199 } 200 201 // build() returns a list of project from which this project depends for future compilation. 202 @SuppressWarnings("unchecked") 203 @Override build(int kind, Map args, IProgressMonitor monitor)204 protected IProject[] build(int kind, Map args, IProgressMonitor monitor) 205 throws CoreException { 206 // get a project object 207 IProject project = getProject(); 208 209 // list of referenced projects. 210 IProject[] referencedProjects = null; 211 212 try { 213 // Top level check to make sure the build can move forward. 214 abortOnBadSetup(project); 215 216 // get the list of referenced projects. 217 referencedProjects = ProjectHelper.getReferencedProjects(project); 218 IJavaProject[] referencedJavaProjects = getJavaProjects(referencedProjects); 219 220 // get the output folder, this method returns the path with a trailing 221 // separator 222 IJavaProject javaProject = JavaCore.create(project); 223 IFolder outputFolder = BaseProjectHelper.getOutputFolder(project); 224 225 // now we need to get the classpath list 226 ArrayList<IPath> sourceList = BaseProjectHelper.getSourceClasspaths(javaProject); 227 228 // First thing we do is go through the resource delta to not 229 // lose it if we have to abort the build for any reason. 230 ApkDeltaVisitor dv = null; 231 if (kind == FULL_BUILD) { 232 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, 233 Messages.Start_Full_Apk_Build); 234 235 mPackageResources = true; 236 mConvertToDex = true; 237 mBuildFinalPackage = true; 238 } else { 239 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, 240 Messages.Start_Inc_Apk_Build); 241 242 // go through the resources and see if something changed. 243 IResourceDelta delta = getDelta(project); 244 if (delta == null) { 245 mPackageResources = true; 246 mConvertToDex = true; 247 mBuildFinalPackage = true; 248 } else { 249 dv = new ApkDeltaVisitor(this, sourceList, outputFolder); 250 delta.accept(dv); 251 252 // save the state 253 mPackageResources |= dv.getPackageResources(); 254 mConvertToDex |= dv.getConvertToDex(); 255 mBuildFinalPackage |= dv.getMakeFinalPackage(); 256 } 257 258 // also go through the delta for all the referenced projects, until we are forced to 259 // compile anyway 260 for (int i = 0 ; i < referencedJavaProjects.length && 261 (mBuildFinalPackage == false || mConvertToDex == false); i++) { 262 IJavaProject referencedJavaProject = referencedJavaProjects[i]; 263 delta = getDelta(referencedJavaProject.getProject()); 264 if (delta != null) { 265 ReferencedProjectDeltaVisitor refProjectDv = new ReferencedProjectDeltaVisitor( 266 referencedJavaProject); 267 delta.accept(refProjectDv); 268 269 // save the state 270 mConvertToDex |= refProjectDv.needDexConvertion(); 271 mBuildFinalPackage |= refProjectDv.needMakeFinalPackage(); 272 } 273 } 274 } 275 276 // store the build status in the persistent storage 277 saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , mConvertToDex); 278 saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources); 279 saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage); 280 281 if (dv != null && dv.mXmlError) { 282 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, 283 Messages.Xml_Error); 284 285 // if there was some XML errors, we just return w/o doing 286 // anything since we've put some markers in the files anyway 287 return referencedProjects; 288 } 289 290 if (outputFolder == null) { 291 // mark project and exit 292 markProject(AdtConstants.MARKER_ADT, Messages.Failed_To_Get_Output, 293 IMarker.SEVERITY_ERROR); 294 return referencedProjects; 295 } 296 297 // first thing we do is check that the SDK directory has been setup. 298 String osSdkFolder = AdtPlugin.getOsSdkFolder(); 299 300 if (osSdkFolder.length() == 0) { 301 // this has already been checked in the precompiler. Therefore, 302 // while we do have to cancel the build, we don't have to return 303 // any error or throw anything. 304 return referencedProjects; 305 } 306 307 // get the APK configs for the project. 308 ApkSettings apkSettings = Sdk.getCurrent().getApkSettings(project); 309 Set<Entry<String, String>> apkfilters = null; 310 if (apkSettings != null) { 311 Map<String, String> filterMap = apkSettings.getResourceFilters(); 312 if (filterMap != null && filterMap.size() > 0) { 313 apkfilters = filterMap.entrySet(); 314 } 315 } 316 317 // do some extra check, in case the output files are not present. This 318 // will force to recreate them. 319 IResource tmp = null; 320 321 if (mPackageResources == false) { 322 // check the full resource package 323 tmp = outputFolder.findMember(AndroidConstants.FN_RESOURCES_AP_); 324 if (tmp == null || tmp.exists() == false) { 325 mPackageResources = true; 326 mBuildFinalPackage = true; 327 } else { 328 // if the full package is present, we check the filtered resource packages 329 // as well 330 if (apkfilters != null) { 331 for (Entry<String, String> entry : apkfilters) { 332 String filename = String.format(AndroidConstants.FN_RESOURCES_S_AP_, 333 entry.getKey()); 334 335 tmp = outputFolder.findMember(filename); 336 if (tmp == null || (tmp instanceof IFile && 337 tmp.exists() == false)) { 338 String msg = String.format(Messages.s_Missing_Repackaging, 339 filename); 340 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, 341 project, msg); 342 mPackageResources = true; 343 mBuildFinalPackage = true; 344 break; 345 } 346 } 347 } 348 } 349 } 350 351 // check classes.dex is present. If not we force to recreate it. 352 if (mConvertToDex == false) { 353 tmp = outputFolder.findMember(AndroidConstants.FN_CLASSES_DEX); 354 if (tmp == null || tmp.exists() == false) { 355 mConvertToDex = true; 356 mBuildFinalPackage = true; 357 } 358 } 359 360 // also check the final file(s)! 361 String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/); 362 if (mBuildFinalPackage == false) { 363 tmp = outputFolder.findMember(finalPackageName); 364 if (tmp == null || (tmp instanceof IFile && 365 tmp.exists() == false)) { 366 String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName); 367 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg); 368 mBuildFinalPackage = true; 369 } else if (apkfilters != null) { 370 // if the full apk is present, we check the filtered apk as well 371 for (Entry<String, String> entry : apkfilters) { 372 String filename = ProjectHelper.getApkFilename(project, entry.getKey()); 373 374 tmp = outputFolder.findMember(filename); 375 if (tmp == null || (tmp instanceof IFile && 376 tmp.exists() == false)) { 377 String msg = String.format(Messages.s_Missing_Repackaging, filename); 378 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg); 379 mBuildFinalPackage = true; 380 break; 381 } 382 } 383 } 384 } 385 386 // at this point we know if we need to recreate the temporary apk 387 // or the dex file, but we don't know if we simply need to recreate them 388 // because they are missing 389 390 // refresh the output directory first 391 IContainer ic = outputFolder.getParent(); 392 if (ic != null) { 393 ic.refreshLocal(IResource.DEPTH_ONE, monitor); 394 } 395 396 // we need to test all three, as we may need to make the final package 397 // but not the intermediary ones. 398 if (mPackageResources || mConvertToDex || mBuildFinalPackage) { 399 IPath binLocation = outputFolder.getLocation(); 400 if (binLocation == null) { 401 markProject(AdtConstants.MARKER_ADT, Messages.Output_Missing, 402 IMarker.SEVERITY_ERROR); 403 return referencedProjects; 404 } 405 String osBinPath = binLocation.toOSString(); 406 407 // Remove the old .apk. 408 // This make sure that if the apk is corrupted, then dx (which would attempt 409 // to open it), will not fail. 410 String osFinalPackagePath = osBinPath + File.separator + finalPackageName; 411 File finalPackage = new File(osFinalPackagePath); 412 413 // if delete failed, this is not really a problem, as the final package generation 414 // handle already present .apk, and if that one failed as well, the user will be 415 // notified. 416 finalPackage.delete(); 417 418 if (apkfilters != null) { 419 for (Entry<String, String> entry : apkfilters) { 420 String packageFilepath = osBinPath + File.separator + 421 ProjectHelper.getApkFilename(project, entry.getKey()); 422 423 finalPackage = new File(packageFilepath); 424 finalPackage.delete(); 425 } 426 } 427 428 // first we check if we need to package the resources. 429 if (mPackageResources) { 430 // remove some aapt_package only markers. 431 removeMarkersFromContainer(project, AndroidConstants.MARKER_AAPT_PACKAGE); 432 433 // need to figure out some path before we can execute aapt; 434 435 // resource to the AndroidManifest.xml file 436 IResource manifestResource = project .findMember( 437 AndroidConstants.WS_SEP + AndroidConstants.FN_ANDROID_MANIFEST); 438 439 if (manifestResource == null 440 || manifestResource.exists() == false) { 441 // mark project and exit 442 String msg = String.format(Messages.s_File_Missing, 443 AndroidConstants.FN_ANDROID_MANIFEST); 444 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 445 return referencedProjects; 446 } 447 448 // get the resource folder 449 IFolder resFolder = project.getFolder( 450 AndroidConstants.WS_RESOURCES); 451 452 // and the assets folder 453 IFolder assetsFolder = project.getFolder( 454 AndroidConstants.WS_ASSETS); 455 456 // we need to make sure this one exists. 457 if (assetsFolder.exists() == false) { 458 assetsFolder = null; 459 } 460 461 IPath resLocation = resFolder.getLocation(); 462 IPath manifestLocation = manifestResource.getLocation(); 463 464 if (resLocation != null && manifestLocation != null) { 465 String osResPath = resLocation.toOSString(); 466 String osManifestPath = manifestLocation.toOSString(); 467 468 String osAssetsPath = null; 469 if (assetsFolder != null) { 470 osAssetsPath = assetsFolder.getLocation().toOSString(); 471 } 472 473 // build the default resource package 474 if (executeAapt(project, osManifestPath, osResPath, 475 osAssetsPath, osBinPath + File.separator + 476 AndroidConstants.FN_RESOURCES_AP_, null /*configFilter*/) == false) { 477 // aapt failed. Whatever files that needed to be marked 478 // have already been marked. We just return. 479 return referencedProjects; 480 } 481 482 // now do the same thing for all the configured resource packages. 483 if (apkfilters != null) { 484 for (Entry<String, String> entry : apkfilters) { 485 String outPathFormat = osBinPath + File.separator + 486 AndroidConstants.FN_RESOURCES_S_AP_; 487 String outPath = String.format(outPathFormat, entry.getKey()); 488 if (executeAapt(project, osManifestPath, osResPath, 489 osAssetsPath, outPath, entry.getValue()) == false) { 490 // aapt failed. Whatever files that needed to be marked 491 // have already been marked. We just return. 492 return referencedProjects; 493 } 494 } 495 } 496 497 // build has been done. reset the state of the builder 498 mPackageResources = false; 499 500 // and store it 501 saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources); 502 } 503 } 504 505 // then we check if we need to package the .class into classes.dex 506 if (mConvertToDex) { 507 if (executeDx(javaProject, osBinPath, osBinPath + File.separator + 508 AndroidConstants.FN_CLASSES_DEX, referencedJavaProjects) == false) { 509 // dx failed, we return 510 return referencedProjects; 511 } 512 513 // build has been done. reset the state of the builder 514 mConvertToDex = false; 515 516 // and store it 517 saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex); 518 } 519 520 // now we need to make the final package from the intermediary apk 521 // and classes.dex. 522 // This is the default package with all the resources. 523 524 String classesDexPath = osBinPath + File.separator + AndroidConstants.FN_CLASSES_DEX; 525 if (finalPackage(osBinPath + File.separator + AndroidConstants.FN_RESOURCES_AP_, 526 classesDexPath,osFinalPackagePath, javaProject, 527 referencedJavaProjects) == false) { 528 return referencedProjects; 529 } 530 531 // now do the same thing for all the configured resource packages. 532 if (apkfilters != null) { 533 String resPathFormat = osBinPath + File.separator + 534 AndroidConstants.FN_RESOURCES_S_AP_; 535 536 for (Entry<String, String> entry : apkfilters) { 537 // make the filename for the resource package. 538 String resPath = String.format(resPathFormat, entry.getKey()); 539 540 // make the filename for the apk to generate 541 String apkOsFilePath = osBinPath + File.separator + 542 ProjectHelper.getApkFilename(project, entry.getKey()); 543 if (finalPackage(resPath, classesDexPath, apkOsFilePath, javaProject, 544 referencedJavaProjects) == false) { 545 return referencedProjects; 546 } 547 } 548 } 549 550 // we are done. 551 552 // get the resource to bin 553 outputFolder.refreshLocal(IResource.DEPTH_ONE, monitor); 554 555 // build has been done. reset the state of the builder 556 mBuildFinalPackage = false; 557 558 // and store it 559 saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage); 560 561 // reset the installation manager to force new installs of this project 562 ApkInstallManager.getInstance().resetInstallationFor(project); 563 564 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(), 565 "Build Success!"); 566 } 567 } catch (Exception exception) { 568 // try to catch other exception to actually display an error. This will be useful 569 // if we get an NPE or something so that we can at least notify the user that something 570 // went wrong. 571 572 // first check if this is a CoreException we threw to cancel the build. 573 if (exception instanceof CoreException) { 574 if (((CoreException)exception).getStatus().getSeverity() == IStatus.CANCEL) { 575 // Project is already marked with an error. Nothing to do 576 return referencedProjects; 577 } 578 } 579 580 String msg = exception.getMessage(); 581 if (msg == null) { 582 msg = exception.getClass().getCanonicalName(); 583 } 584 585 msg = String.format("Unknown error: %1$s", msg); 586 AdtPlugin.printErrorToConsole(project, msg); 587 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 588 } 589 590 return referencedProjects; 591 } 592 593 @Override startupOnInitialize()594 protected void startupOnInitialize() { 595 super.startupOnInitialize(); 596 597 // load the build status. We pass true as the default value to 598 // force a recompile in case the property was not found 599 mConvertToDex = loadProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , true); 600 mPackageResources = loadProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, true); 601 mBuildFinalPackage = loadProjectBooleanProperty(PROPERTY_BUILD_APK, true); 602 } 603 604 /** 605 * Executes aapt. If any error happen, files or the project will be marked. 606 * @param project The Project 607 * @param osManifestPath The path to the manifest file 608 * @param osResPath The path to the res folder 609 * @param osAssetsPath The path to the assets folder. This can be null. 610 * @param osOutFilePath The path to the temporary resource file to create. 611 * @param configFilter The configuration filter for the resources to include 612 * (used with -c option, for example "port,en,fr" to include portrait, English and French 613 * resources.) 614 * @return true if success, false otherwise. 615 */ executeAapt(IProject project, String osManifestPath, String osResPath, String osAssetsPath, String osOutFilePath, String configFilter)616 private boolean executeAapt(IProject project, String osManifestPath, 617 String osResPath, String osAssetsPath, String osOutFilePath, String configFilter) { 618 IAndroidTarget target = Sdk.getCurrent().getTarget(project); 619 620 // Create the command line. 621 ArrayList<String> commandArray = new ArrayList<String>(); 622 commandArray.add(target.getPath(IAndroidTarget.AAPT)); 623 commandArray.add("package"); //$NON-NLS-1$ 624 commandArray.add("-f");//$NON-NLS-1$ 625 if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) { 626 commandArray.add("-v"); //$NON-NLS-1$ 627 } 628 if (configFilter != null) { 629 commandArray.add("-c"); //$NON-NLS-1$ 630 commandArray.add(configFilter); 631 } 632 commandArray.add("-M"); //$NON-NLS-1$ 633 commandArray.add(osManifestPath); 634 commandArray.add("-S"); //$NON-NLS-1$ 635 commandArray.add(osResPath); 636 if (osAssetsPath != null) { 637 commandArray.add("-A"); //$NON-NLS-1$ 638 commandArray.add(osAssetsPath); 639 } 640 commandArray.add("-I"); //$NON-NLS-1$ 641 commandArray.add(target.getPath(IAndroidTarget.ANDROID_JAR)); 642 commandArray.add("-F"); //$NON-NLS-1$ 643 commandArray.add(osOutFilePath); 644 645 String command[] = commandArray.toArray( 646 new String[commandArray.size()]); 647 648 if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) { 649 StringBuilder sb = new StringBuilder(); 650 for (String c : command) { 651 sb.append(c); 652 sb.append(' '); 653 } 654 AdtPlugin.printToConsole(project, sb.toString()); 655 } 656 657 // launch 658 int execError = 1; 659 try { 660 // launch the command line process 661 Process process = Runtime.getRuntime().exec(command); 662 663 // list to store each line of stderr 664 ArrayList<String> results = new ArrayList<String>(); 665 666 // get the output and return code from the process 667 execError = grabProcessOutput(process, results); 668 669 // attempt to parse the error output 670 boolean parsingError = parseAaptOutput(results, project); 671 672 // if we couldn't parse the output we display it in the console. 673 if (parsingError) { 674 if (execError != 0) { 675 AdtPlugin.printErrorToConsole(project, results.toArray()); 676 } else { 677 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_ALWAYS, project, 678 results.toArray()); 679 } 680 } 681 682 // We need to abort if the exec failed. 683 if (execError != 0) { 684 // if the exec failed, and we couldn't parse the error output (and therefore 685 // not all files that should have been marked, were marked), we put a generic 686 // marker on the project and abort. 687 if (parsingError) { 688 markProject(AdtConstants.MARKER_ADT, Messages.Unparsed_AAPT_Errors, 689 IMarker.SEVERITY_ERROR); 690 } 691 692 // abort if exec failed. 693 return false; 694 } 695 } catch (IOException e1) { 696 String msg = String.format(Messages.AAPT_Exec_Error, command[0]); 697 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 698 return false; 699 } catch (InterruptedException e) { 700 String msg = String.format(Messages.AAPT_Exec_Error, command[0]); 701 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 702 return false; 703 } 704 705 return true; 706 } 707 708 /** 709 * Execute the Dx tool for dalvik code conversion. 710 * @param javaProject The java project 711 * @param osBinPath the path to the output folder of the project 712 * @param osOutFilePath the path of the dex file to create. 713 * @param referencedJavaProjects the list of referenced projects for this project. 714 * 715 * @throws CoreException 716 */ executeDx(IJavaProject javaProject, String osBinPath, String osOutFilePath, IJavaProject[] referencedJavaProjects)717 private boolean executeDx(IJavaProject javaProject, String osBinPath, String osOutFilePath, 718 IJavaProject[] referencedJavaProjects) throws CoreException { 719 IAndroidTarget target = Sdk.getCurrent().getTarget(javaProject.getProject()); 720 AndroidTargetData targetData = Sdk.getCurrent().getTargetData(target); 721 if (targetData == null) { 722 throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, 723 Messages.ApkBuilder_UnableBuild_Dex_Not_loaded)); 724 } 725 726 // get the dex wrapper 727 DexWrapper wrapper = targetData.getDexWrapper(); 728 729 if (wrapper == null) { 730 throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, 731 Messages.ApkBuilder_UnableBuild_Dex_Not_loaded)); 732 } 733 734 // make sure dx use the proper output streams. 735 // first make sure we actually have the streams available. 736 if (mOutStream == null) { 737 IProject project = getProject(); 738 mOutStream = AdtPlugin.getOutPrintStream(project, DX_PREFIX); 739 mErrStream = AdtPlugin.getErrPrintStream(project, DX_PREFIX); 740 } 741 742 try { 743 // get the list of libraries to include with the source code 744 String[] libraries = getExternalJars(); 745 746 // get the list of referenced projects output to add 747 String[] projectOutputs = getProjectOutputs(referencedJavaProjects); 748 749 String[] fileNames = new String[1 + projectOutputs.length + libraries.length]; 750 751 // first this project output 752 fileNames[0] = osBinPath; 753 754 // then other project output 755 System.arraycopy(projectOutputs, 0, fileNames, 1, projectOutputs.length); 756 757 // then external jars. 758 System.arraycopy(libraries, 0, fileNames, 1 + projectOutputs.length, libraries.length); 759 760 int res = wrapper.run(osOutFilePath, fileNames, 761 AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE, 762 mOutStream, mErrStream); 763 764 if (res != 0) { 765 // output error message and marker the project. 766 String message = String.format(Messages.Dalvik_Error_d, 767 res); 768 AdtPlugin.printErrorToConsole(getProject(), message); 769 markProject(AdtConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR); 770 return false; 771 } 772 } catch (Throwable ex) { 773 String message = ex.getMessage(); 774 if (message == null) { 775 message = ex.getClass().getCanonicalName(); 776 } 777 message = String.format(Messages.Dalvik_Error_s, message); 778 AdtPlugin.printErrorToConsole(getProject(), message); 779 markProject(AdtConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR); 780 if ((ex instanceof NoClassDefFoundError) 781 || (ex instanceof NoSuchMethodError)) { 782 AdtPlugin.printErrorToConsole(getProject(), Messages.Incompatible_VM_Warning, 783 Messages.Requires_1_5_Error); 784 } 785 return false; 786 } 787 788 return true; 789 } 790 791 /** 792 * Makes the final package. Package the dex files, the temporary resource file into the final 793 * package file. 794 * @param intermediateApk The path to the temporary resource file. 795 * @param dex The path to the dex file. 796 * @param output The path to the final package file to create. 797 * @param javaProject 798 * @param referencedJavaProjects 799 * @return true if success, false otherwise. 800 */ finalPackage(String intermediateApk, String dex, String output, final IJavaProject javaProject, IJavaProject[] referencedJavaProjects)801 private boolean finalPackage(String intermediateApk, String dex, String output, 802 final IJavaProject javaProject, IJavaProject[] referencedJavaProjects) { 803 FileOutputStream fos = null; 804 try { 805 IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); 806 String osKeyPath = store.getString(AdtPlugin.PREFS_CUSTOM_DEBUG_KEYSTORE); 807 if (osKeyPath == null || new File(osKeyPath).exists() == false) { 808 osKeyPath = DebugKeyProvider.getDefaultKeyStoreOsPath(); 809 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(), 810 Messages.ApkBuilder_Using_Default_Key); 811 } else { 812 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(), 813 String.format(Messages.ApkBuilder_Using_s_To_Sign, osKeyPath)); 814 } 815 816 // TODO: get the store type from somewhere else. 817 DebugKeyProvider provider = new DebugKeyProvider(osKeyPath, null /* storeType */, 818 new IKeyGenOutput() { 819 public void err(String message) { 820 AdtPlugin.printErrorToConsole(javaProject.getProject(), 821 Messages.ApkBuilder_Signing_Key_Creation_s + message); 822 } 823 824 public void out(String message) { 825 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, 826 javaProject.getProject(), 827 Messages.ApkBuilder_Signing_Key_Creation_s + message); 828 } 829 }); 830 PrivateKey key = provider.getDebugKey(); 831 X509Certificate certificate = (X509Certificate)provider.getCertificate(); 832 833 if (key == null) { 834 String msg = String.format(Messages.Final_Archive_Error_s, 835 Messages.ApkBuilder_Unable_To_Gey_Key); 836 AdtPlugin.printErrorToConsole(javaProject.getProject(), msg); 837 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 838 return false; 839 } 840 841 // compare the certificate expiration date 842 if (certificate != null && certificate.getNotAfter().compareTo(new Date()) < 0) { 843 // TODO, regenerate a new one. 844 String msg = String.format(Messages.Final_Archive_Error_s, 845 String.format(Messages.ApkBuilder_Certificate_Expired_on_s, 846 DateFormat.getInstance().format(certificate.getNotAfter()))); 847 AdtPlugin.printErrorToConsole(javaProject.getProject(), msg); 848 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 849 return false; 850 } 851 852 // create the jar builder. 853 fos = new FileOutputStream(output); 854 SignedJarBuilder builder = new SignedJarBuilder(fos, key, certificate); 855 856 // add the intermediate file containing the compiled resources. 857 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(), 858 String.format(Messages.ApkBuilder_Packaging_s, intermediateApk)); 859 FileInputStream fis = new FileInputStream(intermediateApk); 860 try { 861 builder.writeZip(fis, null /* filter */); 862 } finally { 863 fis.close(); 864 } 865 866 // Now we add the new file to the zip archive for the classes.dex file. 867 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(), 868 String.format(Messages.ApkBuilder_Packaging_s, AndroidConstants.FN_CLASSES_DEX)); 869 File entryFile = new File(dex); 870 builder.writeFile(entryFile, AndroidConstants.FN_CLASSES_DEX); 871 872 // Now we write the standard resources from the project and the referenced projects. 873 writeStandardResources(builder, javaProject, referencedJavaProjects); 874 875 // Now we write the standard resources from the external libraries 876 for (String libraryOsPath : getExternalJars()) { 877 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(), 878 String.format(Messages.ApkBuilder_Packaging_s, libraryOsPath)); 879 try { 880 fis = new FileInputStream(libraryOsPath); 881 builder.writeZip(fis, mJavaResourcesFilter); 882 } finally { 883 fis.close(); 884 } 885 } 886 887 // now write the native libraries. 888 // First look if the lib folder is there. 889 IResource libFolder = javaProject.getProject().findMember(SdkConstants.FD_NATIVE_LIBS); 890 if (libFolder != null && libFolder.exists() && 891 libFolder.getType() == IResource.FOLDER) { 892 // look inside and put .so in lib/* by keeping the relative folder path. 893 writeNativeLibraries(libFolder.getFullPath().segmentCount(), builder, libFolder); 894 } 895 896 // close the jar file and write the manifest and sign it. 897 builder.close(); 898 } catch (GeneralSecurityException e1) { 899 // mark project and return 900 String msg = String.format(Messages.Final_Archive_Error_s, e1.getMessage()); 901 AdtPlugin.printErrorToConsole(javaProject.getProject(), msg); 902 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 903 return false; 904 } catch (IOException e1) { 905 // mark project and return 906 String msg = String.format(Messages.Final_Archive_Error_s, e1.getMessage()); 907 AdtPlugin.printErrorToConsole(javaProject.getProject(), msg); 908 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 909 return false; 910 } catch (KeytoolException e) { 911 String eMessage = e.getMessage(); 912 913 // mark the project with the standard message 914 String msg = String.format(Messages.Final_Archive_Error_s, eMessage); 915 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 916 917 // output more info in the console 918 AdtPlugin.printErrorToConsole(javaProject.getProject(), 919 msg, 920 String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()), 921 Messages.ApkBuilder_Update_or_Execute_manually_s, 922 e.getCommandLine()); 923 } catch (AndroidLocationException e) { 924 String eMessage = e.getMessage(); 925 926 // mark the project with the standard message 927 String msg = String.format(Messages.Final_Archive_Error_s, eMessage); 928 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 929 930 // and also output it in the console 931 AdtPlugin.printErrorToConsole(javaProject.getProject(), msg); 932 } catch (CoreException e) { 933 // mark project and return 934 String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage()); 935 AdtPlugin.printErrorToConsole(javaProject.getProject(), msg); 936 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 937 return false; 938 } catch (Exception e) { 939 // try to catch other exception to actually display an error. This will be useful 940 // if we get an NPE or something so that we can at least notify the user that something 941 // went wrong (otherwise the build appears to succeed but the zip archive is not closed 942 // and therefore invalid. 943 String msg = e.getMessage(); 944 if (msg == null) { 945 msg = e.getClass().getCanonicalName(); 946 } 947 948 msg = String.format("Unknown error: %1$s", msg); 949 AdtPlugin.printErrorToConsole(javaProject.getProject(), msg); 950 markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); 951 return false; 952 } finally { 953 if (fos != null) { 954 try { 955 fos.close(); 956 } catch (IOException e) { 957 // pass. 958 } 959 } 960 } 961 962 return true; 963 } 964 965 /** 966 * Writes native libraries into a {@link SignedJarBuilder}. 967 * <p/>This recursively go through folder and writes .so files. 968 * The path in the archive is based on the root folder containing the libraries in the project. 969 * Its segment count is passed to the method to compute the resources path relative to the root 970 * folder. 971 * Native libraries in the archive must be in a "lib" folder. Everything in the project native 972 * lib folder directly goes in this "lib" folder in the archive. 973 * 974 * 975 * @param rootSegmentCount The number of segment of the path of the folder containing the 976 * libraries. This is used to compute the path in the archive. 977 * @param jarBuilder the {@link SignedJarBuilder} used to create the archive. 978 * @param resource the IResource to write. 979 * @throws CoreException 980 * @throws IOException 981 */ writeNativeLibraries(int rootSegmentCount, SignedJarBuilder jarBuilder, IResource resource)982 private void writeNativeLibraries(int rootSegmentCount, SignedJarBuilder jarBuilder, 983 IResource resource) throws CoreException, IOException { 984 if (resource.getType() == IResource.FILE) { 985 IPath path = resource.getFullPath(); 986 987 // check the extension. 988 String ext = path.getFileExtension(); 989 if (ext != null && ext.equalsIgnoreCase(AndroidConstants.EXT_NATIVE_LIB)) { 990 // remove the first segment to build the path inside the archive. 991 path = path.removeFirstSegments(rootSegmentCount); 992 993 // add it to the archive. 994 IPath apkPath = new Path(SdkConstants.FD_APK_NATIVE_LIBS); 995 apkPath = apkPath.append(path); 996 997 // writes the file in the apk. 998 jarBuilder.writeFile(resource.getLocation().toFile(), apkPath.toString()); 999 } 1000 } else if (resource.getType() == IResource.FOLDER && 1001 checkFolderForPackaging((IFolder)resource)) { 1002 IResource[] members = ((IFolder)resource).members(); 1003 for (IResource member : members) { 1004 writeNativeLibraries(rootSegmentCount, jarBuilder, member); 1005 } 1006 } 1007 } 1008 1009 /** 1010 * Writes the standard resources of a project and its referenced projects 1011 * into a {@link SignedJarBuilder}. 1012 * Standard resources are non java/aidl files placed in the java package folders. 1013 * @param jarBuilder the {@link SignedJarBuilder}. 1014 * @param javaProject the javaProject object. 1015 * @param referencedJavaProjects the java projects that this project references. 1016 * @throws IOException 1017 * @throws CoreException 1018 */ writeStandardResources(SignedJarBuilder jarBuilder, IJavaProject javaProject, IJavaProject[] referencedJavaProjects)1019 private void writeStandardResources(SignedJarBuilder jarBuilder, IJavaProject javaProject, 1020 IJavaProject[] referencedJavaProjects) throws IOException, CoreException { 1021 IWorkspace ws = ResourcesPlugin.getWorkspace(); 1022 IWorkspaceRoot wsRoot = ws.getRoot(); 1023 1024 // create a list of path already put into the archive, in order to detect conflict 1025 ArrayList<String> list = new ArrayList<String>(); 1026 1027 writeStandardProjectResources(jarBuilder, javaProject, wsRoot, list); 1028 1029 for (IJavaProject referencedJavaProject : referencedJavaProjects) { 1030 // only include output from non android referenced project 1031 // (This is to handle the case of reference Android projects in the context of 1032 // instrumentation projects that need to reference the projects to be tested). 1033 if (referencedJavaProject.getProject().hasNature(AndroidConstants.NATURE) == false) { 1034 writeStandardProjectResources(jarBuilder, referencedJavaProject, wsRoot, list); 1035 } 1036 } 1037 } 1038 1039 /** 1040 * Writes the standard resources of a {@link IJavaProject} into a {@link SignedJarBuilder}. 1041 * Standard resources are non java/aidl files placed in the java package folders. 1042 * @param jarBuilder the {@link SignedJarBuilder}. 1043 * @param javaProject the javaProject object. 1044 * @param wsRoot the {@link IWorkspaceRoot}. 1045 * @param list a list of files already added to the archive, to detect conflicts. 1046 * @throws IOException 1047 */ writeStandardProjectResources(SignedJarBuilder jarBuilder, IJavaProject javaProject, IWorkspaceRoot wsRoot, ArrayList<String> list)1048 private void writeStandardProjectResources(SignedJarBuilder jarBuilder, 1049 IJavaProject javaProject, IWorkspaceRoot wsRoot, ArrayList<String> list) 1050 throws IOException { 1051 // get the source pathes 1052 ArrayList<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject); 1053 1054 // loop on them and then recursively go through the content looking for matching files. 1055 for (IPath sourcePath : sourceFolders) { 1056 IResource sourceResource = wsRoot.findMember(sourcePath); 1057 if (sourceResource != null && sourceResource.getType() == IResource.FOLDER) { 1058 writeStandardSourceFolderResources(jarBuilder, sourcePath, (IFolder)sourceResource, 1059 list); 1060 } 1061 } 1062 } 1063 1064 /** 1065 * Recursively writes the standard resources of a source folder into a {@link SignedJarBuilder}. 1066 * Standard resources are non java/aidl files placed in the java package folders. 1067 * @param jarBuilder the {@link SignedJarBuilder}. 1068 * @param sourceFolder the {@link IPath} of the source folder. 1069 * @param currentFolder The current folder we're recursively processing. 1070 * @param list a list of files already added to the archive, to detect conflicts. 1071 * @throws IOException 1072 */ writeStandardSourceFolderResources(SignedJarBuilder jarBuilder, IPath sourceFolder, IFolder currentFolder, ArrayList<String> list)1073 private void writeStandardSourceFolderResources(SignedJarBuilder jarBuilder, IPath sourceFolder, 1074 IFolder currentFolder, ArrayList<String> list) throws IOException { 1075 try { 1076 IResource[] members = currentFolder.members(); 1077 1078 for (IResource member : members) { 1079 int type = member.getType(); 1080 if (type == IResource.FILE && member.exists()) { 1081 if (checkFileForPackaging((IFile)member)) { 1082 // this files must be added to the archive. 1083 IPath fullPath = member.getFullPath(); 1084 1085 // We need to create its path inside the archive. 1086 // This path is relative to the source folder. 1087 IPath relativePath = fullPath.removeFirstSegments( 1088 sourceFolder.segmentCount()); 1089 String zipPath = relativePath.toString(); 1090 1091 // lets check it's not already in the list of path added to the archive 1092 if (list.indexOf(zipPath) != -1) { 1093 AdtPlugin.printErrorToConsole(getProject(), 1094 String.format( 1095 Messages.ApkBuilder_s_Conflict_with_file_s, 1096 fullPath, zipPath)); 1097 } else { 1098 // get the File object 1099 File entryFile = member.getLocation().toFile(); 1100 1101 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(), 1102 String.format(Messages.ApkBuilder_Packaging_s_into_s, fullPath, zipPath)); 1103 1104 // write it in the zip archive 1105 jarBuilder.writeFile(entryFile, zipPath); 1106 1107 // and add it to the list of entries 1108 list.add(zipPath); 1109 } 1110 } 1111 } else if (type == IResource.FOLDER) { 1112 if (checkFolderForPackaging((IFolder)member)) { 1113 writeStandardSourceFolderResources(jarBuilder, sourceFolder, 1114 (IFolder)member, list); 1115 } 1116 } 1117 } 1118 } catch (CoreException e) { 1119 // if we can't get the members of the folder, we just don't do anything. 1120 } 1121 } 1122 1123 /** 1124 * Returns the list of the output folders for the specified {@link IJavaProject} objects, if 1125 * they are Android projects. 1126 * 1127 * @param referencedJavaProjects the java projects. 1128 * @return an array, always. Can be empty. 1129 * @throws CoreException 1130 */ getProjectOutputs(IJavaProject[] referencedJavaProjects)1131 private String[] getProjectOutputs(IJavaProject[] referencedJavaProjects) throws CoreException { 1132 ArrayList<String> list = new ArrayList<String>(); 1133 1134 IWorkspace ws = ResourcesPlugin.getWorkspace(); 1135 IWorkspaceRoot wsRoot = ws.getRoot(); 1136 1137 for (IJavaProject javaProject : referencedJavaProjects) { 1138 // only include output from non android referenced project 1139 // (This is to handle the case of reference Android projects in the context of 1140 // instrumentation projects that need to reference the projects to be tested). 1141 if (javaProject.getProject().hasNature(AndroidConstants.NATURE) == false) { 1142 // get the output folder 1143 IPath path = null; 1144 try { 1145 path = javaProject.getOutputLocation(); 1146 } catch (JavaModelException e) { 1147 continue; 1148 } 1149 1150 IResource outputResource = wsRoot.findMember(path); 1151 if (outputResource != null && outputResource.getType() == IResource.FOLDER) { 1152 String outputOsPath = outputResource.getLocation().toOSString(); 1153 1154 list.add(outputOsPath); 1155 } 1156 } 1157 } 1158 1159 return list.toArray(new String[list.size()]); 1160 } 1161 1162 /** 1163 * Returns an array of {@link IJavaProject} matching the provided {@link IProject} objects. 1164 * @param projects the IProject objects. 1165 * @return an array, always. Can be empty. 1166 * @throws CoreException 1167 */ getJavaProjects(IProject[] projects)1168 private IJavaProject[] getJavaProjects(IProject[] projects) throws CoreException { 1169 ArrayList<IJavaProject> list = new ArrayList<IJavaProject>(); 1170 1171 for (IProject p : projects) { 1172 if (p.isOpen() && p.hasNature(JavaCore.NATURE_ID)) { 1173 1174 list.add(JavaCore.create(p)); 1175 } 1176 } 1177 1178 return list.toArray(new IJavaProject[list.size()]); 1179 } 1180 1181 /** 1182 * Checks a {@link IFile} to make sure it should be packaged as standard resources. 1183 * @param file the IFile representing the file. 1184 * @return true if the file should be packaged as standard java resources. 1185 */ checkFileForPackaging(IFile file)1186 static boolean checkFileForPackaging(IFile file) { 1187 String name = file.getName(); 1188 1189 String ext = file.getFileExtension(); 1190 return JavaResourceFilter.checkFileForPackaging(name, ext); 1191 } 1192 1193 /** 1194 * Checks whether an {@link IFolder} and its content is valid for packaging into the .apk as 1195 * standard Java resource. 1196 * @param folder the {@link IFolder} to check. 1197 */ checkFolderForPackaging(IFolder folder)1198 static boolean checkFolderForPackaging(IFolder folder) { 1199 String name = folder.getName(); 1200 return JavaResourceFilter.checkFolderForPackaging(name); 1201 } 1202 1203 @Override abortOnBadSetup(IProject project)1204 protected void abortOnBadSetup(IProject project) throws CoreException { 1205 super.abortOnBadSetup(project); 1206 1207 // for this version, we stop on any marker (ie also markers coming from JDT) 1208 IMarker[] markers = project.findMarkers(null /*type*/, false /*includeSubtypes*/, 1209 IResource.DEPTH_ZERO); 1210 1211 if (markers.length > 0) { 1212 stopBuild(""); 1213 } 1214 } 1215 } 1216