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