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