blob: 806fa9c40579a2a7044263dad90124cbfc58fe74 [file] [log] [blame]
/*
* 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;
}
}