• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.builders;
18 
19 import com.android.ide.eclipse.adt.AdtConstants;
20 import com.android.ide.eclipse.adt.AdtPlugin;
21 import com.android.ide.eclipse.adt.AndroidPrintStream;
22 import com.android.ide.eclipse.adt.internal.build.AaptExecException;
23 import com.android.ide.eclipse.adt.internal.build.AaptParser;
24 import com.android.ide.eclipse.adt.internal.build.AaptResultException;
25 import com.android.ide.eclipse.adt.internal.build.BuildHelper;
26 import com.android.ide.eclipse.adt.internal.build.BuildHelper.ResourceMarker;
27 import com.android.ide.eclipse.adt.internal.build.DexException;
28 import com.android.ide.eclipse.adt.internal.build.Messages;
29 import com.android.ide.eclipse.adt.internal.build.NativeLibInJarException;
30 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
31 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
32 import com.android.ide.eclipse.adt.internal.project.ApkInstallManager;
33 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
34 import com.android.ide.eclipse.adt.internal.project.LibraryClasspathContainerInitializer;
35 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
36 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
37 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
38 import com.android.ide.eclipse.adt.io.IFileWrapper;
39 import com.android.prefs.AndroidLocation.AndroidLocationException;
40 import com.android.sdklib.SdkConstants;
41 import com.android.sdklib.build.ApkBuilder;
42 import com.android.sdklib.build.ApkCreationException;
43 import com.android.sdklib.build.DuplicateFileException;
44 import com.android.sdklib.build.IArchiveBuilder;
45 import com.android.sdklib.build.SealedApkException;
46 import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
47 import com.android.sdklib.xml.AndroidManifest;
48 
49 import org.eclipse.core.resources.IContainer;
50 import org.eclipse.core.resources.IFile;
51 import org.eclipse.core.resources.IFolder;
52 import org.eclipse.core.resources.IMarker;
53 import org.eclipse.core.resources.IProject;
54 import org.eclipse.core.resources.IResource;
55 import org.eclipse.core.resources.IResourceDelta;
56 import org.eclipse.core.resources.IResourceDeltaVisitor;
57 import org.eclipse.core.runtime.CoreException;
58 import org.eclipse.core.runtime.IPath;
59 import org.eclipse.core.runtime.IProgressMonitor;
60 import org.eclipse.core.runtime.IStatus;
61 import org.eclipse.jdt.core.IJavaModelMarker;
62 import org.eclipse.jdt.core.IJavaProject;
63 import org.eclipse.jdt.core.JavaCore;
64 import org.eclipse.jdt.core.JavaModelException;
65 
66 import java.io.File;
67 import java.io.FileInputStream;
68 import java.io.FileOutputStream;
69 import java.io.IOException;
70 import java.io.InputStream;
71 import java.util.ArrayList;
72 import java.util.Collection;
73 import java.util.List;
74 import java.util.Map;
75 import java.util.jar.Attributes;
76 import java.util.jar.JarEntry;
77 import java.util.jar.JarOutputStream;
78 import java.util.jar.Manifest;
79 import java.util.regex.Pattern;
80 
81 public class PostCompilerBuilder extends BaseBuilder {
82 
83     /** This ID is used in plugin.xml and in each project's .project file.
84      * It cannot be changed even if the class is renamed/moved */
85     public static final String ID = "com.android.ide.eclipse.adt.ApkBuilder"; //$NON-NLS-1$
86 
87     private static final String PROPERTY_CONVERT_TO_DEX = "convertToDex"; //$NON-NLS-1$
88     private static final String PROPERTY_PACKAGE_RESOURCES = "packageResources"; //$NON-NLS-1$
89     private static final String PROPERTY_BUILD_APK = "buildApk"; //$NON-NLS-1$
90 
91     /** Flag to pass to PostCompiler builder that sets if it runs or not.
92      *  Set this flag whenever calling build if PostCompiler is to run
93      */
94     public final static String POST_C_REQUESTED = "RunPostCompiler"; //$NON-NLS-1$
95 
96     /**
97      * Dex conversion flag. This is set to true if one of the changed/added/removed
98      * file is a .class file. Upon visiting all the delta resource, if this
99      * flag is true, then we know we'll have to make the "classes.dex" file.
100      */
101     private boolean mConvertToDex = false;
102 
103     /**
104      * Package resources flag. This is set to true if one of the changed/added/removed
105      * file is a resource file. Upon visiting all the delta resource, if
106      * this flag is true, then we know we'll have to repackage the resources.
107      */
108     private boolean mPackageResources = false;
109 
110     /**
111      * Final package build flag.
112      */
113     private boolean mBuildFinalPackage = false;
114 
115     private AndroidPrintStream mOutStream = null;
116     private AndroidPrintStream mErrStream = null;
117 
118     /**
119      * Basic Resource Delta Visitor class to check if a referenced project had a change in its
120      * compiled java files.
121      */
122     private static class ReferencedProjectDeltaVisitor implements IResourceDeltaVisitor {
123 
124         private boolean mConvertToDex = false;
125         private boolean mMakeFinalPackage;
126 
127         private IPath mOutputFolder;
128         private List<IPath> mSourceFolders;
129 
ReferencedProjectDeltaVisitor(IJavaProject javaProject)130         private ReferencedProjectDeltaVisitor(IJavaProject javaProject) {
131             try {
132                 mOutputFolder = javaProject.getOutputLocation();
133                 mSourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject);
134             } catch (JavaModelException e) {
135             }
136         }
137 
138         /**
139          * {@inheritDoc}
140          * @throws CoreException
141          */
142         @Override
visit(IResourceDelta delta)143         public boolean visit(IResourceDelta delta) throws CoreException {
144             //  no need to keep looking if we already know we need to convert
145             // to dex and make the final package.
146             if (mConvertToDex && mMakeFinalPackage) {
147                 return false;
148             }
149 
150             // get the resource and the path segments.
151             IResource resource = delta.getResource();
152             IPath resourceFullPath = resource.getFullPath();
153 
154             if (mOutputFolder.isPrefixOf(resourceFullPath)) {
155                 int type = resource.getType();
156                 if (type == IResource.FILE) {
157                     String ext = resource.getFileExtension();
158                     if (AdtConstants.EXT_CLASS.equals(ext)) {
159                         mConvertToDex = true;
160                     }
161                 }
162                 return true;
163             } else {
164                 for (IPath sourceFullPath : mSourceFolders) {
165                     if (sourceFullPath.isPrefixOf(resourceFullPath)) {
166                         int type = resource.getType();
167                         if (type == IResource.FILE) {
168                             // check if the file is a valid file that would be
169                             // included during the final packaging.
170                             if (BuildHelper.checkFileForPackaging((IFile)resource)) {
171                                 mMakeFinalPackage = true;
172                             }
173 
174                             return false;
175                         } else if (type == IResource.FOLDER) {
176                             // if this is a folder, we check if this is a valid folder as well.
177                             // If this is a folder that needs to be ignored, we must return false,
178                             // so that we ignore its content.
179                             return BuildHelper.checkFolderForPackaging((IFolder)resource);
180                         }
181                     }
182                 }
183             }
184 
185             return true;
186         }
187 
188         /**
189          * Returns if one of the .class file was modified.
190          */
needDexConvertion()191         boolean needDexConvertion() {
192             return mConvertToDex;
193         }
194 
needMakeFinalPackage()195         boolean needMakeFinalPackage() {
196             return mMakeFinalPackage;
197         }
198     }
199 
200     private ResourceMarker mResourceMarker = new ResourceMarker() {
201         @Override
202         public void setWarning(IResource resource, String message) {
203             BaseProjectHelper.markResource(resource, AdtConstants.MARKER_PACKAGING,
204                     message, IMarker.SEVERITY_WARNING);
205         }
206     };
207 
208 
PostCompilerBuilder()209     public PostCompilerBuilder() {
210         super();
211     }
212 
213     @Override
clean(IProgressMonitor monitor)214     protected void clean(IProgressMonitor monitor) throws CoreException {
215         super.clean(monitor);
216 
217         // Get the project.
218         IProject project = getProject();
219 
220         if (DEBUG) {
221             System.out.println("CLEAN(POST) " + project.getName());
222         }
223 
224         // Clear the project of the generic markers
225         removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE);
226         removeMarkersFromContainer(project, AdtConstants.MARKER_PACKAGING);
227 
228         // also remove the files in the output folder (but not the Eclipse output folder).
229         IFolder javaOutput = BaseProjectHelper.getJavaOutputFolder(project);
230         IFolder androidOutput = BaseProjectHelper.getAndroidOutputFolder(project);
231 
232         if (javaOutput.equals(androidOutput) == false) {
233             // get the content
234             IResource[] members = androidOutput.members();
235             for (IResource member : members) {
236                 if (member.equals(javaOutput) == false) {
237                     member.delete(true /*force*/, monitor);
238                 }
239             }
240         }
241     }
242 
243     // build() returns a list of project from which this project depends for future compilation.
244     @Override
build( int kind, @SuppressWarnings("rawtypes") Map args, IProgressMonitor monitor)245     protected IProject[] build(
246             int kind,
247             @SuppressWarnings("rawtypes") Map args,
248             IProgressMonitor monitor)
249             throws CoreException {
250         // get a project object
251         IProject project = getProject();
252 
253         if (DEBUG) {
254             System.out.println("BUILD(POST) " + project.getName());
255         }
256 
257         // Benchmarking start
258         long startBuildTime = 0;
259         if (BuildHelper.BENCHMARK_FLAG) {
260             // End JavaC Timer
261             String msg = "BENCHMARK ADT: Ending Compilation \n BENCHMARK ADT: Time Elapsed: " +    //$NON-NLS-1$
262                          (System.nanoTime() - BuildHelper.sStartJavaCTime)/Math.pow(10, 6) + "ms"; //$NON-NLS-1$
263             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
264             msg = "BENCHMARK ADT: Starting PostCompilation";                                        //$NON-NLS-1$
265             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
266             startBuildTime = System.nanoTime();
267         }
268 
269         // list of referenced projects. This is a mix of java projects and library projects
270         // and is computed below.
271         IProject[] allRefProjects = null;
272 
273         try {
274             // get the project info
275             ProjectState projectState = Sdk.getProjectState(project);
276 
277             // this can happen if the project has no project.properties.
278             if (projectState == null) {
279                 return null;
280             }
281 
282             boolean isLibrary = projectState.isLibrary();
283 
284             // get the libraries
285             List<IProject> libProjects = projectState.getFullLibraryProjects();
286 
287             IJavaProject javaProject = JavaCore.create(project);
288 
289             // get the list of referenced projects.
290             List<IProject> javaProjects = ProjectHelper.getReferencedProjects(project);
291             List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects(
292                     javaProjects);
293 
294             // mix the java project and the library projects
295             final int size = libProjects.size() + javaProjects.size();
296             ArrayList<IProject> refList = new ArrayList<IProject>(size);
297             refList.addAll(libProjects);
298             refList.addAll(javaProjects);
299             allRefProjects = refList.toArray(new IProject[size]);
300 
301             // get the android output folder
302             IFolder androidOutputFolder = BaseProjectHelper.getAndroidOutputFolder(project);
303             IFolder resOutputFolder = androidOutputFolder.getFolder(SdkConstants.FD_RES);
304 
305             // now we need to get the classpath list
306             List<IPath> sourceList = BaseProjectHelper.getSourceClasspaths(javaProject);
307 
308             // First thing we do is go through the resource delta to not
309             // lose it if we have to abort the build for any reason.
310             PostCompilerDeltaVisitor dv = null;
311             if (args.containsKey(POST_C_REQUESTED)
312                     && AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) {
313                 // Skip over flag setting
314             } else if (kind == FULL_BUILD) {
315                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
316                         Messages.Start_Full_Apk_Build);
317 
318                 if (DEBUG) {
319                     System.out.println("\tfull build!");
320                 }
321 
322                 // Full build: we do all the steps.
323                 mPackageResources = true;
324                 mConvertToDex = true;
325                 mBuildFinalPackage = true;
326             } else {
327                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
328                         Messages.Start_Inc_Apk_Build);
329 
330                 // go through the resources and see if something changed.
331                 IResourceDelta delta = getDelta(project);
332                 if (delta == null) {
333                     // no delta? Same as full build: we do all the steps.
334                     mPackageResources = true;
335                     mConvertToDex = true;
336                     mBuildFinalPackage = true;
337                 } else {
338                     dv = new PostCompilerDeltaVisitor(this, sourceList, androidOutputFolder);
339                     delta.accept(dv);
340 
341                     // save the state
342                     mPackageResources |= dv.getPackageResources();
343                     mConvertToDex |= dv.getConvertToDex();
344                     mBuildFinalPackage |= dv.getMakeFinalPackage();
345                 }
346 
347                 // if the main resources didn't change, then we check for the library
348                 // ones (will trigger resource repackaging too)
349                 if ((mPackageResources == false || mBuildFinalPackage == false) &&
350                         libProjects.size() > 0) {
351                     for (IProject libProject : libProjects) {
352                         delta = getDelta(libProject);
353                         if (delta != null) {
354                             LibraryDeltaVisitor visitor = new LibraryDeltaVisitor();
355                             delta.accept(visitor);
356 
357                             mPackageResources |= visitor.getResChange();
358                             mBuildFinalPackage |= visitor.getLibChange();
359 
360                             if (mPackageResources && mBuildFinalPackage) {
361                                 break;
362                             }
363                         }
364                     }
365                 }
366 
367                 // also go through the delta for all the referenced projects, until we are forced to
368                 // compile anyway
369                 final int referencedCount = referencedJavaProjects.size();
370                 for (int i = 0 ; i < referencedCount &&
371                         (mBuildFinalPackage == false || mConvertToDex == false); i++) {
372                     IJavaProject referencedJavaProject = referencedJavaProjects.get(i);
373                     delta = getDelta(referencedJavaProject.getProject());
374                     if (delta != null) {
375                         ReferencedProjectDeltaVisitor refProjectDv =
376                                 new ReferencedProjectDeltaVisitor(referencedJavaProject);
377 
378                         delta.accept(refProjectDv);
379 
380                         // save the state
381                         mConvertToDex |= refProjectDv.needDexConvertion();
382                         mBuildFinalPackage |= refProjectDv.needMakeFinalPackage();
383                     }
384                 }
385             }
386 
387             // store the build status in the persistent storage
388             saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
389             saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
390             saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
391 
392             // Top level check to make sure the build can move forward. Only do this after recording
393             // delta changes.
394             abortOnBadSetup(javaProject);
395 
396             if (dv != null && dv.mXmlError) {
397                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
398                 Messages.Xml_Error);
399 
400                 // if there was some XML errors, we just return w/o doing
401                 // anything since we've put some markers in the files anyway
402                 return allRefProjects;
403             }
404 
405             // remove older packaging markers.
406             removeMarkersFromContainer(javaProject.getProject(), AdtConstants.MARKER_PACKAGING);
407 
408             if (androidOutputFolder == null) {
409                 // mark project and exit
410                 markProject(AdtConstants.MARKER_PACKAGING, Messages.Failed_To_Get_Output,
411                         IMarker.SEVERITY_ERROR);
412                 return allRefProjects;
413             }
414 
415             // finished with the common init and tests. Special case of the library.
416             if (isLibrary) {
417                 // check the jar output file is present, if not create it.
418                 IFile jarIFile = androidOutputFolder.getFile(
419                         project.getName().toLowerCase() + AdtConstants.DOT_JAR);
420                 if (mConvertToDex == false && jarIFile.exists() == false) {
421                     mConvertToDex = true;
422                 }
423 
424                 // also update the crunch cache always since aapt does it smartly only
425                 // on the files that need it.
426                 if (DEBUG) {
427                     System.out.println("\trunning crunch!");
428                 }
429                 BuildHelper helper = new BuildHelper(project,
430                         mOutStream, mErrStream,
431                         true /*debugMode*/,
432                         AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE,
433                         mResourceMarker);
434                 updateCrunchCache(project, helper);
435 
436                 // refresh recursively bin/res folder
437                 resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
438 
439                 if (mConvertToDex) { // in this case this means some class files changed and
440                                      // we need to update the jar file.
441                     if (DEBUG) {
442                         System.out.println("\tupdating jar!");
443                     }
444 
445                     // resource to the AndroidManifest.xml file
446                     IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
447                     String appPackage = AndroidManifest.getPackage(new IFileWrapper(manifestFile));
448 
449                     IFolder javaOutputFolder = BaseProjectHelper.getJavaOutputFolder(project);
450 
451                     writeLibraryPackage(jarIFile, project, appPackage, javaOutputFolder);
452                     saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex = false);
453 
454                     // refresh the bin folder content with no recursion to update the library
455                     // jar file.
456                     androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
457 
458                     // Also update the projects. The only way to force recompile them is to
459                     // reset the library container.
460                     List<ProjectState> parentProjects = projectState.getParentProjects();
461                     LibraryClasspathContainerInitializer.updateProject(parentProjects);
462                 }
463 
464                 return allRefProjects;
465             }
466 
467             // Check to see if we're going to launch or export. If not, we can skip
468             // the packaging and dexing process.
469             if (!args.containsKey(POST_C_REQUESTED)
470                     && AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) {
471                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
472                         Messages.Skip_Post_Compiler);
473                 return allRefProjects;
474             } else {
475                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
476                         Messages.Start_Full_Post_Compiler);
477             }
478 
479             // first thing we do is check that the SDK directory has been setup.
480             String osSdkFolder = AdtPlugin.getOsSdkFolder();
481 
482             if (osSdkFolder.length() == 0) {
483                 // this has already been checked in the precompiler. Therefore,
484                 // while we do have to cancel the build, we don't have to return
485                 // any error or throw anything.
486                 return allRefProjects;
487             }
488 
489             // do some extra check, in case the output files are not present. This
490             // will force to recreate them.
491             IResource tmp = null;
492 
493             if (mPackageResources == false) {
494                 // check the full resource package
495                 tmp = androidOutputFolder.findMember(AdtConstants.FN_RESOURCES_AP_);
496                 if (tmp == null || tmp.exists() == false) {
497                     mPackageResources = true;
498                     mBuildFinalPackage = true;
499                 }
500             }
501 
502             // check classes.dex is present. If not we force to recreate it.
503             if (mConvertToDex == false) {
504                 tmp = androidOutputFolder.findMember(SdkConstants.FN_APK_CLASSES_DEX);
505                 if (tmp == null || tmp.exists() == false) {
506                     mConvertToDex = true;
507                     mBuildFinalPackage = true;
508                 }
509             }
510 
511             // also check the final file(s)!
512             String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/);
513             if (mBuildFinalPackage == false) {
514                 tmp = androidOutputFolder.findMember(finalPackageName);
515                 if (tmp == null || (tmp instanceof IFile &&
516                         tmp.exists() == false)) {
517                     String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName);
518                     AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg);
519                     mBuildFinalPackage = true;
520                 }
521             }
522 
523             // at this point we know if we need to recreate the temporary apk
524             // or the dex file, but we don't know if we simply need to recreate them
525             // because they are missing
526 
527             // refresh the output directory first
528             IContainer ic = androidOutputFolder.getParent();
529             if (ic != null) {
530                 ic.refreshLocal(IResource.DEPTH_ONE, monitor);
531             }
532 
533             // Get the DX output stream. Since the builder is created for the life of the
534             // project, they can be kept around.
535             if (mOutStream == null) {
536                 mOutStream = new AndroidPrintStream(project, null /*prefix*/,
537                         AdtPlugin.getOutStream());
538                 mErrStream = new AndroidPrintStream(project, null /*prefix*/,
539                         AdtPlugin.getOutStream());
540             }
541 
542             // we need to test all three, as we may need to make the final package
543             // but not the intermediary ones.
544             if (mPackageResources || mConvertToDex || mBuildFinalPackage) {
545                 BuildHelper helper = new BuildHelper(project,
546                         mOutStream, mErrStream,
547                         true /*debugMode*/,
548                         AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE,
549                         mResourceMarker);
550 
551                 // resource to the AndroidManifest.xml file
552                 IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
553 
554                 if (manifestFile == null || manifestFile.exists() == false) {
555                     // mark project and exit
556                     String msg = String.format(Messages.s_File_Missing,
557                             SdkConstants.FN_ANDROID_MANIFEST_XML);
558                     markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR);
559                     return allRefProjects;
560                 }
561 
562                 IPath androidBinLocation = androidOutputFolder.getLocation();
563                 if (androidBinLocation == null) {
564                     markProject(AdtConstants.MARKER_PACKAGING, Messages.Output_Missing,
565                             IMarker.SEVERITY_ERROR);
566                     return allRefProjects;
567                 }
568                 String osAndroidBinPath = androidBinLocation.toOSString();
569 
570                 // Remove the old .apk.
571                 // This make sure that if the apk is corrupted, then dx (which would attempt
572                 // to open it), will not fail.
573                 String osFinalPackagePath = osAndroidBinPath + File.separator + finalPackageName;
574                 File finalPackage = new File(osFinalPackagePath);
575 
576                 // if delete failed, this is not really a problem, as the final package generation
577                 // handle already present .apk, and if that one failed as well, the user will be
578                 // notified.
579                 finalPackage.delete();
580 
581                 // Check if we need to package the resources.
582                 if (mPackageResources) {
583                     // also update the crunch cache always since aapt does it smartly only
584                     // on the files that need it.
585                     if (DEBUG) {
586                         System.out.println("\trunning crunch!");
587                     }
588                     if (updateCrunchCache(project, helper) == false) {
589                         return allRefProjects;
590                     }
591 
592                     // refresh recursively bin/res folder
593                     resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
594 
595                     if (DEBUG) {
596                         System.out.println("\tpackaging resources!");
597                     }
598                     // remove some aapt_package only markers.
599                     removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE);
600 
601                     try {
602                         helper.packageResources(manifestFile, libProjects, null /*resfilter*/,
603                                 0 /*versionCode */, osAndroidBinPath,
604                                 AdtConstants.FN_RESOURCES_AP_);
605                     } catch (AaptExecException e) {
606                         BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING,
607                                 e.getMessage(), IMarker.SEVERITY_ERROR);
608                         return allRefProjects;
609                     } catch (AaptResultException e) {
610                         // attempt to parse the error output
611                         String[] aaptOutput = e.getOutput();
612                         boolean parsingError = AaptParser.parseOutput(aaptOutput, project);
613 
614                         // if we couldn't parse the output we display it in the console.
615                         if (parsingError) {
616                             AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput);
617 
618                             // if the exec failed, and we couldn't parse the error output (and
619                             // therefore not all files that should have been marked, were marked),
620                             // we put a generic marker on the project and abort.
621                             BaseProjectHelper.markResource(project,
622                                     AdtConstants.MARKER_PACKAGING,
623                                     Messages.Unparsed_AAPT_Errors,
624                                     IMarker.SEVERITY_ERROR);
625                         }
626                     }
627 
628                     // build has been done. reset the state of the builder
629                     mPackageResources = false;
630 
631                     // and store it
632                     saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
633                 }
634 
635                 String classesDexPath = osAndroidBinPath + File.separator +
636                         SdkConstants.FN_APK_CLASSES_DEX;
637 
638                 // then we check if we need to package the .class into classes.dex
639                 if (mConvertToDex) {
640                     if (DEBUG) {
641                         System.out.println("\trunning dex!");
642                     }
643                     try {
644                         Collection<String> dxInputPaths = helper.getCompiledCodePaths();
645 
646                         helper.executeDx(javaProject, dxInputPaths, classesDexPath);
647                     } catch (DexException e) {
648                         String message = e.getMessage();
649 
650                         AdtPlugin.printErrorToConsole(project, message);
651                         BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING,
652                                 message, IMarker.SEVERITY_ERROR);
653 
654                         Throwable cause = e.getCause();
655 
656                         if (cause instanceof NoClassDefFoundError
657                                 || cause instanceof NoSuchMethodError) {
658                             AdtPlugin.printErrorToConsole(project, Messages.Incompatible_VM_Warning,
659                                     Messages.Requires_1_5_Error);
660                         }
661 
662                         // dx failed, we return
663                         return allRefProjects;
664                     }
665 
666                     // build has been done. reset the state of the builder
667                     mConvertToDex = false;
668 
669                     // and store it
670                     saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
671                 }
672 
673                 // now we need to make the final package from the intermediary apk
674                 // and classes.dex.
675                 // This is the default package with all the resources.
676 
677                 try {
678                     if (DEBUG) {
679                         System.out.println("\tmaking final package!");
680                     }
681                     helper.finalDebugPackage(
682                             osAndroidBinPath + File.separator + AdtConstants.FN_RESOURCES_AP_,
683                         classesDexPath, osFinalPackagePath, libProjects, mResourceMarker);
684                 } catch (KeytoolException e) {
685                     String eMessage = e.getMessage();
686 
687                     // mark the project with the standard message
688                     String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
689                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
690                             IMarker.SEVERITY_ERROR);
691 
692                     // output more info in the console
693                     AdtPlugin.printErrorToConsole(project,
694                             msg,
695                             String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()),
696                             Messages.ApkBuilder_Update_or_Execute_manually_s,
697                             e.getCommandLine());
698 
699                     return allRefProjects;
700                 } catch (ApkCreationException e) {
701                     String eMessage = e.getMessage();
702 
703                     // mark the project with the standard message
704                     String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
705                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
706                             IMarker.SEVERITY_ERROR);
707                 } catch (AndroidLocationException e) {
708                     String eMessage = e.getMessage();
709 
710                     // mark the project with the standard message
711                     String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
712                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
713                             IMarker.SEVERITY_ERROR);
714                 } catch (NativeLibInJarException e) {
715                     String msg = e.getMessage();
716 
717                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING,
718                             msg, IMarker.SEVERITY_ERROR);
719 
720                     AdtPlugin.printErrorToConsole(project, (Object[]) e.getAdditionalInfo());
721                 } catch (CoreException e) {
722                     // mark project and return
723                     String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage());
724                     AdtPlugin.printErrorToConsole(project, msg);
725                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
726                             IMarker.SEVERITY_ERROR);
727                 } catch (DuplicateFileException e) {
728                     String msg1 = String.format(
729                             "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s",
730                             e.getArchivePath(), e.getFile1(), e.getFile2());
731                     String msg2 = String.format(Messages.Final_Archive_Error_s, msg1);
732                     AdtPlugin.printErrorToConsole(project, msg2);
733                     BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg2,
734                             IMarker.SEVERITY_ERROR);
735                 }
736 
737                 // we are done.
738 
739                 // refresh the bin folder content with no recursion.
740                 androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
741 
742                 // build has been done. reset the state of the builder
743                 mBuildFinalPackage = false;
744 
745                 // and store it
746                 saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
747 
748                 // reset the installation manager to force new installs of this project
749                 ApkInstallManager.getInstance().resetInstallationFor(project);
750 
751                 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(),
752                         "Build Success!");
753             }
754         } catch (AbortBuildException e) {
755             return allRefProjects;
756         } catch (Exception exception) {
757             // try to catch other exception to actually display an error. This will be useful
758             // if we get an NPE or something so that we can at least notify the user that something
759             // went wrong.
760 
761             // first check if this is a CoreException we threw to cancel the build.
762             if (exception instanceof CoreException) {
763                 if (((CoreException)exception).getStatus().getSeverity() == IStatus.CANCEL) {
764                     // Project is already marked with an error. Nothing to do
765                     return allRefProjects;
766                 }
767             }
768 
769             String msg = exception.getMessage();
770             if (msg == null) {
771                 msg = exception.getClass().getCanonicalName();
772             }
773 
774             msg = String.format("Unknown error: %1$s", msg);
775             AdtPlugin.logAndPrintError(exception, project.getName(), msg);
776             markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR);
777         }
778 
779         // Benchmarking end
780         if (BuildHelper.BENCHMARK_FLAG) {
781             String msg = "BENCHMARK ADT: Ending PostCompilation. \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$
782                          ((System.nanoTime() - startBuildTime)/Math.pow(10, 6)) + "ms";              //$NON-NLS-1$
783             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
784             // End Overall Timer
785             msg = "BENCHMARK ADT: Done with everything! \n BENCHMARK ADT: Time Elapsed: " +          //$NON-NLS-1$
786                   (System.nanoTime() - BuildHelper.sStartOverallTime)/Math.pow(10, 6) + "ms";        //$NON-NLS-1$
787             AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
788         }
789 
790         return allRefProjects;
791     }
792 
793     private static class JarBuilder implements IArchiveBuilder {
794 
795         private static Pattern R_PATTERN = Pattern.compile("R(\\$.*)?\\.class"); //$NON-NLS-1$
796         private static Pattern MANIFEST_PATTERN = Pattern.compile("Manifest(\\$.*)?\\.class"); //$NON-NLS-1$
797         private static String BUILD_CONFIG_CLASS = "BuildConfig.class"; //$NON-NLS-1$
798 
799         private final byte[] buffer = new byte[1024];
800         private final JarOutputStream mOutputStream;
801         private final String mAppPackage;
802 
JarBuilder(JarOutputStream outputStream, String appPackage)803         JarBuilder(JarOutputStream outputStream, String appPackage) {
804             mOutputStream = outputStream;
805             mAppPackage = appPackage.replace('.', '/');
806         }
807 
addFile(IFile file, IFolder rootFolder)808         public void addFile(IFile file, IFolder rootFolder) throws ApkCreationException {
809             // we only package class file from the output folder
810             if (AdtConstants.EXT_CLASS.equals(file.getFileExtension()) == false) {
811                 return;
812             }
813 
814             IPath packageApp = file.getParent().getFullPath().makeRelativeTo(
815                     rootFolder.getFullPath());
816 
817             String name = file.getName();
818             // Ignore the library's R/Manifest/BuildConfig classes.
819             if (mAppPackage.equals(packageApp.toString()) &&
820                             (BUILD_CONFIG_CLASS.equals(name) ||
821                             MANIFEST_PATTERN.matcher(name).matches() ||
822                             R_PATTERN.matcher(name).matches())) {
823                 return;
824             }
825 
826             IPath path = file.getFullPath().makeRelativeTo(rootFolder.getFullPath());
827             try {
828                 addFile(file.getContents(), file.getLocalTimeStamp(), path.toString());
829             } catch (ApkCreationException e) {
830                 throw e;
831             } catch (Exception e) {
832                 throw new ApkCreationException(e, "Failed to add %s", file);
833             }
834         }
835 
836         @Override
addFile(File file, String archivePath)837         public void addFile(File file, String archivePath) throws ApkCreationException,
838                 SealedApkException, DuplicateFileException {
839             try {
840                 FileInputStream inputStream = new FileInputStream(file);
841                 long lastModified = file.lastModified();
842                 addFile(inputStream, lastModified, archivePath);
843             } catch (ApkCreationException e) {
844                 throw e;
845             } catch (Exception e) {
846                 throw new ApkCreationException(e, "Failed to add %s", file);
847             }
848         }
849 
addFile(InputStream content, long lastModified, String archivePath)850         private void addFile(InputStream content, long lastModified, String archivePath)
851                 throws IOException, ApkCreationException {
852             // create the jar entry
853             JarEntry entry = new JarEntry(archivePath);
854             entry.setTime(lastModified);
855 
856             try {
857                 // add the entry to the jar archive
858                 mOutputStream.putNextEntry(entry);
859 
860                 // read the content of the entry from the input stream, and write
861                 // it into the archive.
862                 int count;
863                 while ((count = content.read(buffer)) != -1) {
864                     mOutputStream.write(buffer, 0, count);
865                 }
866             } finally {
867                 try {
868                     if (content != null) {
869                         content.close();
870                     }
871                 } catch (Exception e) {
872                     throw new ApkCreationException(e, "Failed to close stream");
873                 }
874             }
875         }
876     }
877 
878     /**
879      * Updates the crunch cache if needed and return true if the build must continue.
880      */
updateCrunchCache(IProject project, BuildHelper helper)881     private boolean updateCrunchCache(IProject project, BuildHelper helper) {
882         try {
883             helper.updateCrunchCache();
884         } catch (AaptExecException e) {
885             BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING,
886                     e.getMessage(), IMarker.SEVERITY_ERROR);
887             return false;
888         } catch (AaptResultException e) {
889             // attempt to parse the error output
890             String[] aaptOutput = e.getOutput();
891             boolean parsingError = AaptParser.parseOutput(aaptOutput, project);
892             // if we couldn't parse the output we display it in the console.
893             if (parsingError) {
894                 AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput);
895             }
896         }
897 
898         return true;
899     }
900 
901     /**
902      * Writes the library jar file.
903      * @param jarIFile the destination file
904      * @param project the library project
905      * @param appPackage the library android package
906      * @param javaOutputFolder the JDT output folder.
907      */
writeLibraryPackage(IFile jarIFile, IProject project, String appPackage, IFolder javaOutputFolder)908     private void writeLibraryPackage(IFile jarIFile, IProject project, String appPackage,
909             IFolder javaOutputFolder) {
910 
911         JarOutputStream jos = null;
912         try {
913             Manifest manifest = new Manifest();
914             Attributes mainAttributes = manifest.getMainAttributes();
915             mainAttributes.put(Attributes.Name.CLASS_PATH, "Android ADT"); //$NON-NLS-1$
916             mainAttributes.putValue("Created-By", "1.0 (Android)"); //$NON-NLS-1$  //$NON-NLS-2$
917             jos = new JarOutputStream(
918                     new FileOutputStream(jarIFile.getLocation().toFile()), manifest);
919 
920             JarBuilder jarBuilder = new JarBuilder(jos, appPackage);
921 
922             // write the class files
923             writeClassFilesIntoJar(jarBuilder, javaOutputFolder, javaOutputFolder);
924 
925             // now write the standard Java resources from the output folder
926             ApkBuilder.addSourceFolder(jarBuilder, javaOutputFolder.getLocation().toFile());
927 
928             saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
929         } catch (Exception e) {
930             AdtPlugin.log(e, "Failed to write jar file %s", jarIFile.getLocation().toOSString());
931         } finally {
932             if (jos != null) {
933                 try {
934                     jos.close();
935                 } catch (IOException e) {
936                     // pass
937                 }
938             }
939         }
940     }
941 
writeClassFilesIntoJar(JarBuilder builder, IFolder folder, IFolder rootFolder)942     private void writeClassFilesIntoJar(JarBuilder builder, IFolder folder, IFolder rootFolder)
943             throws CoreException, IOException, ApkCreationException {
944         IResource[] members = folder.members();
945         for (IResource member : members) {
946             if (member.getType() == IResource.FOLDER) {
947                 writeClassFilesIntoJar(builder, (IFolder) member, rootFolder);
948             } else if (member.getType() == IResource.FILE) {
949                 IFile file = (IFile) member;
950                 builder.addFile(file, rootFolder);
951             }
952         }
953     }
954 
955     @Override
startupOnInitialize()956     protected void startupOnInitialize() {
957         super.startupOnInitialize();
958 
959         // load the build status. We pass true as the default value to
960         // force a recompile in case the property was not found
961         mConvertToDex = loadProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, true);
962         mPackageResources = loadProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, true);
963         mBuildFinalPackage = loadProjectBooleanProperty(PROPERTY_BUILD_APK, true);
964     }
965 
966     @Override
abortOnBadSetup(IJavaProject javaProject)967     protected void abortOnBadSetup(IJavaProject javaProject) throws AbortBuildException {
968         super.abortOnBadSetup(javaProject);
969 
970         IProject iProject = getProject();
971 
972         // do a (hopefully quick) search for Precompiler type markers. Those are always only
973         // errors.
974         stopOnMarker(iProject, AdtConstants.MARKER_AAPT_COMPILE, IResource.DEPTH_INFINITE,
975                 false /*checkSeverity*/);
976         stopOnMarker(iProject, AdtConstants.MARKER_AIDL, IResource.DEPTH_INFINITE,
977                 false /*checkSeverity*/);
978         stopOnMarker(iProject, AdtConstants.MARKER_RENDERSCRIPT, IResource.DEPTH_INFINITE,
979                 false /*checkSeverity*/);
980         stopOnMarker(iProject, AdtConstants.MARKER_ANDROID, IResource.DEPTH_ZERO,
981                 false /*checkSeverity*/);
982 
983         // do a search for JDT markers. Those can be errors or warnings
984         stopOnMarker(iProject, IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER,
985                 IResource.DEPTH_INFINITE, true /*checkSeverity*/);
986         stopOnMarker(iProject, IJavaModelMarker.BUILDPATH_PROBLEM_MARKER,
987                 IResource.DEPTH_INFINITE, true /*checkSeverity*/);
988     }
989 }
990