aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java
diff options
context:
space:
mode:
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.java946
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*/);
+ }
+}