diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java | 484 |
1 files changed, 484 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java new file mode 100644 index 000000000..806fa9c40 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java @@ -0,0 +1,484 @@ +/* + * 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.SdkConstants; +import com.android.annotations.NonNull; +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +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.sdklib.BuildToolInfo; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.io.FileOp; + +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.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.NullProgressMonitor; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.jdt.core.IJavaProject; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link SourceProcessor} for aidl files. + * + */ +public class AidlProcessor extends SourceProcessor { + + private static final String PROPERTY_COMPILE_AIDL = "compileAidl"; //$NON-NLS-1$ + + /** + * Single line aidl error<br> + * {@code <path>:<line>: <error>}<br> + * or<br> + * {@code <path>:<line> <error>}<br> + */ + private static Pattern sAidlPattern1 = Pattern.compile("^(.+?):(\\d+):?\\s(.+)$"); //$NON-NLS-1$ + + private final static Set<String> EXTENSIONS = Collections.singleton(SdkConstants.EXT_AIDL); + + 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*(?:\\{.*)?$"); + + + public AidlProcessor(@NonNull IJavaProject javaProject, @NonNull BuildToolInfo buildToolInfo, + @NonNull IFolder genFolder) { + super(javaProject, buildToolInfo, genFolder); + } + + @Override + protected Set<String> getExtensions() { + return EXTENSIONS; + } + + @Override + protected String getSavePropertyName() { + return PROPERTY_COMPILE_AIDL; + } + + @Override + protected void doCompileFiles(List<IFile> sources, BaseBuilder builder, + IProject project, IAndroidTarget projectTarget, + List<IPath> sourceFolders, List<IFile> notCompiledOut, List<File> libraryProjectsOut, + IProgressMonitor monitor) throws CoreException { + // create the command line + List<String> commandList = new ArrayList<String>( + 4 + sourceFolders.size() + libraryProjectsOut.size()); + commandList.add(getBuildToolInfo().getPath(BuildToolInfo.PathId.AIDL)); + commandList.add(quote("-p" + projectTarget.getPath(IAndroidTarget.ANDROID_AIDL))); //$NON-NLS-1$ + + // 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); + if (f.exists()) { // if the resource doesn't exist, getLocation will return null. + commandList.add(quote("-I" + f.getLocation().toOSString())); //$NON-NLS-1$ + } + } + + for (File libOut : libraryProjectsOut) { + // FIXME: make folder configurable + File aidlFile = new File(libOut, SdkConstants.FD_AIDL); + if (aidlFile.isDirectory()) { + commandList.add(quote("-I" + aidlFile.getAbsolutePath())); //$NON-NLS-1$ + } + } + + // convert to array with 2 extra strings for the in/out file paths. + int index = commandList.size(); + String[] commands = commandList.toArray(new String[index + 2]); + + boolean verbose = AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE; + + // remove the generic marker from the project + builder.removeMarkersFromResource(project, AdtConstants.MARKER_AIDL); + + // prepare the two output folders. + IFolder genFolder = getGenFolder(); + IFolder projectOut = BaseProjectHelper.getAndroidOutputFolder(project); + IFolder aidlOutFolder = projectOut.getFolder(SdkConstants.FD_AIDL); + if (aidlOutFolder.exists() == false) { + aidlOutFolder.create(true /*force*/, true /*local*/, + new SubProgressMonitor(monitor, 10)); + } + + boolean success = false; + + // loop until we've compile them all + for (IFile sourceFile : sources) { + if (verbose) { + String name = sourceFile.getName(); + IPath sourceFolderPath = getSourceFolderFor(sourceFile); + if (sourceFolderPath != null) { + // make a path to the source file relative to the source folder. + IPath relative = sourceFile.getFullPath().makeRelativeTo(sourceFolderPath); + name = relative.toString(); + } + AdtPlugin.printToConsole(project, "AIDL: " + name); + } + + // Remove the AIDL error markers from the aidl file + builder.removeMarkersFromResource(sourceFile, AdtConstants.MARKER_AIDL); + + // get the path of the source file. + IPath sourcePath = sourceFile.getLocation(); + String osSourcePath = sourcePath.toOSString(); + + // look if we already know the output + SourceFileData data = getFileData(sourceFile); + if (data == null) { + data = new SourceFileData(sourceFile); + addData(data); + } + + // if there's no output file yet, compute it. + if (data.getOutput() == null) { + IFile javaFile = getAidlOutputFile(sourceFile, genFolder, + true /*replaceExt*/, true /*createFolders*/, monitor); + data.setOutputFile(javaFile); + } + + // finish to set the command line. + commands[index] = quote(osSourcePath); + commands[index + 1] = quote(data.getOutput().getLocation().toOSString()); + + // launch the process + if (execAidl(builder, project, commands, sourceFile, verbose) == false) { + // aidl failed. File should be marked. We add the file to the list + // of file that will need compilation again. + notCompiledOut.add(sourceFile); + } else { + // Success. we'll return that we generated code + setCompilationStatus(COMPILE_STATUS_CODE); + success = true; + + // Also copy the file to the bin folder. + IFile aidlOutFile = getAidlOutputFile(sourceFile, aidlOutFolder, + false /*replaceExt*/, true /*createFolders*/, monitor); + + FileOp op = new FileOp(); + try { + op.copyFile(sourceFile.getLocation().toFile(), + aidlOutFile.getLocation().toFile()); + } catch (IOException e) { + } + } + } + + if (success) { + aidlOutFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); + } + } + + @Override + protected void loadOutputAndDependencies() { + IProgressMonitor monitor = new NullProgressMonitor(); + IFolder genFolder = getGenFolder(); + + Collection<SourceFileData> dataList = getAllFileData(); + for (SourceFileData data : dataList) { + try { + IFile javaFile = getAidlOutputFile(data.getSourceFile(), genFolder, + true /*replaceExt*/, false /*createFolders*/, monitor); + data.setOutputFile(javaFile); + } catch (CoreException e) { + // ignore, we're not asking to create the folder so this won't happen anyway. + } + + } + } + + /** + * 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. + * @param verbose the build verbosity + * @return false if the exec failed, and build needs to be aborted. + */ + private boolean execAidl(BaseBuilder builder, IProject project, String[] command, IFile file, + boolean verbose) { + // do the exec + try { + if (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> stdErr = new ArrayList<String>(); + + // get the output and return code from the process + int returnCode = BuildHelper.grabProcessOutput(project, p, stdErr); + + if (stdErr.size() > 0) { + // attempt to parse the error output + boolean parsingError = parseAidlOutput(stdErr, file); + + // If the process failed and we couldn't parse the output + // we print a message, mark the project and exit + if (returnCode != 0) { + + if (parsingError || verbose) { + // display the message in the console. + if (parsingError) { + AdtPlugin.printErrorToConsole(project, stdErr.toArray()); + + // mark the project + BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL, + Messages.Unparsed_AIDL_Errors, IMarker.SEVERITY_ERROR); + } else { + AdtPlugin.printToConsole(project, stdErr.toArray()); + } + } + return false; + } + } else if (returnCode != 0) { + // no stderr output but exec failed. + String msg = String.format(Messages.AIDL_Exec_Error_d, returnCode); + + BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL, + msg, IMarker.SEVERITY_ERROR); + + return false; + } + } catch (IOException e) { + // mark the project and exit + String msg = String.format(Messages.AIDL_Exec_Error_s, command[0]); + BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL, msg, + IMarker.SEVERITY_ERROR); + return false; + } catch (InterruptedException e) { + // mark the project and exit + String msg = String.format(Messages.AIDL_Exec_Error_s, command[0]); + BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL, 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, AdtConstants.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; + } + + /** + * Returns the {@link IFile} handle to the destination file for a given aidl source file + * ({@link AidlData}). + * @param sourceFile The source file + * @param outputFolder the top level output folder (not including the package folders) + * @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 getAidlOutputFile(IFile sourceFile, IFolder outputFolder, boolean replaceExt, + boolean createFolders, IProgressMonitor monitor) throws CoreException { + + IPath sourceFolderPath = getSourceFolderFor(sourceFile); + + // this really shouldn't happen since the sourceFile must be in a source folder + // since it comes from the delta visitor + if (sourceFolderPath != null) { + // make a path to the source file relative to the source folder. + IPath relative = sourceFile.getFullPath().makeRelativeTo(sourceFolderPath); + // remove the file name. This is now the destination folder. + relative = relative.removeLastSegments(1); + + // get an IFolder for this path. + IFolder destinationFolder = outputFolder.getFolder(relative); + + // create it if needed. + if (destinationFolder.exists() == false && createFolders) { + createFolder(destinationFolder, monitor); + } + + // Build the Java file name from the aidl name. + String javaName; + if (replaceExt) { + javaName = sourceFile.getName().replaceAll( + AdtConstants.RE_AIDL_EXT, SdkConstants.DOT_JAVA); + } else { + javaName = sourceFile.getName(); + } + + // get the resource for the java file. + IFile javaFile = destinationFolder.getFile(javaName); + return javaFile; + } + + return null; + } + + /** + * 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() - SdkConstants.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; + } +} |