| /* |
| * 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*/); |
| } |
| } |