diff options
18 files changed, 1180 insertions, 612 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlGenerator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlGenerator.java new file mode 100644 index 0000000..e28cc12 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlGenerator.java @@ -0,0 +1,530 @@ +/* + * Copyright (C) 2011 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; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder; +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.BaseProjectHelper; +import com.android.ide.eclipse.adt.internal.project.ProjectHelper; +import com.android.sdklib.IAndroidTarget; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.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.Path; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.jdt.core.IJavaProject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link JavaGenerator} for aidl files. + * + */ +public class AidlGenerator extends JavaGenerator { + + private static final String PROPERTY_COMPILE_AIDL = "compileAidl"; //$NON-NLS-1$ + + /** + * Single line aidl error<br> + * "<path>:<line>: <error>" + * or + * "<path>:<line> <error>" + */ + private static Pattern sAidlPattern1 = Pattern.compile("^(.+?):(\\d+):?\\s(.+)$"); //$NON-NLS-1$ + + + private enum AidlType { + UNKNOWN, INTERFACE, PARCELABLE; + } + + // See comment in #getAidlType() +// private final static Pattern sParcelablePattern = Pattern.compile( +// "^\\s*parcelable\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*;\\s*$"); +// +// private final static Pattern sInterfacePattern = Pattern.compile( +// "^\\s*interface\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?:\\{.*)?$"); + + + /** + * The custom delta visitor for aidl files. Based on the base JavaGenerator's delta visitor. + */ + public static class AidlGeneratorDeltaVisitor extends JavaGeneratorDeltaVisitor { + + @Override + public void handleChangedNonJavaFile(IFolder currentSourceFolder, IFile file, int kind) { + // get the extension of the resource + String ext = file.getFileExtension(); + if (AndroidConstants.EXT_AIDL.equalsIgnoreCase(ext)) { + // first check whether it's a regular file or a parcelable. + AidlType type = getAidlType(file); + + if (type == AidlType.INTERFACE) { + if (kind == IResourceDelta.REMOVED) { + // we'll have to remove the generated file. + addFileToRemove(currentSourceFolder, file); + } else if (getForceCompile() == false) { + // add the aidl file to the list of file to (re)compile + addFileToCompile(currentSourceFolder, file); + } + } else { + // force recompilations of all Aidl Files. + setForceCompile(); + //mAidlToCompile.clear(); + } + } + } + + @Override + public boolean handleChangedGeneratedJavaFile(IFolder currentSourceFolder, IFile file, + List<IPath> sourceFolders) { + + String fileName = file.getName(); + String[] segments = file.getFullPath().segments(); + + // Look for the source aidl file in all the source folders that would match this given + // java file. + String aidlFileName = fileName.replaceAll(AndroidConstants.RE_JAVA_EXT, + AndroidConstants.DOT_AIDL); + + for (IPath sourceFolderPath : sourceFolders) { + // do not search in the current source folder as it is the 'gen' folder. + if (sourceFolderPath.equals(currentSourceFolder.getFullPath())) { + continue; + } + + IFolder sourceFolder = getFolder(sourceFolderPath); + if (sourceFolder != null) { + // go recursively, segment by segment. + // index starts at 2 (0 is project, 1 is 'gen' + IFile sourceFile = findFile(sourceFolder, segments, 2, aidlFileName); + + if (sourceFile != null) { + // found the source. add it to the list of files to compile + addFileToCompile(currentSourceFolder, sourceFile); + return true; + } + } + } + + return false; + } + } + + public AidlGenerator(IJavaProject javaProject, IFolder genFolder) { + super(new AidlGeneratorDeltaVisitor(), genFolder); + + IProject project = javaProject.getProject(); + + boolean mustCompileAidl = ProjectHelper.loadBooleanProperty(project, + PROPERTY_COMPILE_AIDL, true /*defaultValue*/); + + // if we stored that we have to compile some aidl, we build the list that will compile them + // all + if (mustCompileAidl) { + ArrayList<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths( + javaProject); + + buildCompilationList(project, sourceFolderPathList); + } + } + + @Override + public boolean compileFiles(BaseBuilder builder, IProject project, IAndroidTarget projectTarget, + List<IPath> sourceFolders, IProgressMonitor monitor) throws CoreException { + List<SourceData> toCompile = getToCompile(); + List<SourceData> toRemove = getToRemove(); + + if (toCompile.size() == 0 && toRemove.size() == 0) { + return false; + } + + // create the command line + String[] command = new String[4 + sourceFolders.size()]; + int index = 0; + command[index++] = projectTarget.getPath(IAndroidTarget.AIDL); + command[index++] = "-p" + projectTarget.getPath(IAndroidTarget.ANDROID_AIDL); + + // since the path are relative to the workspace and not the project itself, we need + // the workspace root. + IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); + for (IPath p : sourceFolders) { + IFolder f = wsRoot.getFolder(p); + command[index++] = "-I" + f.getLocation().toOSString(); //$NON-NLS-1$ + } + + // list of files that have failed compilation. + List<SourceData> stillNeedCompilation = new ArrayList<SourceData>(); + + // if an aidl file is being removed before we managed to compile it, it'll be in + // both list. We *need* to remove it from the compile list or it'll never go away. + for (SourceData aidlFile : toRemove) { + int pos = toCompile.indexOf(aidlFile); + if (pos != -1) { + toCompile.remove(pos); + } + } + + // loop until we've compile them all + for (SourceData aidlData : toCompile) { + // Remove the AIDL error markers from the aidl file + builder.removeMarkersFromFile(aidlData.sourceFile, AndroidConstants.MARKER_AIDL); + + // get the path of the source file. + IPath sourcePath = aidlData.sourceFile.getLocation(); + String osSourcePath = sourcePath.toOSString(); + + IFile javaFile = getGenDestinationFile(aidlData, true /*createFolders*/, monitor); + + // finish to set the command line. + command[index] = osSourcePath; + command[index + 1] = javaFile.getLocation().toOSString(); + + // launch the process + if (execAidl(builder, project, command, aidlData.sourceFile) == false) { + // aidl failed. File should be marked. We add the file to the list + // of file that will need compilation again. + stillNeedCompilation.add(aidlData); + + // and we move on to the next one. + continue; + } + } + + // change the list to only contains the file that have failed compilation + toCompile.clear(); + toCompile.addAll(stillNeedCompilation); + + // Remove the java files created from aidl files that have been removed. + for (SourceData aidlData : toRemove) { + IFile javaFile = getGenDestinationFile(aidlData, false /*createFolders*/, monitor); + if (javaFile.exists()) { + // This confirms the java file was generated by the builder, + // we can delete the aidlFile. + javaFile.getLocation().toFile().delete(); + } + } + + toRemove.clear(); + + // store the build state. If there are any files that failed to compile, we will + // force a full aidl compile on the next project open. (unless a full compilation succeed + // before the project is closed/re-opened.) + saveState(project); + + return true; + } + + /** + * Execute the aidl command line, parse the output, and mark the aidl file + * with any reported errors. + * @param command the String array containing the command line to execute. + * @param file The IFile object representing the aidl file being + * compiled. + * @return false if the exec failed, and build needs to be aborted. + */ + private boolean execAidl(BaseBuilder builder, IProject project, String[] command, IFile file) { + // do the exec + try { + if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) { + StringBuilder sb = new StringBuilder(); + for (String c : command) { + sb.append(c); + sb.append(' '); + } + String cmd_line = sb.toString(); + AdtPlugin.printToConsole(project, cmd_line); + } + + Process p = Runtime.getRuntime().exec(command); + + // list to store each line of stderr + ArrayList<String> results = new ArrayList<String>(); + + // get the output and return code from the process + int result = BuildHelper.grabProcessOutput(project, p, results); + + // attempt to parse the error output + boolean error = parseAidlOutput(results, file); + + // If the process failed and we couldn't parse the output + // we print a message, mark the project and exit + if (result != 0 && error == true) { + // display the message in the console. + AdtPlugin.printErrorToConsole(project, results.toArray()); + + // mark the project and exit + BaseProjectHelper.markResource(project, AndroidConstants.MARKER_ADT, + Messages.Unparsed_AIDL_Errors, IMarker.SEVERITY_ERROR); + return false; + } + } catch (IOException e) { + // mark the project and exit + String msg = String.format(Messages.AIDL_Exec_Error, command[0]); + BaseProjectHelper.markResource(project, AndroidConstants.MARKER_ADT, msg, + IMarker.SEVERITY_ERROR); + return false; + } catch (InterruptedException e) { + // mark the project and exit + String msg = String.format(Messages.AIDL_Exec_Error, command[0]); + BaseProjectHelper.markResource(project, AndroidConstants.MARKER_ADT, msg, + IMarker.SEVERITY_ERROR); + return false; + } + + return true; + } + + /** + * Parse the output of aidl and mark the file with any errors. + * @param lines The output to parse. + * @param file The file to mark with error. + * @return true if the parsing failed, false if success. + */ + private boolean parseAidlOutput(ArrayList<String> lines, IFile file) { + // nothing to parse? just return false; + if (lines.size() == 0) { + return false; + } + + Matcher m; + + for (int i = 0; i < lines.size(); i++) { + String p = lines.get(i); + + m = sAidlPattern1.matcher(p); + if (m.matches()) { + // we can ignore group 1 which is the location since we already + // have a IFile object representing the aidl file. + String lineStr = m.group(2); + String msg = m.group(3); + + // get the line number + int line = 0; + try { + line = Integer.parseInt(lineStr); + } catch (NumberFormatException e) { + // looks like the string we extracted wasn't a valid + // file number. Parsing failed and we return true + return true; + } + + // mark the file + BaseProjectHelper.markResource(file, AndroidConstants.MARKER_AIDL, msg, line, + IMarker.SEVERITY_ERROR); + + // success, go to the next line + continue; + } + + // invalid line format, flag as error, and bail + return true; + } + + return false; + } + + @Override + public void saveState(IProject project) { + // TODO: Optimize by saving only the files that need compilation + ProjectHelper.saveStringProperty(project, PROPERTY_COMPILE_AIDL, + Boolean.toString(getToCompile().size() > 0)); + } + + + /** + * Goes through the build paths and fills the list of aidl files to compile + * ({@link #mAidlToCompile}). + * @param project The project. + * @param sourceFolderPathList The list of source folder paths. + * @param genFolder the gen folder + */ + @Override + protected void buildCompilationList(IProject project, List<IPath> sourceFolderPathList) { + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + for (IPath sourceFolderPath : sourceFolderPathList) { + IFolder sourceFolder = root.getFolder(sourceFolderPath); + // we don't look in the 'gen' source folder as there will be no source in there. + if (sourceFolder.exists() && sourceFolder.equals(getGenFolder()) == false) { + scanFolderForAidl(sourceFolder, sourceFolder); + } + } + } + + /** + * Returns the {@link IFile} handle to the destination file for a given aidl source file + * ({@link AidlData}). + * @param aidlData the data for the aidl source file. + * @param createFolders whether or not the parent folder of the destination should be created + * if it does not exist. + * @param monitor the progress monitor + * @return the handle to the destination file. + * @throws CoreException + */ + private IFile getGenDestinationFile(SourceData aidlData, boolean createFolders, + IProgressMonitor monitor) throws CoreException { + // build the destination folder path. + // Use the path of the source file, except for the path leading to its source folder, + // and for the last segment which is the filename. + int segmentToSourceFolderCount = aidlData.sourceFolder.getFullPath().segmentCount(); + IPath packagePath = aidlData.sourceFile.getFullPath().removeFirstSegments( + segmentToSourceFolderCount).removeLastSegments(1); + Path destinationPath = new Path(packagePath.toString()); + + // get an IFolder for this path. It's relative to the 'gen' folder already + IFolder destinationFolder = getGenFolder().getFolder(destinationPath); + + // create it if needed. + if (destinationFolder.exists() == false && createFolders) { + createFolder(destinationFolder, monitor); + } + + // Build the Java file name from the aidl name. + String javaName = aidlData.sourceFile.getName().replaceAll(AndroidConstants.RE_AIDL_EXT, + AndroidConstants.DOT_JAVA); + + // get the resource for the java file. + IFile javaFile = destinationFolder.getFile(javaName); + return javaFile; + } + + /** + * Scans a folder and fills the list of aidl files to compile. + * @param sourceFolder the root source folder. + * @param folder The folder to scan. + */ + private void scanFolderForAidl(IFolder sourceFolder, IFolder folder) { + try { + IResource[] members = folder.members(); + for (IResource r : members) { + // get the type of the resource + switch (r.getType()) { + case IResource.FILE: + // if this a file, check that the file actually exist + // and that it's an aidl file + if (r.exists() && + AndroidConstants.EXT_AIDL.equalsIgnoreCase(r.getFileExtension())) { + addFileToCompile(sourceFolder, (IFile)r); + } + break; + case IResource.FOLDER: + // recursively go through children + scanFolderForAidl(sourceFolder, (IFolder)r); + break; + default: + // this would mean it's a project or the workspace root + // which is unlikely to happen. we do nothing + break; + } + } + } catch (CoreException e) { + // Couldn't get the members list for some reason. Just return. + } + } + + /** + * Creates the destination folder. Because + * {@link IFolder#create(boolean, boolean, IProgressMonitor)} only works if the parent folder + * already exists, this goes and ensure that all the parent folders actually exist, or it + * creates them as well. + * @param destinationFolder The folder to create + * @param monitor the {@link IProgressMonitor}, + * @throws CoreException + */ + private void createFolder(IFolder destinationFolder, IProgressMonitor monitor) + throws CoreException { + + // check the parent exist and create if necessary. + IContainer parent = destinationFolder.getParent(); + if (parent.getType() == IResource.FOLDER && parent.exists() == false) { + createFolder((IFolder)parent, monitor); + } + + // create the folder. + destinationFolder.create(true /*force*/, true /*local*/, + new SubProgressMonitor(monitor, 10)); + } + + /** + * Returns the type of the aidl file. Aidl files can either declare interfaces, or declare + * parcelables. This method will attempt to parse the file and return the type. If the type + * cannot be determined, then it will return {@link AidlType#UNKNOWN}. + * @param file The aidl file + * @return the type of the aidl. + */ + private static AidlType getAidlType(IFile file) { + // At this time, parsing isn't available, so we return UNKNOWN. This will force + // a recompilation of all aidl file as soon as one is changed. + return AidlType.UNKNOWN; + + // TODO: properly parse aidl file to determine type and generate dependency graphs. +// +// String className = file.getName().substring(0, +// file.getName().length() - AndroidConstants.DOT_AIDL.length()); +// +// InputStream input = file.getContents(true /* force*/); +// try { +// BufferedReader reader = new BufferedReader(new InputStreateader(input)); +// String line; +// while ((line = reader.readLine()) != null) { +// if (line.length() == 0) { +// continue; +// } +// +// Matcher m = sParcelablePattern.matcher(line); +// if (m.matches() && m.group(1).equals(className)) { +// return AidlType.PARCELABLE; +// } +// +// m = sInterfacePattern.matcher(line); +// if (m.matches() && m.group(1).equals(className)) { +// return AidlType.INTERFACE; +// } +// } +// } catch (IOException e) { +// throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, +// "Error parsing aidl file", e)); +// } finally { +// try { +// input.close(); +// } catch (IOException e) { +// throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, +// "Error parsing aidl file", e)); +// } +// } +// +// return AidlType.UNKNOWN; + } + + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/JavaGenerator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/JavaGenerator.java new file mode 100644 index 0000000..1f9244d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/JavaGenerator.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2011 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; + +import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder; +import com.android.sdklib.IAndroidTarget; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Base class to handle generated java code. + * + * It provides management for modified source file list, deleted source file list, reconciliation + * of previous lists, storing the current state of the build. + * + * It also provides a base class for delta visitor that should be customized for each Generator + * extending this class. + * + */ +public abstract class JavaGenerator { + + /** + * Data to temporarily store source file information. + */ + protected static class SourceData { + IFile sourceFile; + /** this is the root source folder, not the file parent. */ + IFolder sourceFolder; + + SourceData(IFolder sourceFolder, IFile sourceFile) { + this.sourceFolder = sourceFolder; + this.sourceFile = sourceFile; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((sourceFile == null) ? 0 : sourceFile.hashCode()); + result = prime * result + ((sourceFolder == null) ? 0 : sourceFolder.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SourceData other = (SourceData) obj; + if (sourceFile == null) { + if (other.sourceFile != null) + return false; + } else if (!sourceFile.equals(other.sourceFile)) + return false; + if (sourceFolder == null) { + if (other.sourceFolder != null) + return false; + } else if (!sourceFolder.equals(other.sourceFolder)) + return false; + return true; + } + + @Override + public String toString() { + return "SourceData [sourceFile=" //$NON-NLS-1$ + + sourceFile + + ", sourceFolder=" //$NON-NLS-1$ + + sourceFolder + + "]"; //$NON-NLS-1$ + } + } + + /** + * Base delta visitor for the Java generator classes. + * + * It provides storage for modified and deleted source files and a few other features. + */ + public abstract static class JavaGeneratorDeltaVisitor { + + private IWorkspaceRoot mRoot; + + private boolean mForceCompile; + + /** List of source files found that are modified or new. */ + private final List<SourceData> mToCompile = new ArrayList<SourceData>(); + + /** List of .aidl files that have been removed. */ + private final List<SourceData> mToRemove = new ArrayList<SourceData>(); + + public abstract boolean handleChangedGeneratedJavaFile(IFolder currentSourceFolder, + IFile file, List<IPath> sourceFolders); + + public abstract void handleChangedNonJavaFile(IFolder currentSourceFolder, + IFile file, int kind); + + public void setWorkspaceRoot(IWorkspaceRoot root) { + mRoot = root; + } + + public void setForceCompile() { + mForceCompile = true; + } + + boolean getForceCompile() { + return mForceCompile; + } + + public void addFileToCompile(IFolder sourceFolder, IFile sourceFile) { + mToCompile.add(new SourceData(sourceFolder, sourceFile)); + } + + List<SourceData> getFilesToCompile() { + return mToCompile; + } + + public void addFileToRemove(IFolder sourceFolder, IFile sourceFile) { + mToRemove.add(new SourceData(sourceFolder, sourceFile)); + } + + List<SourceData> getFilesToRemove() { + return mToRemove; + } + + public void reset() { + mForceCompile = false; + mToCompile.clear(); + mToRemove.clear(); + } + + /** + * Returns a handle to the folder identified by the given path in this container. + * <p/>The different with {@link IContainer#getFolder(IPath)} is that this returns a non + * null object only if the resource actually exists and is a folder (and not a file) + * @param path the path of the folder to return. + * @return a handle to the folder if it exists, or null otherwise. + */ + protected IFolder getFolder(IPath path) { + IResource resource = mRoot.findMember(path); + if (resource != null && resource.exists() && resource.getType() == IResource.FOLDER) { + return (IFolder)resource; + } + + return null; + } + + /** + * Searches for and return a file in a folder. The file is defined by its segments, and + * a new name (replacing the last segment). + * @param folder the folder we are searching + * @param segments the segments of the file to search. + * @param index the index of the current segment we are looking for + * @param filename the new name to replace the last segment. + * @return the {@link IFile} representing the searched file, or null if not found + */ + protected IFile findFile(IFolder folder, String[] segments, int index, String filename) { + boolean lastSegment = index == segments.length - 1; + IResource resource = folder.findMember(lastSegment ? filename : segments[index]); + if (resource != null && resource.exists()) { + if (lastSegment) { + if (resource.getType() == IResource.FILE) { + return (IFile)resource; + } + } else { + if (resource.getType() == IResource.FOLDER) { + return findFile((IFolder)resource, segments, index+1, filename); + } + } + } + return null; + } + } + + /** List of source files found that are modified or new. */ + private final List<SourceData> mToCompile = new ArrayList<SourceData>(); + + /** List of .aidl files that have been removed. */ + private final List<SourceData> mToRemove = new ArrayList<SourceData>(); + + private final JavaGeneratorDeltaVisitor mDeltaVisitor; + + private final IFolder mGenFolder; + + protected JavaGenerator(JavaGeneratorDeltaVisitor deltaVisitor, IFolder genFolder) { + mDeltaVisitor = deltaVisitor; + mGenFolder = genFolder; + } + + public JavaGeneratorDeltaVisitor getDeltaVisitor() { + return mDeltaVisitor; + } + + IFolder getGenFolder() { + return mGenFolder; + } + + List<SourceData> getToCompile() { + return mToCompile; + } + + List<SourceData> getToRemove() { + return mToRemove; + } + + void addFileToCompile(IFolder sourceFolder, IFile sourceFile) { + mToCompile.add(new SourceData(sourceFolder, sourceFile)); + } + + public void prepareFullBuild(IProject project, List<IPath> sourceFolders) { + mDeltaVisitor.reset(); + buildCompilationList(project, sourceFolders); + } + + public void doneVisiting(IProject project, List<IPath> sourceFolders) { + if (mDeltaVisitor.getForceCompile()) { + buildCompilationList(project, sourceFolders); + } else { + // merge the previous file modification lists and the new one. + mergeFileModifications(mDeltaVisitor); + } + + mDeltaVisitor.reset(); + + saveState(project); + } + + protected abstract void buildCompilationList(IProject project, List<IPath> sourceFolders); + + public abstract boolean compileFiles(BaseBuilder builder, + IProject project, IAndroidTarget projectTarget, + List<IPath> sourceFolders, IProgressMonitor monitor) throws CoreException; + + public abstract void saveState(IProject project); + + /** + * Merge the current list of source file to compile/remove with the one coming from the + * delta visitor + * @param visitor the delta visitor. + */ + private void mergeFileModifications(JavaGeneratorDeltaVisitor visitor) { + List<SourceData> toRemove = visitor.getFilesToRemove(); + List<SourceData> toCompile = visitor.getFilesToCompile(); + + // loop through the new toRemove list, and add it to the old one, + // plus remove any file that was still to compile and that are now + // removed + for (SourceData r : toRemove) { + if (mToRemove.indexOf(r) == -1) { + mToRemove.add(r); + } + + int index = mToCompile.indexOf(r); + if (index != -1) { + mToCompile.remove(index); + } + } + + // now loop through the new files to compile and add it to the list. + // Also look for them in the remove list, this would mean that they + // were removed, then added back, and we shouldn't remove them, just + // recompile them. + for (SourceData r : toCompile) { + if (mToCompile.indexOf(r) == -1) { + mToCompile.add(r); + } + + int index = mToRemove.indexOf(r); + if (index != -1) { + mToRemove.remove(index); + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java index 553ec47..ee7756b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java @@ -53,7 +53,7 @@ import javax.xml.parsers.SAXParserFactory; * Base builder for XML files. This class allows for basic XML parsing with * error checking and marking the files for errors/warnings. */ -abstract class BaseBuilder extends IncrementalProjectBuilder { +public abstract class BaseBuilder extends IncrementalProjectBuilder { /** SAX Parser factory. */ @@ -193,7 +193,7 @@ abstract class BaseBuilder extends IncrementalProjectBuilder { * @param markerId The id of the markers to remove. If null, all marker of * type <code>IMarker.PROBLEM</code> will be removed. */ - protected final void removeMarkersFromFile(IFile file, String markerId) { + public final void removeMarkersFromFile(IFile file, String markerId) { try { if (file.exists()) { file.deleteMarkers(markerId, true, IResource.DEPTH_ZERO); 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 index 11712bf..586dca5 100644 --- 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 @@ -19,6 +19,8 @@ package com.android.ide.eclipse.adt.internal.build.builders; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.build.AaptParser; +import com.android.ide.eclipse.adt.internal.build.AidlGenerator; +import com.android.ide.eclipse.adt.internal.build.JavaGenerator; import com.android.ide.eclipse.adt.internal.build.Messages; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; @@ -37,20 +39,16 @@ import com.android.sdklib.SdkConstants; import com.android.sdklib.xml.AndroidManifest; import com.android.sdklib.xml.ManifestData; -import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.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.Path; -import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; @@ -58,8 +56,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Pre Java Compiler. @@ -80,42 +76,6 @@ public class PreCompilerBuilder extends BaseBuilder { private static final String PROPERTY_PACKAGE = "manifestPackage"; //$NON-NLS-1$ private static final String PROPERTY_COMPILE_RESOURCES = "compileResources"; //$NON-NLS-1$ - private static final String PROPERTY_COMPILE_AIDL = "compileAidl"; //$NON-NLS-1$ - - /** - * Single line aidl error<br> - * "<path>:<line>: <error>" - * or - * "<path>:<line> <error>" - */ - private static Pattern sAidlPattern1 = Pattern.compile("^(.+?):(\\d+):?\\s(.+)$"); //$NON-NLS-1$ - - /** - * Data to temporarily store aidl source file information - */ - static class AidlData { - IFile aidlFile; - IFolder sourceFolder; - - AidlData(IFolder sourceFolder, IFile aidlFile) { - this.sourceFolder = sourceFolder; - this.aidlFile = aidlFile; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (obj instanceof AidlData) { - AidlData file = (AidlData)obj; - return aidlFile.equals(file.aidlFile) && sourceFolder.equals(file.sourceFolder); - } - - return false; - } - } /** * Resource Compile flag. This flag is reset to false after each successful compilation, and @@ -124,11 +84,7 @@ public class PreCompilerBuilder extends BaseBuilder { */ private boolean mMustCompileResources = false; - /** List of .aidl files found that are modified or new. */ - private final ArrayList<AidlData> mAidlToCompile = new ArrayList<AidlData>(); - - /** List of .aidl files that have been removed. */ - private final ArrayList<AidlData> mAidlToRemove = new ArrayList<AidlData>(); + private final List<JavaGenerator> mGeneratorList = new ArrayList<JavaGenerator>(); /** cache of the java package defined in the manifest */ private String mManifestPackage; @@ -144,6 +100,7 @@ public class PreCompilerBuilder extends BaseBuilder { */ private DerivedProgressMonitor mDerivedProgressMonitor; + /** * 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>, @@ -153,17 +110,14 @@ public class PreCompilerBuilder extends BaseBuilder { */ private static class DerivedProgressMonitor implements IProgressMonitor { private boolean mCancelled = false; - private final ArrayList<IFile> mFileList = new ArrayList<IFile>(); private boolean mDone = false; - public DerivedProgressMonitor() { - } + private final IFolder mGenFolder; - void addFile(IFile file) { - mFileList.add(file); + public DerivedProgressMonitor(IFolder genFolder) { + mGenFolder = genFolder; } void reset() { - mFileList.clear(); mDone = false; } @@ -173,14 +127,30 @@ public class PreCompilerBuilder extends BaseBuilder { public void done() { if (mDone == false) { mDone = true; - for (IFile file : mFileList) { - if (file.exists()) { - try { - file.setDerived(true); - } 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. - } + 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); + } 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. } } } @@ -260,7 +230,10 @@ public class PreCompilerBuilder extends BaseBuilder { doClean(project, monitor); mMustCompileResources = true; - buildAidlCompilationList(project, sourceFolderPathList); + + for (JavaGenerator generator : mGeneratorList) { + generator.prepareFullBuild(project, sourceFolderPathList); + } } else { AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, Messages.Start_Inc_Pre_Compiler); @@ -272,20 +245,18 @@ public class PreCompilerBuilder extends BaseBuilder { IResourceDelta delta = getDelta(project); if (delta == null) { mMustCompileResources = true; - buildAidlCompilationList(project, sourceFolderPathList); + + for (JavaGenerator generator : mGeneratorList) { + generator.prepareFullBuild(project, sourceFolderPathList); + } } else { - dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList); + dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList, mGeneratorList); delta.accept(dv); // record the state mMustCompileResources |= dv.getCompileResources(); - - if (dv.getForceAidlCompile()) { - buildAidlCompilationList(project, sourceFolderPathList); - } else { - // handle aidl modification, and update mMustCompileAidl - mergeAidlFileModifications(dv.getAidlToCompile(), - dv.getAidlToRemove()); + for (JavaGenerator generator : mGeneratorList) { + generator.doneVisiting(project, sourceFolderPathList); } // get the java package from the visitor @@ -496,7 +467,9 @@ public class PreCompilerBuilder extends BaseBuilder { // force a clean doClean(project, monitor); mMustCompileResources = true; - buildAidlCompilationList(project, sourceFolderPathList); + for (JavaGenerator generator : mGeneratorList) { + generator.prepareFullBuild(project, sourceFolderPathList); + } saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , mMustCompileResources); } @@ -505,10 +478,14 @@ public class PreCompilerBuilder extends BaseBuilder { handleResources(project, javaPackage, projectTarget, manifestFile, libProjects); } - // now handle the aidl stuff. - boolean aidlStatus = handleAidl(projectTarget, sourceFolderPathList, monitor); + // run the Java generators + boolean generatorStatus = false; + for (JavaGenerator generator : mGeneratorList) { + generatorStatus |= generator.compileFiles(this, + project, projectTarget, sourceFolderPathList, monitor); + } - if (aidlStatus == false && mMustCompileResources == false) { + if (generatorStatus == false && mMustCompileResources == false) { AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, Messages.Nothing_To_Compile); } @@ -551,8 +528,6 @@ public class PreCompilerBuilder extends BaseBuilder { protected void startupOnInitialize() { super.startupOnInitialize(); - mDerivedProgressMonitor = new DerivedProgressMonitor(); - IProject project = getProject(); // load the previous IFolder and java package. @@ -561,20 +536,16 @@ public class PreCompilerBuilder extends BaseBuilder { // get the source folder in which all the Java files are created mGenFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES); - // Load the current compile flags. We ask for true if not found to force a - // recompile. + // Load the current compile flags. We ask for true if not found to force a recompile. mMustCompileResources = loadProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, true); - boolean mustCompileAidl = loadProjectBooleanProperty(PROPERTY_COMPILE_AIDL, true); - // if we stored that we have to compile some aidl, we build the list that will compile them - // all - if (mustCompileAidl) { - IJavaProject javaProject = JavaCore.create(project); - ArrayList<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths( - javaProject); + IJavaProject javaProject = JavaCore.create(project); - buildAidlCompilationList(project, sourceFolderPathList); - } + // load the java generators + JavaGenerator aidlGenerator = new AidlGenerator(javaProject, mGenFolder); + mGeneratorList.add(aidlGenerator); + + mDerivedProgressMonitor = new DerivedProgressMonitor(mGenFolder); } /** @@ -671,16 +642,10 @@ public class PreCompilerBuilder extends BaseBuilder { private void execAapt(IProject project, IAndroidTarget projectTarget, String osOutputPath, String osResPath, String osManifestPath, IFolder packageFolder, ArrayList<IFolder> libResFolders, String customJavaPackage) throws CoreException { - // since the R.java file may be already existing in read-only - // mode we need to make it readable so that aapt can overwrite it - IFile rJavaFile = packageFolder.getFile(AndroidConstants.FN_RESOURCE_CLASS); - - // do the same for the Manifest.java class - IFile manifestJavaFile = packageFolder.getFile(AndroidConstants.FN_MANIFEST_CLASS); - - // we actually need to delete the manifest.java as it may become empty and + // 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(AndroidConstants.FN_MANIFEST_CLASS); manifestJavaFile.getLocation().toFile().delete(); // launch aapt: create the command line @@ -788,11 +753,6 @@ public class PreCompilerBuilder extends BaseBuilder { // if the return code was OK, we refresh the folder that // contains R.java to force a java recompile. if (execError == 0) { - // now add the R.java/Manifest.java to the list of file to be marked - // as derived. - mDerivedProgressMonitor.addFile(rJavaFile); - mDerivedProgressMonitor.addFile(manifestJavaFile); - // build has been done. reset the state of the builder mMustCompileResources = false; @@ -853,358 +813,4 @@ public class PreCompilerBuilder extends BaseBuilder { // This IFolder may not reference an actual existing folder. return mGenFolder.getFolder(packagePath); } - - /** - * Compiles aidl files into java. This will also removes old java files - * created from aidl files that are now gone. - * @param projectTarget Target of the project - * @param sourceFolders the list of source folders, relative to the workspace. - * @param monitor the progress monitor - * @returns true if it did something - * @throws CoreException - */ - private boolean handleAidl(IAndroidTarget projectTarget, ArrayList<IPath> sourceFolders, - IProgressMonitor monitor) throws CoreException { - if (mAidlToCompile.size() == 0 && mAidlToRemove.size() == 0) { - return false; - } - - // create the command line - String[] command = new String[4 + sourceFolders.size()]; - int index = 0; - command[index++] = projectTarget.getPath(IAndroidTarget.AIDL); - command[index++] = "-p" + Sdk.getCurrent().getTarget(getProject()).getPath( //$NON-NLS-1$ - IAndroidTarget.ANDROID_AIDL); - - // since the path are relative to the workspace and not the project itself, we need - // the workspace root. - IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); - for (IPath p : sourceFolders) { - IFolder f = wsRoot.getFolder(p); - command[index++] = "-I" + f.getLocation().toOSString(); //$NON-NLS-1$ - } - - // list of files that have failed compilation. - ArrayList<AidlData> stillNeedCompilation = new ArrayList<AidlData>(); - - // if an aidl file is being removed before we managed to compile it, it'll be in - // both list. We *need* to remove it from the compile list or it'll never go away. - for (AidlData aidlFile : mAidlToRemove) { - int pos = mAidlToCompile.indexOf(aidlFile); - if (pos != -1) { - mAidlToCompile.remove(pos); - } - } - - // loop until we've compile them all - for (AidlData aidlData : mAidlToCompile) { - // Remove the AIDL error markers from the aidl file - removeMarkersFromFile(aidlData.aidlFile, AndroidConstants.MARKER_AIDL); - - // get the path of the source file. - IPath sourcePath = aidlData.aidlFile.getLocation(); - String osSourcePath = sourcePath.toOSString(); - - IFile javaFile = getGenDestinationFile(aidlData, true /*createFolders*/, monitor); - - // finish to set the command line. - command[index] = osSourcePath; - command[index + 1] = javaFile.getLocation().toOSString(); - - // launch the process - if (execAidl(command, aidlData.aidlFile) == false) { - // aidl failed. File should be marked. We add the file to the list - // of file that will need compilation again. - stillNeedCompilation.add(aidlData); - - // and we move on to the next one. - continue; - } else { - // make sure the file will be marked as derived once we refresh the 'gen' source - // folder. - mDerivedProgressMonitor.addFile(javaFile); - } - } - - // change the list to only contains the file that have failed compilation - mAidlToCompile.clear(); - mAidlToCompile.addAll(stillNeedCompilation); - - // Remove the java files created from aidl files that have been removed. - for (AidlData aidlData : mAidlToRemove) { - IFile javaFile = getGenDestinationFile(aidlData, false /*createFolders*/, monitor); - if (javaFile.exists()) { - // This confirms the java file was generated by the builder, - // we can delete the aidlFile. - javaFile.getLocation().toFile().delete(); - } - } - - mAidlToRemove.clear(); - - // store the build state. If there are any files that failed to compile, we will - // force a full aidl compile on the next project open. (unless a full compilation succeed - // before the project is closed/re-opened.) - // TODO: Optimize by saving only the files that need compilation - saveProjectBooleanProperty(PROPERTY_COMPILE_AIDL , mAidlToCompile.size() > 0); - - return true; - } - - /** - * Returns the {@link IFile} handle to the destination file for a given aidl source file - * ({@link AidlData}). - * @param aidlData the data for the aidl source file. - * @param createFolders whether or not the parent folder of the destination should be created - * if it does not exist. - * @param monitor the progress monitor - * @return the handle to the destination file. - * @throws CoreException - */ - private IFile getGenDestinationFile(AidlData aidlData, boolean createFolders, - IProgressMonitor monitor) throws CoreException { - // build the destination folder path. - // Use the path of the source file, except for the path leading to its source folder, - // and for the last segment which is the filename. - int segmentToSourceFolderCount = aidlData.sourceFolder.getFullPath().segmentCount(); - IPath packagePath = aidlData.aidlFile.getFullPath().removeFirstSegments( - segmentToSourceFolderCount).removeLastSegments(1); - Path destinationPath = new Path(packagePath.toString()); - - // get an IFolder for this path. It's relative to the 'gen' folder already - IFolder destinationFolder = mGenFolder.getFolder(destinationPath); - - // create it if needed. - if (destinationFolder.exists() == false && createFolders) { - createFolder(destinationFolder, monitor); - } - - // Build the Java file name from the aidl name. - String javaName = aidlData.aidlFile.getName().replaceAll(AndroidConstants.RE_AIDL_EXT, - AndroidConstants.DOT_JAVA); - - // get the resource for the java file. - IFile javaFile = destinationFolder.getFile(javaName); - return javaFile; - } - - /** - * Creates the destination folder. Because - * {@link IFolder#create(boolean, boolean, IProgressMonitor)} only works if the parent folder - * already exists, this goes and ensure that all the parent folders actually exist, or it - * creates them as well. - * @param destinationFolder The folder to create - * @param monitor the {@link IProgressMonitor}, - * @throws CoreException - */ - private void createFolder(IFolder destinationFolder, IProgressMonitor monitor) - throws CoreException { - - // check the parent exist and create if necessary. - IContainer parent = destinationFolder.getParent(); - if (parent.getType() == IResource.FOLDER && parent.exists() == false) { - createFolder((IFolder)parent, monitor); - } - - // create the folder. - destinationFolder.create(true /*force*/, true /*local*/, - new SubProgressMonitor(monitor, 10)); - } - - /** - * Execute the aidl command line, parse the output, and mark the aidl file - * with any reported errors. - * @param command the String array containing the command line to execute. - * @param file The IFile object representing the aidl file being - * compiled. - * @return false if the exec failed, and build needs to be aborted. - */ - private boolean execAidl(String[] command, IFile file) { - // do the exec - try { - if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) { - StringBuilder sb = new StringBuilder(); - for (String c : command) { - sb.append(c); - sb.append(' '); - } - String cmd_line = sb.toString(); - AdtPlugin.printToConsole(getProject(), cmd_line); - } - - Process p = Runtime.getRuntime().exec(command); - - // list to store each line of stderr - ArrayList<String> results = new ArrayList<String>(); - - // get the output and return code from the process - int result = grabProcessOutput(p, results); - - // attempt to parse the error output - boolean error = parseAidlOutput(results, file); - - // If the process failed and we couldn't parse the output - // we print a message, mark the project and exit - if (result != 0 && error == true) { - // display the message in the console. - AdtPlugin.printErrorToConsole(getProject(), results.toArray()); - - // mark the project and exit - markProject(AndroidConstants.MARKER_ADT, Messages.Unparsed_AIDL_Errors, - IMarker.SEVERITY_ERROR); - return false; - } - } catch (IOException e) { - // mark the project and exit - String msg = String.format(Messages.AIDL_Exec_Error, command[0]); - markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); - return false; - } catch (InterruptedException e) { - // mark the project and exit - String msg = String.format(Messages.AIDL_Exec_Error, command[0]); - markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); - return false; - } - - return true; - } - - /** - * Goes through the build paths and fills the list of aidl files to compile - * ({@link #mAidlToCompile}). - * @param project The project. - * @param sourceFolderPathList The list of source folder paths. - */ - private void buildAidlCompilationList(IProject project, - ArrayList<IPath> sourceFolderPathList) { - IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); - for (IPath sourceFolderPath : sourceFolderPathList) { - IFolder sourceFolder = root.getFolder(sourceFolderPath); - // we don't look in the 'gen' source folder as there will be no source in there. - if (sourceFolder.exists() && sourceFolder.equals(mGenFolder) == false) { - scanFolderForAidl(sourceFolder, sourceFolder); - } - } - } - - /** - * Scans a folder and fills the list of aidl files to compile. - * @param sourceFolder the root source folder. - * @param folder The folder to scan. - */ - private void scanFolderForAidl(IFolder sourceFolder, IFolder folder) { - try { - IResource[] members = folder.members(); - for (IResource r : members) { - // get the type of the resource - switch (r.getType()) { - case IResource.FILE: - // if this a file, check that the file actually exist - // and that it's an aidl file - if (r.exists() && - AndroidConstants.EXT_AIDL.equalsIgnoreCase(r.getFileExtension())) { - mAidlToCompile.add(new AidlData(sourceFolder, (IFile)r)); - } - break; - case IResource.FOLDER: - // recursively go through children - scanFolderForAidl(sourceFolder, (IFolder)r); - break; - default: - // this would mean it's a project or the workspace root - // which is unlikely to happen. we do nothing - break; - } - } - } catch (CoreException e) { - // Couldn't get the members list for some reason. Just return. - } - } - - - /** - * Parse the output of aidl and mark the file with any errors. - * @param lines The output to parse. - * @param file The file to mark with error. - * @return true if the parsing failed, false if success. - */ - private boolean parseAidlOutput(ArrayList<String> lines, IFile file) { - // nothing to parse? just return false; - if (lines.size() == 0) { - return false; - } - - Matcher m; - - for (int i = 0; i < lines.size(); i++) { - String p = lines.get(i); - - m = sAidlPattern1.matcher(p); - if (m.matches()) { - // we can ignore group 1 which is the location since we already - // have a IFile object representing the aidl file. - String lineStr = m.group(2); - String msg = m.group(3); - - // get the line number - int line = 0; - try { - line = Integer.parseInt(lineStr); - } catch (NumberFormatException e) { - // looks like the string we extracted wasn't a valid - // file number. Parsing failed and we return true - return true; - } - - // mark the file - BaseProjectHelper.markResource(file, AndroidConstants.MARKER_AIDL, msg, line, - IMarker.SEVERITY_ERROR); - - // success, go to the next line - continue; - } - - // invalid line format, flag as error, and bail - return true; - } - - return false; - } - - /** - * Merge the current list of aidl file to compile/remove with the new one. - * @param toCompile List of file to compile - * @param toRemove List of file to remove - */ - private void mergeAidlFileModifications(ArrayList<AidlData> toCompile, - ArrayList<AidlData> toRemove) { - // loop through the new toRemove list, and add it to the old one, - // plus remove any file that was still to compile and that are now - // removed - for (AidlData r : toRemove) { - if (mAidlToRemove.indexOf(r) == -1) { - mAidlToRemove.add(r); - } - - int index = mAidlToCompile.indexOf(r); - if (index != -1) { - mAidlToCompile.remove(index); - } - } - - // now loop through the new files to compile and add it to the list. - // Also look for them in the remove list, this would mean that they - // were removed, then added back, and we shouldn't remove them, just - // recompile them. - for (AidlData r : toCompile) { - if (mAidlToCompile.indexOf(r) == -1) { - mAidlToCompile.add(r); - } - - int index = mAidlToRemove.indexOf(r); - if (index != -1) { - mAidlToRemove.remove(index); - } - } - } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java index 4776666..ed3f503 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java @@ -18,9 +18,10 @@ package com.android.ide.eclipse.adt.internal.build.builders; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.internal.build.JavaGenerator; import com.android.ide.eclipse.adt.internal.build.Messages; +import com.android.ide.eclipse.adt.internal.build.JavaGenerator.JavaGeneratorDeltaVisitor; import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder.BaseDeltaVisitor; -import com.android.ide.eclipse.adt.internal.build.builders.PreCompilerBuilder.AidlData; 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.io.IFileWrapper; @@ -39,6 +40,7 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import java.util.ArrayList; +import java.util.List; /** * Resource Delta visitor for the pre-compiler. @@ -56,16 +58,6 @@ import java.util.ArrayList; class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDeltaVisitor { - private enum AidlType { - UNKNOWN, INTERFACE, PARCELABLE; - } - - // See comment in #getAidlType() -// private final static Pattern sParcelablePattern = Pattern.compile( -// "^\\s*parcelable\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*;\\s*$"); -// -// private final static Pattern sInterfacePattern = Pattern.compile( -// "^\\s*interface\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?:\\{.*)?$"); // Result fields. /** @@ -76,17 +68,6 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements */ private boolean mCompileResources = false; - /** - * Aidl force recompilation flag. If true, we'll attempt to recompile all aidl files. - */ - private boolean mForceAidlCompile = false; - - /** List of .aidl files found that are modified or new. */ - private final ArrayList<AidlData> mAidlToCompile = new ArrayList<AidlData>(); - - /** List of .aidl files that have been removed. */ - private final ArrayList<AidlData> mAidlToRemove = new ArrayList<AidlData>(); - /** Manifest check/parsing flag. */ private boolean mCheckedManifestXml = false; @@ -109,34 +90,32 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements private IFolder mSourceFolder = null; /** List of source folders. */ - private ArrayList<IPath> mSourceFolders; + private List<IPath> mSourceFolders; private boolean mIsGenSourceFolder = false; + private final List<JavaGeneratorDeltaVisitor> mGeneratorDeltaVisitors = + new ArrayList<JavaGeneratorDeltaVisitor>(); private IWorkspaceRoot mRoot; - public PreCompilerDeltaVisitor(BaseBuilder builder, ArrayList<IPath> sourceFolders) { + + public PreCompilerDeltaVisitor(BaseBuilder builder, ArrayList<IPath> sourceFolders, + List<JavaGenerator> generators) { super(builder); mSourceFolders = sourceFolders; mRoot = ResourcesPlugin.getWorkspace().getRoot(); + + for (JavaGenerator generator : generators) { + JavaGeneratorDeltaVisitor dv = generator.getDeltaVisitor(); + dv.setWorkspaceRoot(mRoot); + mGeneratorDeltaVisitors.add(dv); + } } public boolean getCompileResources() { return mCompileResources; } - public boolean getForceAidlCompile() { - return mForceAidlCompile; - } - - public ArrayList<AidlData> getAidlToCompile() { - return mAidlToCompile; - } - - public ArrayList<AidlData> getAidlToRemove() { - return mAidlToRemove; - } - /** * Returns whether the manifest file was parsed/checked for error during the resource delta * visiting. @@ -296,28 +275,13 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements // we want a warning outputWarning = true; } else { - // this has to be a Java file created from an aidl file. - // Look for the source aidl file in all the source folders. - String aidlFileName = fileName.replaceAll(AndroidConstants.RE_JAVA_EXT, - AndroidConstants.DOT_AIDL); - - for (IPath sourceFolderPath : mSourceFolders) { - // do not search in the current source folder as it is the 'gen' folder. - if (sourceFolderPath.equals(mSourceFolder.getFullPath())) { - continue; - } - - IFolder sourceFolder = getFolder(sourceFolderPath); - if (sourceFolder != null) { - // go recursively, segment by segment. - // index starts at 2 (0 is project, 1 is 'gen' - IFile sourceFile = findFile(sourceFolder, segments, 2, aidlFileName); - - if (sourceFile != null) { - // found the source. add it to the list of files to compile - mAidlToCompile.add(new AidlData(sourceFolder, sourceFile)); + // look to see if this java file was generated by a generator. + if (AndroidConstants.EXT_JAVA.equalsIgnoreCase(file.getFileExtension())) { + for (JavaGeneratorDeltaVisitor dv : mGeneratorDeltaVisitors) { + if (dv.handleChangedGeneratedJavaFile( + mSourceFolder, file, mSourceFolders)) { outputWarning = true; - break; + break; // there shouldn't be 2 generators that handles the same file. } } } @@ -337,27 +301,8 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements } } else { // this is another source folder. - // We only care about aidl files being added/modified/removed. - - // get the extension of the resource - String ext = resource.getFileExtension(); - if (AndroidConstants.EXT_AIDL.equalsIgnoreCase(ext)) { - // first check whether it's a regular file or a parcelable. - AidlType type = getAidlType(file); - - if (type == AidlType.INTERFACE) { - if (kind == IResourceDelta.REMOVED) { - // we'll have to remove the generated file. - mAidlToRemove.add(new AidlData(mSourceFolder, file)); - } else if (mForceAidlCompile == false) { - // add the aidl file to the list of file to (re)compile - mAidlToCompile.add(new AidlData(mSourceFolder, file)); - } - } else { - // force recompilations of all Aidl Files. - mForceAidlCompile = true; - mAidlToCompile.clear(); - } + for (JavaGeneratorDeltaVisitor dv : mGeneratorDeltaVisitors) { + dv.handleChangedNonJavaFile(mSourceFolder, file, kind); } } @@ -458,32 +403,6 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements } /** - * Searches for and return a file in a folder. The file is defined by its segments, and a new - * name (replacing the last segment). - * @param folder the folder we are searching - * @param segments the segments of the file to search. - * @param index the index of the current segment we are looking for - * @param filename the new name to replace the last segment. - * @return the {@link IFile} representing the searched file, or null if not found - */ - private IFile findFile(IFolder folder, String[] segments, int index, String filename) { - boolean lastSegment = index == segments.length - 1; - IResource resource = folder.findMember(lastSegment ? filename : segments[index]); - if (resource != null && resource.exists()) { - if (lastSegment) { - if (resource.getType() == IResource.FILE) { - return (IFile)resource; - } - } else { - if (resource.getType() == IResource.FOLDER) { - return findFile((IFolder)resource, segments, index+1, filename); - } - } - } - return null; - } - - /** * Returns a handle to the folder identified by the given path in this container. * <p/>The different with {@link IContainer#getFolder(IPath)} is that this returns a non * null object only if the resource actually exists and is a folder (and not a file) @@ -499,55 +418,4 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements return null; } - /** - * Returns the type of the aidl file. Aidl files can either declare interfaces, or declare - * parcelables. This method will attempt to parse the file and return the type. If the type - * cannot be determined, then it will return {@link AidlType#UNKNOWN}. - * @param file The aidl file - * @return the type of the aidl. - * @throws CoreException - */ - private AidlType getAidlType(IFile file) throws CoreException { - // At this time, parsing isn't available, so we return UNKNOWN. This will force - // a recompilation of all aidl file as soon as one is changed. - return AidlType.UNKNOWN; - - // TODO: properly parse aidl file to determine type and generate dependency graphs. -// -// String className = file.getName().substring(0, -// file.getName().length() - AndroidConstants.DOT_AIDL.length()); -// -// InputStream input = file.getContents(true /* force*/); -// try { -// BufferedReader reader = new BufferedReader(new InputStreamReader(input)); -// String line; -// while ((line = reader.readLine()) != null) { -// if (line.length() == 0) { -// continue; -// } -// -// Matcher m = sParcelablePattern.matcher(line); -// if (m.matches() && m.group(1).equals(className)) { -// return AidlType.PARCELABLE; -// } -// -// m = sInterfacePattern.matcher(line); -// if (m.matches() && m.group(1).equals(className)) { -// return AidlType.INTERFACE; -// } -// } -// } catch (IOException e) { -// throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, -// "Error parsing aidl file", e)); -// } finally { -// try { -// input.close(); -// } catch (IOException e) { -// throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, -// "Error parsing aidl file", e)); -// } -// } -// -// return AidlType.UNKNOWN; - } } diff --git a/testapps/basicProjectWithAidl/.classpath b/testapps/basicProjectWithAidl/.classpath new file mode 100644 index 0000000..609aa00 --- /dev/null +++ b/testapps/basicProjectWithAidl/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="src" path="gen"/> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/testapps/basicProjectWithAidl/.project b/testapps/basicProjectWithAidl/.project new file mode 100644 index 0000000..24ed854 --- /dev/null +++ b/testapps/basicProjectWithAidl/.project @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>basicProjectWithAidl</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>com.android.ide.eclipse.adt.ApkBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>com.android.ide.eclipse.adt.AndroidNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/testapps/basicProjectWithAidl/AndroidManifest.xml b/testapps/basicProjectWithAidl/AndroidManifest.xml new file mode 100644 index 0000000..4cf7553 --- /dev/null +++ b/testapps/basicProjectWithAidl/AndroidManifest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + android:versionCode="1" + android:versionName="1.0" package="com.android.tests.basicprojectwithaidl"> + <application android:label="@string/app_name" android:icon="@drawable/icon"> + <activity android:name="com.android.tests.basicprojectwithaidlwithaidl.Main" + android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + <uses-sdk android:minSdkVersion="AOSP"/> +</manifest> diff --git a/testapps/basicProjectWithAidl/build.properties b/testapps/basicProjectWithAidl/build.properties new file mode 100644 index 0000000..ee52d86 --- /dev/null +++ b/testapps/basicProjectWithAidl/build.properties @@ -0,0 +1,17 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked in Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + diff --git a/testapps/basicProjectWithAidl/build.xml b/testapps/basicProjectWithAidl/build.xml new file mode 100644 index 0000000..9983df5 --- /dev/null +++ b/testapps/basicProjectWithAidl/build.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="basicProject" default="help"> + +<!-- The local.properties file is created and updated by the 'android' + tool. + It contains the path to the SDK. It should *NOT* be checked into + Version Control Systems. --> + <property file="local.properties" /> + + <!-- The build.properties file can be created by you and is never touched + by the 'android' tool. This is the place to change some of the + default property values used by the Ant rules. + Here are some properties you may want to change/update: + + source.dir + The name of the source directory. Default is 'src'. + out.dir + The name of the output directory. Default is 'bin'. + + Properties related to the SDK location or the project target should + be updated using the 'android' tool with the 'update' action. + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. + + --> + <property file="build.properties" /> + + <!-- The default.properties file is created and updated by the 'android' + tool, as well as ADT. + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. --> + <property file="default.properties" /> + + <!-- Custom Android task to deal with the project target, and import the + proper rules. + This requires ant 1.6.0 or above. --> + <path id="android.antlibs"> + <pathelement path="${sdk.dir}/tools/lib/anttasks.jar" /> + <pathelement path="${sdk.dir}/tools/lib/sdklib.jar" /> + <pathelement path="${sdk.dir}/tools/lib/androidprefs.jar" /> + </path> + + <taskdef name="setup" + classname="com.android.ant.SetupTask" + classpathref="android.antlibs" /> + +<!-- extension targets. Uncomment the ones where you want to do custom work + in between standard targets --> +<!-- + <target name="-pre-build"> + </target> + <target name="-pre-compile"> + </target> + + [This is typically used for code obfuscation. + Compiled code location: ${out.classes.absolute.dir} + If this is not done in place, override ${out.dex.input.absolute.dir}] + <target name="-post-compile"> + </target> +--> + + + <!-- Execute the Android Setup task that will setup some properties + specific to the target, and import the build rules files. + + The rules file is imported from + <SDK>/platforms/<target_platform>/ant/ant_rules_r#.xml + + To customize existing targets, there are two options: + - Customize only one target: + - copy/paste the target into this file, *before* the + <setup> task. + - customize it to your needs. + - Customize the whole script. + - copy/paste the content of the rules files (minus the top node) + into this file, *after* the <setup> task + - disable the import of the rules by changing the setup task + below to <setup import="false" />. + - customize to your needs. + --> + <setup /> + +</project> diff --git a/testapps/basicProjectWithAidl/default.properties b/testapps/basicProjectWithAidl/default.properties new file mode 100644 index 0000000..38db660 --- /dev/null +++ b/testapps/basicProjectWithAidl/default.properties @@ -0,0 +1,12 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "build.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-AOSP +proguard.config=../proguard.config diff --git a/testapps/basicProjectWithAidl/res/drawable/icon.png b/testapps/basicProjectWithAidl/res/drawable/icon.png Binary files differnew file mode 100644 index 0000000..a07c69f --- /dev/null +++ b/testapps/basicProjectWithAidl/res/drawable/icon.png diff --git a/testapps/basicProjectWithAidl/res/layout/main.xml b/testapps/basicProjectWithAidl/res/layout/main.xml new file mode 100644 index 0000000..783e4a0 --- /dev/null +++ b/testapps/basicProjectWithAidl/res/layout/main.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + > +<TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="Basic Project" + /> +</LinearLayout> + diff --git a/testapps/basicProjectWithAidl/res/values/strings.xml b/testapps/basicProjectWithAidl/res/values/strings.xml new file mode 100644 index 0000000..a7322d3 --- /dev/null +++ b/testapps/basicProjectWithAidl/res/values/strings.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">basicProject</string> +</resources> diff --git a/testapps/basicProjectWithAidl/src/com/android/tests/basicprojectwithaidl/ITest.aidl b/testapps/basicProjectWithAidl/src/com/android/tests/basicprojectwithaidl/ITest.aidl new file mode 100644 index 0000000..cb7a314 --- /dev/null +++ b/testapps/basicProjectWithAidl/src/com/android/tests/basicprojectwithaidl/ITest.aidl @@ -0,0 +1,7 @@ +package com.android.tests.basicprojectwithaidl; + +interface ITest { + Rect getRect(); + +} + diff --git a/testapps/basicProjectWithAidl/src/com/android/tests/basicprojectwithaidl/Main.java b/testapps/basicProjectWithAidl/src/com/android/tests/basicprojectwithaidl/Main.java new file mode 100644 index 0000000..eaed510 --- /dev/null +++ b/testapps/basicProjectWithAidl/src/com/android/tests/basicprojectwithaidl/Main.java @@ -0,0 +1,15 @@ +package com.android.tests.basicprojectwithaidl; + +import android.app.Activity; +import android.os.Bundle; + +public class Main extends Activity +{ + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + } +} diff --git a/testapps/basicProjectWithAidl/src/com/android/tests/basicprojectwithaidl/Rect.aidl b/testapps/basicProjectWithAidl/src/com/android/tests/basicprojectwithaidl/Rect.aidl new file mode 100644 index 0000000..734cf77 --- /dev/null +++ b/testapps/basicProjectWithAidl/src/com/android/tests/basicprojectwithaidl/Rect.aidl @@ -0,0 +1,5 @@ +package com.android.tests.basicprojectwithaidl; + +// Declare Rect so AIDL can find it and knows that it implements +// the parcelable protocol. +parcelable Rect;
\ No newline at end of file diff --git a/testapps/basicProjectWithAidl/src/com/android/tests/basicprojectwithaidl/Rect.java b/testapps/basicProjectWithAidl/src/com/android/tests/basicprojectwithaidl/Rect.java new file mode 100644 index 0000000..8e16926 --- /dev/null +++ b/testapps/basicProjectWithAidl/src/com/android/tests/basicprojectwithaidl/Rect.java @@ -0,0 +1,52 @@ +package com.android.tests.basicprojectwithaidl; + +import android.os.Parcel; +import android.os.Parcelable; + +public class Rect implements Parcelable { + public int left; + public int top; + public int right; + public int bottom; + + public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() { + public Rect createFromParcel(Parcel in) { + return new Rect(in); + } + + public Rect[] newArray(int size) { + return new Rect[size]; + } + }; + + public Rect() { + } + + private Rect(Parcel in) { + readFromParcel(in); + } + + public void writeToParcel(Parcel out) { + out.writeInt(left); + out.writeInt(top); + out.writeInt(right); + out.writeInt(bottom); + } + + public void readFromParcel(Parcel in) { + left = in.readInt(); + top = in.readInt(); + right = in.readInt(); + bottom = in.readInt(); + } + + public int describeContents() { + // TODO Auto-generated method stub + return 0; + } + + public void writeToParcel(Parcel arg0, int arg1) { + // TODO Auto-generated method stub + + } +} |