/* * 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.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); // 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(project, mOutStream, mErrStream, false /*jumbo mode 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 b = Boolean.valueOf(forceJumboStr); BuildHelper helper = new BuildHelper(project, mOutStream, mErrStream, b.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(IJavaProject javaProject) throws AbortBuildException { super.abortOnBadSetup(javaProject); 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*/); } }