diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java | 946 |
1 files changed, 946 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java new file mode 100644 index 000000000..8aacb44ef --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java @@ -0,0 +1,946 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.build.builders; + +import com.android.SdkConstants; +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.AndroidPrintStream; +import com.android.ide.eclipse.adt.internal.build.AaptExecException; +import com.android.ide.eclipse.adt.internal.build.AaptParser; +import com.android.ide.eclipse.adt.internal.build.AaptResultException; +import com.android.ide.eclipse.adt.internal.build.BuildHelper; +import com.android.ide.eclipse.adt.internal.build.BuildHelper.ResourceMarker; +import com.android.ide.eclipse.adt.internal.build.DexException; +import com.android.ide.eclipse.adt.internal.build.Messages; +import com.android.ide.eclipse.adt.internal.build.NativeLibInJarException; +import com.android.ide.eclipse.adt.internal.lint.LintDeltaProcessor; +import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; +import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; +import com.android.ide.eclipse.adt.internal.project.ApkInstallManager; +import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; +import com.android.ide.eclipse.adt.internal.project.LibraryClasspathContainerInitializer; +import com.android.ide.eclipse.adt.internal.project.ProjectHelper; +import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.ide.eclipse.adt.io.IFileWrapper; +import com.android.prefs.AndroidLocation.AndroidLocationException; +import com.android.sdklib.build.ApkBuilder; +import com.android.sdklib.build.ApkCreationException; +import com.android.sdklib.build.DuplicateFileException; +import com.android.sdklib.build.IArchiveBuilder; +import com.android.sdklib.build.SealedApkException; +import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException; +import com.android.xml.AndroidManifest; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jdt.core.IJavaModelMarker; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.regex.Pattern; + +public class PostCompilerBuilder extends BaseBuilder { + + /** This ID is used in plugin.xml and in each project's .project file. + * It cannot be changed even if the class is renamed/moved */ + public static final String ID = "com.android.ide.eclipse.adt.ApkBuilder"; //$NON-NLS-1$ + + private static final String PROPERTY_CONVERT_TO_DEX = "convertToDex"; //$NON-NLS-1$ + private static final String PROPERTY_PACKAGE_RESOURCES = "packageResources"; //$NON-NLS-1$ + private static final String PROPERTY_BUILD_APK = "buildApk"; //$NON-NLS-1$ + + /** Flag to pass to PostCompiler builder that sets if it runs or not. + * Set this flag whenever calling build if PostCompiler is to run + */ + public final static String POST_C_REQUESTED = "RunPostCompiler"; //$NON-NLS-1$ + + /** + * Dex conversion flag. This is set to true if one of the changed/added/removed + * file is a .class file. Upon visiting all the delta resource, if this + * flag is true, then we know we'll have to make the "classes.dex" file. + */ + private boolean mConvertToDex = false; + + /** + * Package resources flag. This is set to true if one of the changed/added/removed + * file is a resource file. Upon visiting all the delta resource, if + * this flag is true, then we know we'll have to repackage the resources. + */ + private boolean mPackageResources = false; + + /** + * Final package build flag. + */ + private boolean mBuildFinalPackage = false; + + private AndroidPrintStream mOutStream = null; + private AndroidPrintStream mErrStream = null; + + + private ResourceMarker mResourceMarker = new ResourceMarker() { + @Override + public void setWarning(IResource resource, String message) { + BaseProjectHelper.markResource(resource, AdtConstants.MARKER_PACKAGING, + message, IMarker.SEVERITY_WARNING); + } + }; + + + public PostCompilerBuilder() { + super(); + } + + @Override + protected void clean(IProgressMonitor monitor) throws CoreException { + super.clean(monitor); + + // Get the project. + IProject project = getProject(); + + if (DEBUG_LOG) { + AdtPlugin.log(IStatus.INFO, "%s CLEAN(POST)", project.getName()); + } + + // Clear the project of the generic markers + removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE); + removeMarkersFromContainer(project, AdtConstants.MARKER_PACKAGING); + + // also remove the files in the output folder (but not the Eclipse output folder). + IFolder javaOutput = BaseProjectHelper.getJavaOutputFolder(project); + IFolder androidOutput = BaseProjectHelper.getAndroidOutputFolder(project); + + if (javaOutput.equals(androidOutput) == false) { + // get the content + IResource[] members = androidOutput.members(); + for (IResource member : members) { + if (member.equals(javaOutput) == false) { + member.delete(true /*force*/, monitor); + } + } + } + } + + // build() returns a list of project from which this project depends for future compilation. + @Override + protected IProject[] build( + int kind, + @SuppressWarnings("rawtypes") Map args, + IProgressMonitor monitor) + throws CoreException { + // get a project object + IProject project = getProject(); + + if (DEBUG_LOG) { + AdtPlugin.log(IStatus.INFO, "%s BUILD(POST)", project.getName()); + } + + // Benchmarking start + long startBuildTime = 0; + if (BuildHelper.BENCHMARK_FLAG) { + // End JavaC Timer + String msg = "BENCHMARK ADT: Ending Compilation \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$ + (System.nanoTime() - BuildHelper.sStartJavaCTime)/Math.pow(10, 6) + "ms"; //$NON-NLS-1$ + AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg); + msg = "BENCHMARK ADT: Starting PostCompilation"; //$NON-NLS-1$ + AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg); + startBuildTime = System.nanoTime(); + } + + // list of referenced projects. This is a mix of java projects and library projects + // and is computed below. + IProject[] allRefProjects = null; + + try { + // get the project info + ProjectState projectState = Sdk.getProjectState(project); + + // this can happen if the project has no project.properties. + if (projectState == null) { + return null; + } + + boolean isLibrary = projectState.isLibrary(); + + // get the libraries + List<IProject> libProjects = projectState.getFullLibraryProjects(); + + IJavaProject javaProject = JavaCore.create(project); + + // get the list of referenced projects. + List<IProject> javaProjects = ProjectHelper.getReferencedProjects(project); + List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects( + javaProjects); + + // mix the java project and the library projects + final int size = libProjects.size() + javaProjects.size(); + ArrayList<IProject> refList = new ArrayList<IProject>(size); + refList.addAll(libProjects); + refList.addAll(javaProjects); + allRefProjects = refList.toArray(new IProject[size]); + + // get the android output folder + IFolder androidOutputFolder = BaseProjectHelper.getAndroidOutputFolder(project); + IFolder resOutputFolder = androidOutputFolder.getFolder(SdkConstants.FD_RES); + + // First thing we do is go through the resource delta to not + // lose it if we have to abort the build for any reason. + if (args.containsKey(POST_C_REQUESTED) + && AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) { + // Skip over flag setting + } else if (kind == FULL_BUILD) { + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, + Messages.Start_Full_Apk_Build); + + if (DEBUG_LOG) { + AdtPlugin.log(IStatus.INFO, "%s full build!", project.getName()); + } + + // Full build: we do all the steps. + mPackageResources = true; + mConvertToDex = true; + mBuildFinalPackage = true; + } else { + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, + Messages.Start_Inc_Apk_Build); + + // go through the resources and see if something changed. + IResourceDelta delta = getDelta(project); + if (delta == null) { + // no delta? Same as full build: we do all the steps. + mPackageResources = true; + mConvertToDex = true; + mBuildFinalPackage = true; + } else { + + if (ResourceManager.isAutoBuilding() && AdtPrefs.getPrefs().isLintOnSave()) { + // Check for errors on save/build, if enabled + LintDeltaProcessor.create().process(delta); + } + + PatternBasedDeltaVisitor dv = new PatternBasedDeltaVisitor( + project, project, + "POST:Main"); + + ChangedFileSet manifestCfs = ChangedFileSetHelper.getMergedManifestCfs(project); + dv.addSet(manifestCfs); + + ChangedFileSet resCfs = ChangedFileSetHelper.getResCfs(project); + dv.addSet(resCfs); + + ChangedFileSet androidCodeCfs = ChangedFileSetHelper.getCodeCfs(project); + dv.addSet(androidCodeCfs); + + ChangedFileSet javaResCfs = ChangedFileSetHelper.getJavaResCfs(project); + dv.addSet(javaResCfs); + dv.addSet(ChangedFileSetHelper.NATIVE_LIBS); + + delta.accept(dv); + + // save the state + mPackageResources |= dv.checkSet(manifestCfs) || dv.checkSet(resCfs); + + mConvertToDex |= dv.checkSet(androidCodeCfs); + + mBuildFinalPackage |= dv.checkSet(javaResCfs) || + dv.checkSet(ChangedFileSetHelper.NATIVE_LIBS); + } + + // check the libraries + if (libProjects.size() > 0) { + for (IProject libProject : libProjects) { + delta = getDelta(libProject); + if (delta != null) { + PatternBasedDeltaVisitor visitor = new PatternBasedDeltaVisitor( + project, libProject, + "POST:Lib"); + + ChangedFileSet libResCfs = ChangedFileSetHelper.getFullResCfs( + libProject); + visitor.addSet(libResCfs); + visitor.addSet(ChangedFileSetHelper.NATIVE_LIBS); + // FIXME: add check on the library.jar? + + delta.accept(visitor); + + mPackageResources |= visitor.checkSet(libResCfs); + mBuildFinalPackage |= visitor.checkSet( + ChangedFileSetHelper.NATIVE_LIBS); + } + } + } + + // also go through the delta for all the referenced projects + final int referencedCount = referencedJavaProjects.size(); + for (int i = 0 ; i < referencedCount; i++) { + IJavaProject referencedJavaProject = referencedJavaProjects.get(i); + delta = getDelta(referencedJavaProject.getProject()); + if (delta != null) { + IProject referencedProject = referencedJavaProject.getProject(); + PatternBasedDeltaVisitor visitor = new PatternBasedDeltaVisitor( + project, referencedProject, + "POST:RefedProject"); + + ChangedFileSet javaResCfs = ChangedFileSetHelper.getJavaResCfs(referencedProject); + visitor.addSet(javaResCfs); + + ChangedFileSet bytecodeCfs = ChangedFileSetHelper.getByteCodeCfs(referencedProject); + visitor.addSet(bytecodeCfs); + + delta.accept(visitor); + + // save the state + mConvertToDex |= visitor.checkSet(bytecodeCfs); + mBuildFinalPackage |= visitor.checkSet(javaResCfs); + } + } + } + + // store the build status in the persistent storage + saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex); + saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources); + saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage); + + // Top level check to make sure the build can move forward. Only do this after recording + // delta changes. + abortOnBadSetup(javaProject, projectState); + + // Get the output stream. Since the builder is created for the life of the + // project, they can be kept around. + if (mOutStream == null) { + mOutStream = new AndroidPrintStream(project, null /*prefix*/, + AdtPlugin.getOutStream()); + mErrStream = new AndroidPrintStream(project, null /*prefix*/, + AdtPlugin.getOutStream()); + } + + // remove older packaging markers. + removeMarkersFromContainer(javaProject.getProject(), AdtConstants.MARKER_PACKAGING); + + // finished with the common init and tests. Special case of the library. + if (isLibrary) { + // check the jar output file is present, if not create it. + IFile jarIFile = androidOutputFolder.getFile( + project.getName().toLowerCase() + SdkConstants.DOT_JAR); + if (mConvertToDex == false && jarIFile.exists() == false) { + mConvertToDex = true; + } + + // also update the crunch cache always since aapt does it smartly only + // on the files that need it. + if (DEBUG_LOG) { + AdtPlugin.log(IStatus.INFO, "%s running crunch!", project.getName()); + } + BuildHelper helper = new BuildHelper( + projectState, + mBuildToolInfo, + mOutStream, mErrStream, + false /*jumbo mode doesn't matter here*/, + false /*dex merger doesn't matter here*/, + true /*debugMode*/, + AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE, + mResourceMarker); + updateCrunchCache(project, helper); + + // refresh recursively bin/res folder + resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); + + if (mConvertToDex) { // in this case this means some class files changed and + // we need to update the jar file. + if (DEBUG_LOG) { + AdtPlugin.log(IStatus.INFO, "%s updating jar!", project.getName()); + } + + // resource to the AndroidManifest.xml file + IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML); + String appPackage = AndroidManifest.getPackage(new IFileWrapper(manifestFile)); + + IFolder javaOutputFolder = BaseProjectHelper.getJavaOutputFolder(project); + + writeLibraryPackage(jarIFile, project, appPackage, javaOutputFolder); + saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex = false); + + // refresh the bin folder content with no recursion to update the library + // jar file. + androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor); + + // Also update the projects. The only way to force recompile them is to + // reset the library container. + List<ProjectState> parentProjects = projectState.getParentProjects(); + LibraryClasspathContainerInitializer.updateProject(parentProjects); + } + + return allRefProjects; + } + + // Check to see if we're going to launch or export. If not, we can skip + // the packaging and dexing process. + if (!args.containsKey(POST_C_REQUESTED) + && AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) { + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, + Messages.Skip_Post_Compiler); + return allRefProjects; + } else { + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, + Messages.Start_Full_Post_Compiler); + } + + // first thing we do is check that the SDK directory has been setup. + String osSdkFolder = AdtPlugin.getOsSdkFolder(); + + if (osSdkFolder.length() == 0) { + // this has already been checked in the precompiler. Therefore, + // while we do have to cancel the build, we don't have to return + // any error or throw anything. + return allRefProjects; + } + + // do some extra check, in case the output files are not present. This + // will force to recreate them. + IResource tmp = null; + + if (mPackageResources == false) { + // check the full resource package + tmp = androidOutputFolder.findMember(AdtConstants.FN_RESOURCES_AP_); + if (tmp == null || tmp.exists() == false) { + mPackageResources = true; + } + } + + // check classes.dex is present. If not we force to recreate it. + if (mConvertToDex == false) { + tmp = androidOutputFolder.findMember(SdkConstants.FN_APK_CLASSES_DEX); + if (tmp == null || tmp.exists() == false) { + mConvertToDex = true; + } + } + + // also check the final file(s)! + String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/); + if (mBuildFinalPackage == false) { + tmp = androidOutputFolder.findMember(finalPackageName); + if (tmp == null || (tmp instanceof IFile && + tmp.exists() == false)) { + String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName); + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg); + mBuildFinalPackage = true; + } + } + + // at this point we know if we need to recreate the temporary apk + // or the dex file, but we don't know if we simply need to recreate them + // because they are missing + + // refresh the output directory first + IContainer ic = androidOutputFolder.getParent(); + if (ic != null) { + ic.refreshLocal(IResource.DEPTH_ONE, monitor); + } + + // we need to test all three, as we may need to make the final package + // but not the intermediary ones. + if (mPackageResources || mConvertToDex || mBuildFinalPackage) { + String forceJumboStr = projectState.getProperty( + AdtConstants.DEX_OPTIONS_FORCEJUMBO); + Boolean jumbo = Boolean.valueOf(forceJumboStr); + + String dexMergerStr = projectState.getProperty( + AdtConstants.DEX_OPTIONS_DISABLE_MERGER); + Boolean dexMerger = Boolean.valueOf(dexMergerStr); + + BuildHelper helper = new BuildHelper( + projectState, + mBuildToolInfo, + mOutStream, mErrStream, + jumbo.booleanValue(), + dexMerger.booleanValue(), + true /*debugMode*/, + AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE, + mResourceMarker); + + IPath androidBinLocation = androidOutputFolder.getLocation(); + if (androidBinLocation == null) { + markProject(AdtConstants.MARKER_PACKAGING, Messages.Output_Missing, + IMarker.SEVERITY_ERROR); + return allRefProjects; + } + String osAndroidBinPath = androidBinLocation.toOSString(); + + // resource to the AndroidManifest.xml file + IFile manifestFile = androidOutputFolder.getFile( + SdkConstants.FN_ANDROID_MANIFEST_XML); + + if (manifestFile == null || manifestFile.exists() == false) { + // mark project and exit + String msg = String.format(Messages.s_File_Missing, + SdkConstants.FN_ANDROID_MANIFEST_XML); + markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); + return allRefProjects; + } + + // Remove the old .apk. + // This make sure that if the apk is corrupted, then dx (which would attempt + // to open it), will not fail. + String osFinalPackagePath = osAndroidBinPath + File.separator + finalPackageName; + File finalPackage = new File(osFinalPackagePath); + + // if delete failed, this is not really a problem, as the final package generation + // handle already present .apk, and if that one failed as well, the user will be + // notified. + finalPackage.delete(); + + // Check if we need to package the resources. + if (mPackageResources) { + // also update the crunch cache always since aapt does it smartly only + // on the files that need it. + if (DEBUG_LOG) { + AdtPlugin.log(IStatus.INFO, "%s running crunch!", project.getName()); + } + if (updateCrunchCache(project, helper) == false) { + return allRefProjects; + } + + // refresh recursively bin/res folder + resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); + + if (DEBUG_LOG) { + AdtPlugin.log(IStatus.INFO, "%s packaging resources!", project.getName()); + } + // remove some aapt_package only markers. + removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE); + + try { + helper.packageResources(manifestFile, libProjects, null /*resfilter*/, + 0 /*versionCode */, osAndroidBinPath, + AdtConstants.FN_RESOURCES_AP_); + } catch (AaptExecException e) { + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, + e.getMessage(), IMarker.SEVERITY_ERROR); + return allRefProjects; + } catch (AaptResultException e) { + // attempt to parse the error output + String[] aaptOutput = e.getOutput(); + boolean parsingError = AaptParser.parseOutput(aaptOutput, project); + + // if we couldn't parse the output we display it in the console. + if (parsingError) { + AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput); + + // if the exec failed, and we couldn't parse the error output (and + // therefore not all files that should have been marked, were marked), + // we put a generic marker on the project and abort. + BaseProjectHelper.markResource(project, + AdtConstants.MARKER_PACKAGING, + Messages.Unparsed_AAPT_Errors, + IMarker.SEVERITY_ERROR); + } + } + + // build has been done. reset the state of the builder + mPackageResources = false; + + // and store it + saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources); + } + + String classesDexPath = osAndroidBinPath + File.separator + + SdkConstants.FN_APK_CLASSES_DEX; + + // then we check if we need to package the .class into classes.dex + if (mConvertToDex) { + if (DEBUG_LOG) { + AdtPlugin.log(IStatus.INFO, "%s running dex!", project.getName()); + } + try { + Collection<String> dxInputPaths = helper.getCompiledCodePaths(); + + helper.executeDx(javaProject, dxInputPaths, classesDexPath); + } catch (DexException e) { + String message = e.getMessage(); + + AdtPlugin.printErrorToConsole(project, message); + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, + message, IMarker.SEVERITY_ERROR); + + Throwable cause = e.getCause(); + + if (cause instanceof NoClassDefFoundError + || cause instanceof NoSuchMethodError) { + AdtPlugin.printErrorToConsole(project, Messages.Incompatible_VM_Warning, + Messages.Requires_1_5_Error); + } + + // dx failed, we return + return allRefProjects; + } + + // build has been done. reset the state of the builder + mConvertToDex = false; + + // and store it + saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex); + } + + // now we need to make the final package from the intermediary apk + // and classes.dex. + // This is the default package with all the resources. + + try { + if (DEBUG_LOG) { + AdtPlugin.log(IStatus.INFO, "%s making final package!", project.getName()); + } + helper.finalDebugPackage( + osAndroidBinPath + File.separator + AdtConstants.FN_RESOURCES_AP_, + classesDexPath, osFinalPackagePath, libProjects, mResourceMarker); + } catch (KeytoolException e) { + String eMessage = e.getMessage(); + + // mark the project with the standard message + String msg = String.format(Messages.Final_Archive_Error_s, eMessage); + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, + IMarker.SEVERITY_ERROR); + + // output more info in the console + AdtPlugin.printErrorToConsole(project, + msg, + String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()), + Messages.ApkBuilder_Update_or_Execute_manually_s, + e.getCommandLine()); + + AdtPlugin.log(e, msg); + + return allRefProjects; + } catch (ApkCreationException e) { + String eMessage = e.getMessage(); + + // mark the project with the standard message + String msg = String.format(Messages.Final_Archive_Error_s, eMessage); + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, + IMarker.SEVERITY_ERROR); + + AdtPlugin.log(e, msg); + } catch (AndroidLocationException e) { + String eMessage = e.getMessage(); + + // mark the project with the standard message + String msg = String.format(Messages.Final_Archive_Error_s, eMessage); + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, + IMarker.SEVERITY_ERROR); + AdtPlugin.log(e, msg); + } catch (NativeLibInJarException e) { + String msg = e.getMessage(); + + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, + msg, IMarker.SEVERITY_ERROR); + + AdtPlugin.printErrorToConsole(project, (Object[]) e.getAdditionalInfo()); + } catch (CoreException e) { + // mark project and return + String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage()); + AdtPlugin.printErrorToConsole(project, msg); + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, + IMarker.SEVERITY_ERROR); + AdtPlugin.log(e, msg); + } catch (DuplicateFileException e) { + String msg1 = String.format( + "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s", + e.getArchivePath(), e.getFile1(), e.getFile2()); + String msg2 = String.format(Messages.Final_Archive_Error_s, msg1); + AdtPlugin.printErrorToConsole(project, msg2); + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg2, + IMarker.SEVERITY_ERROR); + } + + // we are done. + + // refresh the bin folder content with no recursion. + androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor); + + // build has been done. reset the state of the builder + mBuildFinalPackage = false; + + // and store it + saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage); + + // reset the installation manager to force new installs of this project + ApkInstallManager.getInstance().resetInstallationFor(project); + + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(), + "Build Success!"); + } + } catch (AbortBuildException e) { + return allRefProjects; + } catch (Exception exception) { + // try to catch other exception to actually display an error. This will be useful + // if we get an NPE or something so that we can at least notify the user that something + // went wrong. + + // first check if this is a CoreException we threw to cancel the build. + if (exception instanceof CoreException) { + if (((CoreException)exception).getStatus().getSeverity() == IStatus.CANCEL) { + // Project is already marked with an error. Nothing to do + return allRefProjects; + } + } + + String msg = exception.getMessage(); + if (msg == null) { + msg = exception.getClass().getCanonicalName(); + } + + msg = String.format("Unknown error: %1$s", msg); + AdtPlugin.logAndPrintError(exception, project.getName(), msg); + markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); + } + + // Benchmarking end + if (BuildHelper.BENCHMARK_FLAG) { + String msg = "BENCHMARK ADT: Ending PostCompilation. \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$ + ((System.nanoTime() - startBuildTime)/Math.pow(10, 6)) + "ms"; //$NON-NLS-1$ + AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg); + // End Overall Timer + msg = "BENCHMARK ADT: Done with everything! \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$ + (System.nanoTime() - BuildHelper.sStartOverallTime)/Math.pow(10, 6) + "ms"; //$NON-NLS-1$ + AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg); + } + + return allRefProjects; + } + + private static class JarBuilder implements IArchiveBuilder { + + private static Pattern R_PATTERN = Pattern.compile("R(\\$.*)?\\.class"); //$NON-NLS-1$ + private static String BUILD_CONFIG_CLASS = "BuildConfig.class"; //$NON-NLS-1$ + + private final byte[] buffer = new byte[1024]; + private final JarOutputStream mOutputStream; + private final String mAppPackage; + + JarBuilder(JarOutputStream outputStream, String appPackage) { + mOutputStream = outputStream; + mAppPackage = appPackage.replace('.', '/'); + } + + public void addFile(IFile file, IFolder rootFolder) throws ApkCreationException { + // we only package class file from the output folder + if (SdkConstants.EXT_CLASS.equals(file.getFileExtension()) == false) { + return; + } + + IPath packageApp = file.getParent().getFullPath().makeRelativeTo( + rootFolder.getFullPath()); + + String name = file.getName(); + // Ignore the library's R/Manifest/BuildConfig classes. + if (mAppPackage.equals(packageApp.toString()) && + (BUILD_CONFIG_CLASS.equals(name) || + R_PATTERN.matcher(name).matches())) { + return; + } + + IPath path = file.getFullPath().makeRelativeTo(rootFolder.getFullPath()); + try { + addFile(file.getContents(), file.getLocalTimeStamp(), path.toString()); + } catch (ApkCreationException e) { + throw e; + } catch (Exception e) { + throw new ApkCreationException(e, "Failed to add %s", file); + } + } + + @Override + public void addFile(File file, String archivePath) throws ApkCreationException, + SealedApkException, DuplicateFileException { + try { + FileInputStream inputStream = new FileInputStream(file); + long lastModified = file.lastModified(); + addFile(inputStream, lastModified, archivePath); + } catch (ApkCreationException e) { + throw e; + } catch (Exception e) { + throw new ApkCreationException(e, "Failed to add %s", file); + } + } + + private void addFile(InputStream content, long lastModified, String archivePath) + throws IOException, ApkCreationException { + // create the jar entry + JarEntry entry = new JarEntry(archivePath); + entry.setTime(lastModified); + + try { + // add the entry to the jar archive + mOutputStream.putNextEntry(entry); + + // read the content of the entry from the input stream, and write + // it into the archive. + int count; + while ((count = content.read(buffer)) != -1) { + mOutputStream.write(buffer, 0, count); + } + } finally { + try { + if (content != null) { + content.close(); + } + } catch (Exception e) { + throw new ApkCreationException(e, "Failed to close stream"); + } + } + } + } + + /** + * Updates the crunch cache if needed and return true if the build must continue. + */ + private boolean updateCrunchCache(IProject project, BuildHelper helper) { + try { + helper.updateCrunchCache(); + } catch (AaptExecException e) { + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, + e.getMessage(), IMarker.SEVERITY_ERROR); + return false; + } catch (AaptResultException e) { + // attempt to parse the error output + String[] aaptOutput = e.getOutput(); + boolean parsingError = AaptParser.parseOutput(aaptOutput, project); + // if we couldn't parse the output we display it in the console. + if (parsingError) { + AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput); + } + } + + return true; + } + + /** + * Writes the library jar file. + * @param jarIFile the destination file + * @param project the library project + * @param appPackage the library android package + * @param javaOutputFolder the JDT output folder. + */ + private void writeLibraryPackage(IFile jarIFile, IProject project, String appPackage, + IFolder javaOutputFolder) { + + JarOutputStream jos = null; + try { + Manifest manifest = new Manifest(); + Attributes mainAttributes = manifest.getMainAttributes(); + mainAttributes.put(Attributes.Name.CLASS_PATH, "Android ADT"); //$NON-NLS-1$ + mainAttributes.putValue("Created-By", "1.0 (Android)"); //$NON-NLS-1$ //$NON-NLS-2$ + jos = new JarOutputStream( + new FileOutputStream(jarIFile.getLocation().toFile()), manifest); + + JarBuilder jarBuilder = new JarBuilder(jos, appPackage); + + // write the class files + writeClassFilesIntoJar(jarBuilder, javaOutputFolder, javaOutputFolder); + + // now write the standard Java resources from the output folder + ApkBuilder.addSourceFolder(jarBuilder, javaOutputFolder.getLocation().toFile()); + + saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex); + } catch (Exception e) { + AdtPlugin.log(e, "Failed to write jar file %s", jarIFile.getLocation().toOSString()); + } finally { + if (jos != null) { + try { + jos.close(); + } catch (IOException e) { + // pass + } + } + } + } + + private void writeClassFilesIntoJar(JarBuilder builder, IFolder folder, IFolder rootFolder) + throws CoreException, IOException, ApkCreationException { + IResource[] members = folder.members(); + for (IResource member : members) { + if (member.getType() == IResource.FOLDER) { + writeClassFilesIntoJar(builder, (IFolder) member, rootFolder); + } else if (member.getType() == IResource.FILE) { + IFile file = (IFile) member; + builder.addFile(file, rootFolder); + } + } + } + + @Override + protected void startupOnInitialize() { + super.startupOnInitialize(); + + // load the build status. We pass true as the default value to + // force a recompile in case the property was not found + mConvertToDex = loadProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, true); + mPackageResources = loadProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, true); + mBuildFinalPackage = loadProjectBooleanProperty(PROPERTY_BUILD_APK, true); + } + + @Override + protected void abortOnBadSetup( + @NonNull IJavaProject javaProject, + @Nullable ProjectState projectState) throws AbortBuildException, CoreException { + super.abortOnBadSetup(javaProject, projectState); + + IProject iProject = getProject(); + + // do a (hopefully quick) search for Precompiler type markers. Those are always only + // errors. + stopOnMarker(iProject, AdtConstants.MARKER_AAPT_COMPILE, IResource.DEPTH_INFINITE, + false /*checkSeverity*/); + stopOnMarker(iProject, AdtConstants.MARKER_AIDL, IResource.DEPTH_INFINITE, + false /*checkSeverity*/); + stopOnMarker(iProject, AdtConstants.MARKER_RENDERSCRIPT, IResource.DEPTH_INFINITE, + false /*checkSeverity*/); + stopOnMarker(iProject, AdtConstants.MARKER_ANDROID, IResource.DEPTH_ZERO, + false /*checkSeverity*/); + + // do a search for JDT markers. Those can be errors or warnings + stopOnMarker(iProject, IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, + IResource.DEPTH_INFINITE, true /*checkSeverity*/); + stopOnMarker(iProject, IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, + IResource.DEPTH_INFINITE, true /*checkSeverity*/); + } +} |