diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java | 1401 |
1 files changed, 1401 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java new file mode 100644 index 000000000..0d9ee4897 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java @@ -0,0 +1,1401 @@ +/* + * 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.common.xml.ManifestData; +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.build.AaptParser; +import com.android.ide.eclipse.adt.internal.build.AidlProcessor; +import com.android.ide.eclipse.adt.internal.build.Messages; +import com.android.ide.eclipse.adt.internal.build.RenderScriptLauncher; +import com.android.ide.eclipse.adt.internal.build.RsSourceChangeHandler; +import com.android.ide.eclipse.adt.internal.build.SourceProcessor; +import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder.AbortBuildException; +import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient; +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.AndroidManifestHelper; +import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; +import com.android.ide.eclipse.adt.internal.project.FixLaunchConfig; +import com.android.ide.eclipse.adt.internal.project.ProjectHelper; +import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.BasicXmlErrorListener; +import com.android.ide.eclipse.adt.internal.resources.manager.IdeScanningContext; +import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; +import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; +import com.android.ide.eclipse.adt.internal.sdk.AdtManifestMergeCallback; +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.ide.eclipse.adt.io.IFolderWrapper; +import com.android.io.StreamException; +import com.android.manifmerger.ManifestMerger; +import com.android.manifmerger.MergerLog; +import com.android.sdklib.AndroidVersion; +import com.android.sdklib.BuildToolInfo; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.build.RenderScriptChecker; +import com.android.sdklib.build.RenderScriptProcessor; +import com.android.sdklib.internal.build.BuildConfigGenerator; +import com.android.sdklib.internal.build.SymbolLoader; +import com.android.sdklib.internal.build.SymbolWriter; +import com.android.sdklib.internal.project.ProjectProperties; +import com.android.sdklib.io.FileOp; +import com.android.sdklib.repository.FullRevision; +import com.android.utils.ILogger; +import com.android.utils.Pair; +import com.android.xml.AndroidManifest; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; + +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.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +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.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.xml.sax.SAXException; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.ParserConfigurationException; + +/** + * Pre Java Compiler. + * This incremental builder performs 2 tasks: + * <ul> + * <li>compiles the resources located in the res/ folder, along with the + * AndroidManifest.xml file into the R.java class.</li> + * <li>compiles any .aidl files into a corresponding java file.</li> + * </ul> + * + */ +public class PreCompilerBuilder 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.PreCompilerBuilder"; //$NON-NLS-1$ + + /** Flag to pass to PreCompiler builder that the build is a release build. + */ + public final static String RELEASE_REQUESTED = "android.releaseBuild"; //$NON-NLS-1$ + + private static final String PROPERTY_PACKAGE = "manifestPackage"; //$NON-NLS-1$ + private static final String PROPERTY_MERGE_MANIFEST = "mergeManifest"; //$NON-NLS-1$ + private static final String PROPERTY_COMPILE_RESOURCES = "compileResources"; //$NON-NLS-1$ + private static final String PROPERTY_COMPILE_BUILDCONFIG = "createBuildConfig"; //$NON-NLS-1$ + private static final String PROPERTY_BUILDCONFIG_MODE = "buildConfigMode"; //$NON-NLS-1$ + + private static final boolean MANIFEST_MERGER_ENABLED_DEFAULT = false; + private static final String MANIFEST_MERGER_PROPERTY = "manifestmerger.enabled"; //$NON-NLS-1$ + + /** Merge Manifest Flag. Computed from resource delta, reset after action is taken. + * Stored persistently in the project. */ + private boolean mMustMergeManifest = false; + /** Resource compilation Flag. Computed from resource delta, reset after action is taken. + * Stored persistently in the project. */ + private boolean mMustCompileResources = false; + /** BuildConfig Flag. Computed from resource delta, reset after action is taken. + * Stored persistently in the project. */ + private boolean mMustCreateBuildConfig = false; + /** BuildConfig last more Flag. Computed from resource delta, reset after action is taken. + * Stored persistently in the project. */ + private boolean mLastBuildConfigMode; + + /** cache of the java package defined in the manifest */ + private String mManifestPackage; + + /** Output folder for generated Java File. Created on the Builder init + * @see #startupOnInitialize() + */ + private IFolder mGenFolder; + + /** + * Progress monitor used at the end of every build to refresh the content of the 'gen' folder + * and set the generated files as derived. + */ + private DerivedProgressMonitor mDerivedProgressMonitor; + + private AidlProcessor mAidlProcessor; + private RsSourceChangeHandler mRenderScriptSourceChangeHandler; + + /** + * Progress monitor waiting the end of the process to set a persistent value + * in a file. This is typically used in conjunction with <code>IResource.refresh()</code>, + * since this call is asynchronous, and we need to wait for it to finish for the file + * to be known by eclipse, before we can call <code>resource.setPersistentProperty</code> on + * a new file. + */ + private static class DerivedProgressMonitor implements IProgressMonitor { + private boolean mCancelled = false; + private boolean mDone = false; + private final IFolder mGenFolder; + + public DerivedProgressMonitor(IFolder genFolder) { + mGenFolder = genFolder; + } + + void reset() { + mDone = false; + } + + @Override + public void beginTask(String name, int totalWork) { + } + + @Override + public void done() { + if (mDone == false) { + mDone = true; + processChildrenOf(mGenFolder); + } + } + + private void processChildrenOf(IFolder folder) { + IResource[] list; + try { + list = folder.members(); + } catch (CoreException e) { + return; + } + + for (IResource member : list) { + if (member.exists()) { + if (member.getType() == IResource.FOLDER) { + processChildrenOf((IFolder) member); + } + + try { + member.setDerived(true, new NullProgressMonitor()); + } catch (CoreException e) { + // This really shouldn't happen since we check that the resource + // exist. + // Worst case scenario, the resource isn't marked as derived. + } + } + } + } + + @Override + public void internalWorked(double work) { + } + + @Override + public boolean isCanceled() { + return mCancelled; + } + + @Override + public void setCanceled(boolean value) { + mCancelled = value; + } + + @Override + public void setTaskName(String name) { + } + + @Override + public void subTask(String name) { + } + + @Override + public void worked(int work) { + } + } + + public PreCompilerBuilder() { + super(); + } + + // 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(PRE)", project.getName()); + } + + // For the PreCompiler, only the library projects are considered Referenced projects, + // as only those projects have an impact on what is generated by this builder. + IProject[] result = null; + + IFolder resOutFolder = null; + + try { + assert mDerivedProgressMonitor != null; + + mDerivedProgressMonitor.reset(); + + // 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(); + + IAndroidTarget projectTarget = projectState.getTarget(); + + // get the libraries + List<IProject> libProjects = projectState.getFullLibraryProjects(); + result = libProjects.toArray(new IProject[libProjects.size()]); + + IJavaProject javaProject = JavaCore.create(project); + + // Top level check to make sure the build can move forward. + abortOnBadSetup(javaProject, projectState); + + // now we need to get the classpath list + List<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths(javaProject); + + IFolder androidOutputFolder = BaseProjectHelper.getAndroidOutputFolder(project); + + resOutFolder = getResOutFolder(androidOutputFolder); + + setupSourceProcessors(javaProject, projectState, sourceFolderPathList, + androidOutputFolder); + + PreCompilerDeltaVisitor dv = null; + String javaPackage = null; + String minSdkVersion = null; + + if (kind == FULL_BUILD) { + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, + Messages.Start_Full_Pre_Compiler); + + if (DEBUG_LOG) { + AdtPlugin.log(IStatus.INFO, "%s full build!", project.getName()); + } + + // do some clean up. + doClean(project, monitor); + + mMustMergeManifest = true; + mMustCompileResources = true; + mMustCreateBuildConfig = true; + + mAidlProcessor.prepareFullBuild(project); + mRenderScriptSourceChangeHandler.prepareFullBuild(); + } else { + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, + Messages.Start_Inc_Pre_Compiler); + + // Go through the resources and see if something changed. + // Even if the mCompileResources flag is true from a previously aborted + // build, we need to go through the Resource delta to get a possible + // list of aidl files to compile/remove. + IResourceDelta delta = getDelta(project); + if (delta == null) { + mMustCompileResources = true; + + mAidlProcessor.prepareFullBuild(project); + mRenderScriptSourceChangeHandler.prepareFullBuild(); + } else { + dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList, + mAidlProcessor.getChangeHandler(), + mRenderScriptSourceChangeHandler); + delta.accept(dv); + + // Check to see if Manifest.xml, Manifest.java, or R.java have changed: + mMustCompileResources |= dv.getCompileResources(); + mMustMergeManifest |= dv.hasManifestChanged(); + + // Notify the ResourceManager: + ResourceManager resManager = ResourceManager.getInstance(); + + if (ResourceManager.isAutoBuilding()) { + ProjectResources projectResources = resManager.getProjectResources(project); + + IdeScanningContext context = new IdeScanningContext(projectResources, + project, true); + + boolean wasCleared = projectResources.ensureInitialized(); + + if (!wasCleared) { + resManager.processDelta(delta, context); + } + + // Check whether this project or its dependencies (libraries) have + // resources that need compilation + if (wasCleared || context.needsFullAapt()) { + mMustCompileResources = true; + + // Must also call markAaptRequested on the project to not just + // store "aapt required" on this project, but also on any projects + // depending on this project if it's a library project + ResourceManager.markAaptRequested(project); + } + + // Update error markers in the source editor + if (!mMustCompileResources) { + context.updateMarkers(false /* async */); + } + } // else: already processed the deltas in ResourceManager's IRawDeltaListener + + mAidlProcessor.doneVisiting(project); + + // get the java package from the visitor + javaPackage = dv.getManifestPackage(); + minSdkVersion = dv.getMinSdkVersion(); + } + } + + // Has anyone marked this project as needing aapt? Typically done when + // one of the library projects this project depends on has changed + mMustCompileResources |= ResourceManager.isAaptRequested(project); + + // if the main manifest didn't change, then we check for the library + // ones (will trigger manifest merging too) + if (libProjects.size() > 0) { + for (IProject libProject : libProjects) { + IResourceDelta delta = getDelta(libProject); + if (delta != null) { + PatternBasedDeltaVisitor visitor = new PatternBasedDeltaVisitor( + project, libProject, + "PRE:LibManifest"); //$NON-NLS-1$ + visitor.addSet(ChangedFileSetHelper.MANIFEST); + + ChangedFileSet textSymbolCFS = null; + if (isLibrary == false) { + textSymbolCFS = ChangedFileSetHelper.getTextSymbols( + libProject); + visitor.addSet(textSymbolCFS); + } + + delta.accept(visitor); + + mMustMergeManifest |= visitor.checkSet(ChangedFileSetHelper.MANIFEST); + + if (textSymbolCFS != null) { + mMustCompileResources |= visitor.checkSet(textSymbolCFS); + } + + // no need to test others if we have all flags at true. + if (mMustMergeManifest && + (mMustCompileResources || textSymbolCFS == null)) { + break; + } + } + } + } + + // store the build status in the persistent storage + saveProjectBooleanProperty(PROPERTY_MERGE_MANIFEST, mMustMergeManifest); + saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources); + saveProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, mMustCreateBuildConfig); + + // if there was some XML errors, we just return w/o doing + // anything since we've put some markers in the files anyway. + if (dv != null && dv.mXmlError) { + AdtPlugin.printErrorToConsole(project, Messages.Xml_Error); + + return result; + } + + if (projectState.getRenderScriptSupportMode()) { + FullRevision minBuildToolsRev = new FullRevision(19,0,3); + if (mBuildToolInfo.getRevision().compareTo(minBuildToolsRev) == -1) { + String msg = "RenderScript support mode requires Build-Tools 19.0.3 or later."; + AdtPlugin.printErrorToConsole(project, msg); + markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); + + return result; + } + } + + // get the manifest file + IFile manifestFile = ProjectHelper.getManifest(project); + + if (manifestFile == null) { + String msg = String.format(Messages.s_File_Missing, + SdkConstants.FN_ANDROID_MANIFEST_XML); + AdtPlugin.printErrorToConsole(project, msg); + markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); + + return result; + + // TODO: document whether code below that uses manifest (which is now guaranteed + // to be null) will actually be executed or not. + } + + // lets check the XML of the manifest first, if that hasn't been done by the + // resource delta visitor yet. + if (dv == null || dv.getCheckedManifestXml() == false) { + BasicXmlErrorListener errorListener = new BasicXmlErrorListener(); + try { + ManifestData parser = AndroidManifestHelper.parseUnchecked( + new IFileWrapper(manifestFile), + true /*gather data*/, + errorListener); + + if (errorListener.mHasXmlError == true) { + // There was an error in the manifest, its file has been marked + // by the XmlErrorHandler. The stopBuild() call below will abort + // this with an exception. + String msg = String.format(Messages.s_Contains_Xml_Error, + SdkConstants.FN_ANDROID_MANIFEST_XML); + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg); + markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); + + return result; + } + + // Get the java package from the parser. + // This can be null if the parsing failed because the resource is out of sync, + // in which case the error will already have been logged anyway. + if (parser != null) { + javaPackage = parser.getPackage(); + minSdkVersion = parser.getMinSdkVersionString(); + } + } catch (StreamException e) { + handleStreamException(e); + + return result; + } catch (ParserConfigurationException e) { + String msg = String.format( + "Bad parser configuration for %s: %s", + manifestFile.getFullPath(), + e.getMessage()); + + handleException(e, msg); + return result; + + } catch (SAXException e) { + String msg = String.format( + "Parser exception for %s: %s", + manifestFile.getFullPath(), + e.getMessage()); + + handleException(e, msg); + return result; + } catch (IOException e) { + String msg = String.format( + "I/O error for %s: %s", + manifestFile.getFullPath(), + e.getMessage()); + + handleException(e, msg); + return result; + } + } + + int minSdkValue = -1; + + if (minSdkVersion != null) { + try { + minSdkValue = Integer.parseInt(minSdkVersion); + } catch (NumberFormatException e) { + // it's ok, it means minSdkVersion contains a (hopefully) valid codename. + } + + AndroidVersion targetVersion = projectTarget.getVersion(); + + // remove earlier marker from the manifest + removeMarkersFromResource(manifestFile, AdtConstants.MARKER_ADT); + + if (minSdkValue != -1) { + String codename = targetVersion.getCodename(); + if (codename != null) { + // integer minSdk when the target is a preview => fatal error + String msg = String.format( + "Platform %1$s is a preview and requires application manifest to set %2$s to '%1$s'", + codename, AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION); + AdtPlugin.printErrorToConsole(project, msg); + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, + msg, IMarker.SEVERITY_ERROR); + return result; + } else if (minSdkValue > targetVersion.getApiLevel()) { + // integer minSdk is too high for the target => warning + String msg = String.format( + "Attribute %1$s (%2$d) is higher than the project target API level (%3$d)", + AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, + minSdkValue, targetVersion.getApiLevel()); + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg); + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, + msg, IMarker.SEVERITY_WARNING); + } + } else { + // looks like the min sdk is a codename, check it matches the codename + // of the platform + String codename = targetVersion.getCodename(); + if (codename == null) { + // platform is not a preview => fatal error + String msg = String.format( + "Manifest attribute '%1$s' is set to '%2$s'. Integer is expected.", + AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, minSdkVersion); + AdtPlugin.printErrorToConsole(project, msg); + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, + msg, IMarker.SEVERITY_ERROR); + return result; + } else if (codename.equals(minSdkVersion) == false) { + // platform and manifest codenames don't match => fatal error. + String msg = String.format( + "Value of manifest attribute '%1$s' does not match platform codename '%2$s'", + AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, codename); + AdtPlugin.printErrorToConsole(project, msg); + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, + msg, IMarker.SEVERITY_ERROR); + return result; + } + + // if we get there, the minSdkVersion is a codename matching the target + // platform codename. In this case we set minSdkValue to the previous API + // level, as it's used by source processors. + minSdkValue = targetVersion.getApiLevel(); + } + } else if (projectTarget.getVersion().isPreview()) { + // else the minSdkVersion is not set but we are using a preview target. + // Display an error + String codename = projectTarget.getVersion().getCodename(); + String msg = String.format( + "Platform %1$s is a preview and requires application manifests to set %2$s to '%1$s'", + codename, AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION); + AdtPlugin.printErrorToConsole(project, msg); + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, msg, + IMarker.SEVERITY_ERROR); + return result; + } + + if (javaPackage == null || javaPackage.length() == 0) { + // looks like the AndroidManifest file isn't valid. + String msg = String.format(Messages.s_Doesnt_Declare_Package_Error, + SdkConstants.FN_ANDROID_MANIFEST_XML); + AdtPlugin.printErrorToConsole(project, msg); + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, + msg, IMarker.SEVERITY_ERROR); + + return result; + } else if (javaPackage.indexOf('.') == -1) { + // The application package name does not contain 2+ segments! + String msg = String.format( + "Application package '%1$s' must have a minimum of 2 segments.", + SdkConstants.FN_ANDROID_MANIFEST_XML); + AdtPlugin.printErrorToConsole(project, msg); + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, + msg, IMarker.SEVERITY_ERROR); + + return result; + } + + // at this point we have the java package. We need to make sure it's not a different + // package than the previous one that were built. + if (javaPackage.equals(mManifestPackage) == false) { + // The manifest package has changed, the user may want to update + // the launch configuration + if (mManifestPackage != null) { + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, + Messages.Checking_Package_Change); + + FixLaunchConfig flc = new FixLaunchConfig(project, mManifestPackage, + javaPackage); + flc.start(); + } + + // record the new manifest package, and save it. + mManifestPackage = javaPackage; + saveProjectStringProperty(PROPERTY_PACKAGE, mManifestPackage); + + // force a clean + doClean(project, monitor); + mMustMergeManifest = true; + mMustCompileResources = true; + mMustCreateBuildConfig = true; + mAidlProcessor.prepareFullBuild(project); + mRenderScriptSourceChangeHandler.prepareFullBuild(); + + saveProjectBooleanProperty(PROPERTY_MERGE_MANIFEST, mMustMergeManifest); + saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources); + saveProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, mMustCreateBuildConfig); + } + + try { + handleBuildConfig(args); + } catch (IOException e) { + handleException(e, "Failed to create BuildConfig class"); + return result; + } + + // merge the manifest + if (mMustMergeManifest) { + boolean enabled = MANIFEST_MERGER_ENABLED_DEFAULT; + String propValue = projectState.getProperty(MANIFEST_MERGER_PROPERTY); + if (propValue != null) { + enabled = Boolean.valueOf(propValue); + } + + if (mergeManifest(androidOutputFolder, libProjects, enabled) == false) { + return result; + } + } + + List<File> libProjectsOut = new ArrayList<File>(libProjects.size()); + for (IProject libProject : libProjects) { + libProjectsOut.add( + BaseProjectHelper.getAndroidOutputFolder(libProject) + .getLocation().toFile()); + } + + // run the source processors + int processorStatus = SourceProcessor.COMPILE_STATUS_NONE; + + + try { + processorStatus |= mAidlProcessor.compileFiles(this, + project, projectTarget, sourceFolderPathList, + libProjectsOut, monitor); + } catch (Throwable t) { + handleException(t, "Failed to run aidl. Check workspace log for detail."); + return result; + } + + try { + processorStatus |= compileRs(minSdkValue, projectState, androidOutputFolder, + resOutFolder, monitor); + } catch (Throwable t) { + handleException(t, "Failed to run renderscript. Check workspace log for detail."); + return result; + } + + // if a processor created some resources file, force recompilation of the resources. + if ((processorStatus & SourceProcessor.COMPILE_STATUS_RES) != 0) { + mMustCompileResources = true; + // save the current state before attempting the compilation + saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources); + } + + // handle the resources, after the processors are run since some (renderscript) + // generate resources. + boolean compiledTheResources = mMustCompileResources; + if (mMustCompileResources) { + if (DEBUG_LOG) { + AdtPlugin.log(IStatus.INFO, "%s compiling resources!", project.getName()); + } + + IFile proguardFile = null; + if (projectState.getProperty(ProjectProperties.PROPERTY_PROGUARD_CONFIG) != null) { + proguardFile = androidOutputFolder.getFile(AdtConstants.FN_AAPT_PROGUARD); + } + + handleResources(project, javaPackage, projectTarget, manifestFile, resOutFolder, + libProjects, isLibrary, proguardFile); + } + + if (processorStatus == SourceProcessor.COMPILE_STATUS_NONE && + compiledTheResources == false) { + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, + Messages.Nothing_To_Compile); + } + } catch (AbortBuildException e) { + return result; + } finally { + // refresh the 'gen' source folder. Once this is done with the custom progress + // monitor to mark all new files as derived + mGenFolder.refreshLocal(IResource.DEPTH_INFINITE, mDerivedProgressMonitor); + if (resOutFolder != null) { + resOutFolder.refreshLocal(IResource.DEPTH_INFINITE, mDerivedProgressMonitor); + } + } + + return result; + } + + private IFolder getResOutFolder(IFolder androidOutputFolder) { + return androidOutputFolder.getFolder(AdtConstants.WS_BIN_RELATIVE_BC); + } + + @Override + protected void clean(IProgressMonitor monitor) throws CoreException { + super.clean(monitor); + + if (DEBUG_LOG) { + AdtPlugin.log(IStatus.INFO, "%s CLEAN(PRE)", getProject().getName()); + } + + doClean(getProject(), monitor); + if (mGenFolder != null) { + mGenFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); + } + } + + private void doClean(IProject project, IProgressMonitor monitor) throws CoreException { + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, + Messages.Removing_Generated_Classes); + + // remove all the derived resources from the 'gen' source folder. + if (mGenFolder != null && mGenFolder.exists()) { + // gen folder should not be derived, but previous version could set it to derived + // so we make sure this isn't the case (or it'll get deleted by the clean) + mGenFolder.setDerived(false, monitor); + + removeDerivedResources(mGenFolder, monitor); + } + + // Clear the project of the generic markers + removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_COMPILE); + removeMarkersFromContainer(project, AdtConstants.MARKER_XML); + removeMarkersFromContainer(project, AdtConstants.MARKER_AIDL); + removeMarkersFromContainer(project, AdtConstants.MARKER_RENDERSCRIPT); + removeMarkersFromContainer(project, AdtConstants.MARKER_MANIFMERGER); + removeMarkersFromContainer(project, AdtConstants.MARKER_ANDROID); + + // Also clean up lint + EclipseLintClient.clearMarkers(project); + + // clean the project repo + ProjectResources res = ResourceManager.getInstance().getProjectResources(project); + res.clear(); + } + + @Override + protected void startupOnInitialize() { + try { + super.startupOnInitialize(); + + IProject project = getProject(); + + // load the previous IFolder and java package. + mManifestPackage = loadProjectStringProperty(PROPERTY_PACKAGE); + + // get the source folder in which all the Java files are created + mGenFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES); + mDerivedProgressMonitor = new DerivedProgressMonitor(mGenFolder); + + // Load the current compile flags. We ask for true if not found to force a recompile. + mMustMergeManifest = loadProjectBooleanProperty(PROPERTY_MERGE_MANIFEST, true); + mMustCompileResources = loadProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, true); + mMustCreateBuildConfig = loadProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, true); + Boolean v = ProjectHelper.loadBooleanProperty(project, PROPERTY_BUILDCONFIG_MODE); + if (v == null) { + // no previous build config mode? force regenerate + mMustCreateBuildConfig = true; + } else { + mLastBuildConfigMode = v; + } + + } catch (Throwable throwable) { + AdtPlugin.log(throwable, "Failed to finish PrecompilerBuilder#startupOnInitialize()"); + } + } + + private void setupSourceProcessors(@NonNull IJavaProject javaProject, + @NonNull ProjectState projectState, + @NonNull List<IPath> sourceFolderPathList, + @NonNull IFolder androidOutputFolder) { + if (mAidlProcessor == null) { + mAidlProcessor = new AidlProcessor(javaProject, mBuildToolInfo, mGenFolder); + } else { + mAidlProcessor.setBuildToolInfo(mBuildToolInfo); + } + + List<File> sourceFolders = Lists.newArrayListWithCapacity(sourceFolderPathList.size()); + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + + for (IPath path : sourceFolderPathList) { + IResource resource = root.findMember(path); + if (resource != null && resource.exists() && resource.getType() == IResource.FOLDER) { + IPath fullPath = resource.getLocation(); + if (fullPath != null) { + sourceFolders.add(fullPath.toFile()); + } + } + } + + RenderScriptChecker checker = new RenderScriptChecker(sourceFolders, + androidOutputFolder.getLocation().toFile()); + mRenderScriptSourceChangeHandler = new RsSourceChangeHandler(checker); + } + + private int compileRs(int minSdkValue, + @NonNull ProjectState projectState, + @NonNull IFolder androidOutputFolder, + @NonNull IFolder resOutFolder, + @NonNull IProgressMonitor monitor) + throws IOException, InterruptedException { + if (!mRenderScriptSourceChangeHandler.mustCompile()) { + return SourceProcessor.COMPILE_STATUS_NONE; + } + + RenderScriptChecker checker = mRenderScriptSourceChangeHandler.getChecker(); + + List<File> inputs = checker.findInputFiles(); + List<File> importFolders = checker.getSourceFolders(); + File buildFolder = androidOutputFolder.getLocation().toFile(); + + + // get the renderscript target + int rsTarget = minSdkValue == -1 ? 11 : minSdkValue; + String rsTargetStr = projectState.getProperty(ProjectProperties.PROPERTY_RS_TARGET); + if (rsTargetStr != null) { + try { + rsTarget = Integer.parseInt(rsTargetStr); + } catch (NumberFormatException e) { + handleException(e, String.format( + "Property %s is not an integer.", + ProjectProperties.PROPERTY_RS_TARGET)); + return SourceProcessor.COMPILE_STATUS_NONE; + } + } + + RenderScriptProcessor processor = new RenderScriptProcessor( + inputs, + importFolders, + buildFolder, + mGenFolder.getLocation().toFile(), + resOutFolder.getLocation().toFile(), + new File(buildFolder, SdkConstants.FD_RS_OBJ), + new File(buildFolder, SdkConstants.FD_RS_LIBS), + mBuildToolInfo, + rsTarget, + false /*debugBuild, always false for now*/, + 3, + projectState.getRenderScriptSupportMode()); + + // clean old dependency files fiest + checker.cleanDependencies(); + + // then clean old output files + processor.cleanOldOutput(checker.getOldOutputs()); + + RenderScriptLauncher launcher = new RenderScriptLauncher( + getProject(), + mGenFolder, + resOutFolder, + monitor, + AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE /*verbose*/); + + // and run the build + processor.build(launcher); + + return SourceProcessor.COMPILE_STATUS_CODE | SourceProcessor.COMPILE_STATUS_RES; + } + + @SuppressWarnings("deprecation") + private void handleBuildConfig(@SuppressWarnings("rawtypes") Map args) + throws IOException, CoreException { + boolean debugMode = !args.containsKey(RELEASE_REQUESTED); + + BuildConfigGenerator generator = new BuildConfigGenerator( + mGenFolder.getLocation().toOSString(), mManifestPackage, debugMode); + + if (mMustCreateBuildConfig == false) { + // check the file is present. + IFolder folder = getGenManifestPackageFolder(); + if (folder.exists(new Path(BuildConfigGenerator.BUILD_CONFIG_NAME)) == false) { + mMustCreateBuildConfig = true; + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(), + String.format("Class %1$s is missing!", + BuildConfigGenerator.BUILD_CONFIG_NAME)); + } else if (debugMode != mLastBuildConfigMode) { + // else if the build mode changed, force creation + mMustCreateBuildConfig = true; + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(), + String.format("Different build mode, must update %1$s!", + BuildConfigGenerator.BUILD_CONFIG_NAME)); + } + } + + if (mMustCreateBuildConfig) { + if (DEBUG_LOG) { + AdtPlugin.log(IStatus.INFO, "%s generating BuilderConfig!", getProject().getName()); + } + + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, getProject(), + String.format("Generating %1$s...", BuildConfigGenerator.BUILD_CONFIG_NAME)); + generator.generate(); + + mMustCreateBuildConfig = false; + saveProjectBooleanProperty(PROPERTY_COMPILE_BUILDCONFIG, mMustCreateBuildConfig); + saveProjectBooleanProperty(PROPERTY_BUILDCONFIG_MODE, mLastBuildConfigMode = debugMode); + } + } + + private boolean mergeManifest(IFolder androidOutFolder, List<IProject> libProjects, + boolean enabled) throws CoreException { + if (DEBUG_LOG) { + AdtPlugin.log(IStatus.INFO, "%s merging manifests!", getProject().getName()); + } + + IFile outFile = androidOutFolder.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML); + IFile manifest = getProject().getFile(SdkConstants.FN_ANDROID_MANIFEST_XML); + + // remove existing markers from the manifest. + // FIXME: only remove from manifest once the markers are put there. + removeMarkersFromResource(getProject(), AdtConstants.MARKER_MANIFMERGER); + + // If the merging is not enabled or if there's no library then we simply copy the + // manifest over. + if (enabled == false || libProjects.size() == 0) { + try { + new FileOp().copyFile(manifest.getLocation().toFile(), + outFile.getLocation().toFile()); + + outFile.refreshLocal(IResource.DEPTH_INFINITE, mDerivedProgressMonitor); + + saveProjectBooleanProperty(PROPERTY_MERGE_MANIFEST, mMustMergeManifest = false); + } catch (IOException e) { + handleException(e, "Failed to copy Manifest"); + return false; + } + } else { + final ArrayList<String> errors = new ArrayList<String>(); + + // TODO change MergerLog.wrapSdkLog by a custom IMergerLog that will create + // and maintain error markers. + ManifestMerger merger = new ManifestMerger( + MergerLog.wrapSdkLog(new ILogger() { + @Override + public void warning(@NonNull String warningFormat, Object... args) { + AdtPlugin.printToConsole(getProject(), String.format(warningFormat, args)); + } + + @Override + public void info(@NonNull String msgFormat, Object... args) { + AdtPlugin.printToConsole(getProject(), String.format(msgFormat, args)); + } + + @Override + public void verbose(@NonNull String msgFormat, Object... args) { + info(msgFormat, args); + } + + @Override + public void error(@Nullable Throwable t, @Nullable String errorFormat, + Object... args) { + errors.add(String.format(errorFormat, args)); + } + }), + new AdtManifestMergeCallback()); + + File[] libManifests = new File[libProjects.size()]; + int libIndex = 0; + for (IProject lib : libProjects) { + libManifests[libIndex++] = lib.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML) + .getLocation().toFile(); + } + + if (merger.process( + outFile.getLocation().toFile(), + manifest.getLocation().toFile(), + libManifests, + null /*injectAttributes*/, null /*packageOverride*/) == false) { + if (errors.size() > 1) { + StringBuilder sb = new StringBuilder(); + for (String s : errors) { + sb.append(s).append('\n'); + } + + markProject(AdtConstants.MARKER_MANIFMERGER, sb.toString(), + IMarker.SEVERITY_ERROR); + + } else if (errors.size() == 1) { + markProject(AdtConstants.MARKER_MANIFMERGER, errors.get(0), + IMarker.SEVERITY_ERROR); + } else { + markProject(AdtConstants.MARKER_MANIFMERGER, "Unknown error merging manifest", + IMarker.SEVERITY_ERROR); + } + return false; + } + + outFile.refreshLocal(IResource.DEPTH_INFINITE, mDerivedProgressMonitor); + saveProjectBooleanProperty(PROPERTY_MERGE_MANIFEST, mMustMergeManifest = false); + } + + return true; + } + + /** + * Handles resource changes and regenerate whatever files need regenerating. + * @param project the main project + * @param javaPackage the app package for the main project + * @param projectTarget the target of the main project + * @param manifest the {@link IFile} representing the project manifest + * @param libProjects the library dependencies + * @param isLibrary if the project is a library project + * @throws CoreException + * @throws AbortBuildException + */ + private void handleResources(IProject project, String javaPackage, IAndroidTarget projectTarget, + IFile manifest, IFolder resOutFolder, List<IProject> libProjects, boolean isLibrary, + IFile proguardFile) throws CoreException, AbortBuildException { + // get the resource folder + IFolder resFolder = project.getFolder(AdtConstants.WS_RESOURCES); + + // get the file system path + IPath outputLocation = mGenFolder.getLocation(); + IPath resLocation = resFolder.getLocation(); + IPath manifestLocation = manifest == null ? null : manifest.getLocation(); + + // those locations have to exist for us to do something! + if (outputLocation != null && resLocation != null + && manifestLocation != null) { + String osOutputPath = outputLocation.toOSString(); + String osResPath = resLocation.toOSString(); + String osManifestPath = manifestLocation.toOSString(); + + // remove the aapt markers + removeMarkersFromResource(manifest, AdtConstants.MARKER_AAPT_COMPILE); + removeMarkersFromContainer(resFolder, AdtConstants.MARKER_AAPT_COMPILE); + + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, + Messages.Preparing_Generated_Files); + + // we need to figure out where to store the R class. + // get the parent folder for R.java and update mManifestPackageSourceFolder + IFolder mainPackageFolder = getGenManifestPackageFolder(); + + // handle libraries + ArrayList<IFolder> libResFolders = Lists.newArrayList(); + ArrayList<Pair<File, String>> libRFiles = Lists.newArrayList(); + if (libProjects != null) { + for (IProject lib : libProjects) { + IFolder libResFolder = lib.getFolder(SdkConstants.FD_RES); + if (libResFolder.exists()) { + libResFolders.add(libResFolder); + } + + try { + // get the package of the library, and if it's different form the + // main project, generate the R class for it too. + String libJavaPackage = AndroidManifest.getPackage(new IFolderWrapper(lib)); + if (libJavaPackage.equals(javaPackage) == false) { + + IFolder libOutput = BaseProjectHelper.getAndroidOutputFolder(lib); + File libOutputFolder = libOutput.getLocation().toFile(); + + libRFiles.add(Pair.of( + new File(libOutputFolder, "R.txt"), + libJavaPackage)); + + } + } catch (Exception e) { + } + } + } + + String proguardFilePath = proguardFile != null ? + proguardFile.getLocation().toOSString(): null; + + File resOutFile = resOutFolder.getLocation().toFile(); + String resOutPath = resOutFile.isDirectory() ? resOutFile.getAbsolutePath() : null; + + execAapt(project, projectTarget, osOutputPath, resOutPath, osResPath, osManifestPath, + mainPackageFolder, libResFolders, libRFiles, isLibrary, proguardFilePath); + } + } + + /** + * Executes AAPT to generate R.java/Manifest.java + * @param project the main project + * @param projectTarget the main project target + * @param osOutputPath the OS output path for the generated file. This is the source folder, not + * the package folder. + * @param osResPath the OS path to the res folder for the main project + * @param osManifestPath the OS path to the manifest of the main project + * @param packageFolder the IFolder that will contain the generated file. Unlike + * <var>osOutputPath</var> this is the direct parent of the generated files. + * If <var>customJavaPackage</var> is not null, this must match the new destination triggered + * by its value. + * @param libResFolders the list of res folders for the library. + * @param libRFiles a list of R files for the libraries. + * @param isLibrary if the project is a library project + * @param proguardFile an optional path to store proguard information + * @throws AbortBuildException + */ + @SuppressWarnings("deprecation") + private void execAapt(IProject project, IAndroidTarget projectTarget, String osOutputPath, + String osBcOutPath, String osResPath, String osManifestPath, IFolder packageFolder, + ArrayList<IFolder> libResFolders, List<Pair<File, String>> libRFiles, + boolean isLibrary, String proguardFile) + throws AbortBuildException { + + // We actually need to delete the manifest.java as it may become empty and + // in this case aapt doesn't generate an empty one, but instead doesn't + // touch it. + IFile manifestJavaFile = packageFolder.getFile(SdkConstants.FN_MANIFEST_CLASS); + manifestJavaFile.getLocation().toFile().delete(); + + // launch aapt: create the command line + ArrayList<String> array = new ArrayList<String>(); + + String aaptPath = mBuildToolInfo.getPath(BuildToolInfo.PathId.AAPT); + + array.add(aaptPath); + array.add("package"); //$NON-NLS-1$ + array.add("-m"); //$NON-NLS-1$ + if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) { + array.add("-v"); //$NON-NLS-1$ + } + + if (isLibrary) { + array.add("--non-constant-id"); //$NON-NLS-1$ + } + + if (libResFolders.size() > 0) { + array.add("--auto-add-overlay"); //$NON-NLS-1$ + } + + // If a library or has libraries, generate a text version of the R symbols. + File outputFolder = BaseProjectHelper.getAndroidOutputFolder(project).getLocation() + .toFile(); + + if (isLibrary || !libRFiles.isEmpty()) { + array.add("--output-text-symbols"); //$NON-NLS-1$ + array.add(outputFolder.getAbsolutePath()); + } + + array.add("-J"); //$NON-NLS-1$ + array.add(osOutputPath); + array.add("-M"); //$NON-NLS-1$ + array.add(osManifestPath); + if (osBcOutPath != null) { + array.add("-S"); //$NON-NLS-1$ + array.add(osBcOutPath); + } + array.add("-S"); //$NON-NLS-1$ + array.add(osResPath); + for (IFolder libResFolder : libResFolders) { + array.add("-S"); //$NON-NLS-1$ + array.add(libResFolder.getLocation().toOSString()); + } + + array.add("-I"); //$NON-NLS-1$ + array.add(projectTarget.getPath(IAndroidTarget.ANDROID_JAR)); + + // use the proguard file + if (proguardFile != null && proguardFile.length() > 0) { + array.add("-G"); + array.add(proguardFile); + } + + if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) { + StringBuilder sb = new StringBuilder(); + for (String c : array) { + sb.append(c); + sb.append(' '); + } + String cmd_line = sb.toString(); + AdtPlugin.printToConsole(project, cmd_line); + } + + // launch + try { + // launch the command line process + Process process = Runtime.getRuntime().exec( + array.toArray(new String[array.size()])); + + // list to store each line of stderr + ArrayList<String> stdErr = new ArrayList<String>(); + + // get the output and return code from the process + int returnCode = grabProcessOutput(process, stdErr); + + // attempt to parse the error output + boolean parsingError = AaptParser.parseOutput(stdErr, project); + + // if we couldn't parse the output we display it in the console. + if (parsingError) { + if (returnCode != 0) { + AdtPlugin.printErrorToConsole(project, stdErr.toArray()); + } else { + AdtPlugin.printBuildToConsole(BuildVerbosity.NORMAL, + project, stdErr.toArray()); + } + } + + if (returnCode != 0) { + // 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. + if (parsingError) { + markProject(AdtConstants.MARKER_ADT, + Messages.Unparsed_AAPT_Errors, IMarker.SEVERITY_ERROR); + } else if (stdErr.size() == 0) { + // no parsing error because sdterr was empty. We still need to put + // a marker otherwise there's no user visible feedback. + markProject(AdtConstants.MARKER_ADT, + String.format(Messages.AAPT_Exec_Error_d, returnCode), + IMarker.SEVERITY_ERROR); + } + + AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, + Messages.AAPT_Error); + + // abort if exec failed. + throw new AbortBuildException(); + } + + // now if the project has libraries, R needs to be created for each libraries + // unless this is a library. + if (isLibrary == false && !libRFiles.isEmpty()) { + File rFile = new File(outputFolder, SdkConstants.FN_RESOURCE_TEXT); + // if the project has no resources, the file could not exist. + if (rFile.isFile()) { + // Load the full symbols from the full R.txt file. + SymbolLoader fullSymbolValues = new SymbolLoader(rFile); + fullSymbolValues.load(); + + Multimap<String, SymbolLoader> libMap = ArrayListMultimap.create(); + + // First pass processing the libraries, collecting them by packageName, + // and ignoring the ones that have the same package name as the application + // (since that R class was already created). + + for (Pair<File, String> lib : libRFiles) { + String libPackage = lib.getSecond(); + File rText = lib.getFirst(); + + if (rText.isFile()) { + // load the lib symbols + SymbolLoader libSymbols = new SymbolLoader(rText); + libSymbols.load(); + + // store these symbols by associating them with the package name. + libMap.put(libPackage, libSymbols); + } + } + + // now loop on all the package names, merge all the symbols to write, + // and write them + for (String packageName : libMap.keySet()) { + Collection<SymbolLoader> symbols = libMap.get(packageName); + + SymbolWriter writer = new SymbolWriter(osOutputPath, packageName, + fullSymbolValues); + for (SymbolLoader symbolLoader : symbols) { + writer.addSymbolsToWrite(symbolLoader); + } + writer.write(); + } + } + } + + } catch (IOException e1) { + // something happen while executing the process, + // mark the project and exit + String msg; + String path = array.get(0); + if (!new File(path).exists()) { + msg = String.format(Messages.AAPT_Exec_Error_s, path); + } else { + String description = e1.getLocalizedMessage(); + if (e1.getCause() != null && e1.getCause() != e1) { + description = description + ": " + e1.getCause().getLocalizedMessage(); + } + msg = String.format(Messages.AAPT_Exec_Error_Other_s, description); + } + + markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); + + // Add workaround for the Linux problem described here: + // http://developer.android.com/sdk/installing.html#troubleshooting + // There are various posts on StackOverflow elsewhere where people are asking + // about aapt failing to run, so even though this is documented in the + // Troubleshooting section add an error message to help with this + // scenario. + if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX + && System.getProperty("os.arch").endsWith("64") //$NON-NLS-1$ //$NON-NLS-2$ + && new File(aaptPath).exists() + && new File("/usr/bin/apt-get").exists()) { //$NON-NLS-1$ + markProject(AdtConstants.MARKER_ADT, + "Hint: On 64-bit systems, make sure the 32-bit libraries are installed: \"sudo apt-get install ia32-libs\" or on some systems, \"sudo apt-get install lib32z1\"", + IMarker.SEVERITY_ERROR); + // Note - this uses SEVERITY_ERROR even though it's really SEVERITY_INFO because + // we want this error message to show up adjacent to the aapt error message + // (and Eclipse sorts by priority) + } + + // This interrupts the build. + throw new AbortBuildException(); + } catch (InterruptedException e) { + // we got interrupted waiting for the process to end... + // mark the project and exit + String msg = String.format(Messages.AAPT_Exec_Error_s, array.get(0)); + markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); + + // This interrupts the build. + throw new AbortBuildException(); + } finally { + // we've at least attempted to run aapt, save the fact that we don't have to + // run it again, unless there's a new resource change. + saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, + mMustCompileResources = false); + ResourceManager.clearAaptRequest(project); + } + } + + /** + * Creates a relative {@link IPath} from a java package. + * @param javaPackageName the java package. + */ + private IPath getJavaPackagePath(String javaPackageName) { + // convert the java package into path + String[] segments = javaPackageName.split(AdtConstants.RE_DOT); + + StringBuilder path = new StringBuilder(); + for (String s : segments) { + path.append(AdtConstants.WS_SEP_CHAR); + path.append(s); + } + + return new Path(path.toString()); + } + + /** + * Returns an {@link IFolder} (located inside the 'gen' source folder), that matches the + * package defined in the manifest. This {@link IFolder} may not actually exist + * (aapt will create it anyway). + * @return the {@link IFolder} that will contain the R class or null if + * the folder was not found. + * @throws CoreException + */ + private IFolder getGenManifestPackageFolder() throws CoreException { + // get the path for the package + IPath packagePath = getJavaPackagePath(mManifestPackage); + + // get a folder for this path under the 'gen' source folder, and return it. + // This IFolder may not reference an actual existing folder. + return mGenFolder.getFolder(packagePath); + } +} |