/* * 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.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.RenderScriptProcessor; import com.android.ide.eclipse.adt.internal.build.SourceProcessor; 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.IAndroidTarget; 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.utils.ILogger; import com.android.utils.Pair; import com.android.xml.AndroidManifest; import com.google.common.collect.Lists; 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.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.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; private final List<SourceProcessor> mProcessors = new ArrayList<SourceProcessor>(2); /** 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 RenderScriptProcessor mRenderScriptProcessor; /** * 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; 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); // now we need to get the classpath list List<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths(javaProject); IFolder androidOutputFolder = BaseProjectHelper.getAndroidOutputFolder(project); 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; for (SourceProcessor processor : mProcessors) { processor.prepareFullBuild(project); } } 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; for (SourceProcessor processor : mProcessors) { processor.prepareFullBuild(project); } } else { dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList, mProcessors); 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 for (SourceProcessor processor : mProcessors) { processor.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; } // 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; for (SourceProcessor processor : mProcessors) { processor.prepareFullBuild(project); } 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; // 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 result; } } mRenderScriptProcessor.setTargetApi(rsTarget); for (SourceProcessor processor : mProcessors) { try { processorStatus |= processor.compileFiles(this, project, projectTarget, sourceFolderPathList, libProjectsOut, monitor); } catch (Throwable t) { handleException(t, String.format( "Failed to run %s. Check workspace log for detail.", processor.getClass().getName())); 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, 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); } return result; } @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; } IJavaProject javaProject = JavaCore.create(project); // load the source processors SourceProcessor aidlProcessor = new AidlProcessor(javaProject, mGenFolder); mRenderScriptProcessor = new RenderScriptProcessor(javaProject, mGenFolder); mProcessors.add(aidlProcessor); mProcessors.add(mRenderScriptProcessor); } catch (Throwable throwable) { AdtPlugin.log(throwable, "Failed to finish PrecompilerBuilder#startupOnInitialize()"); } } 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(Throwable t, 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*/) == 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, 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; execAapt(project, projectTarget, osOutputPath, 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 */ private void execAapt(IProject project, IAndroidTarget projectTarget, String osOutputPath, 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>(); @SuppressWarnings("deprecation") String aaptPath = projectTarget.getPath(IAndroidTarget.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); 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()) { SymbolLoader symbolValues = new SymbolLoader(rFile); symbolValues.load(); for (Pair<File, String> libData : libRFiles) { File libRFile = libData.getFirst(); if (libRFile.isFile()) { SymbolLoader symbols = new SymbolLoader(libRFile); symbols.load(); SymbolWriter writer = new SymbolWriter(osOutputPath, libData.getSecond(), symbols, symbolValues); 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", 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); } }