1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.build; 18 19 import com.android.SdkConstants; 20 import com.android.annotations.NonNull; 21 import com.android.ide.eclipse.adt.AdtConstants; 22 import com.android.ide.eclipse.adt.AdtPlugin; 23 import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder; 24 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 25 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; 26 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 27 import com.android.sdklib.BuildToolInfo; 28 import com.android.sdklib.IAndroidTarget; 29 import com.android.sdklib.io.FileOp; 30 31 import org.eclipse.core.resources.IContainer; 32 import org.eclipse.core.resources.IFile; 33 import org.eclipse.core.resources.IFolder; 34 import org.eclipse.core.resources.IMarker; 35 import org.eclipse.core.resources.IProject; 36 import org.eclipse.core.resources.IResource; 37 import org.eclipse.core.resources.IWorkspaceRoot; 38 import org.eclipse.core.resources.ResourcesPlugin; 39 import org.eclipse.core.runtime.CoreException; 40 import org.eclipse.core.runtime.IPath; 41 import org.eclipse.core.runtime.IProgressMonitor; 42 import org.eclipse.core.runtime.NullProgressMonitor; 43 import org.eclipse.core.runtime.SubProgressMonitor; 44 import org.eclipse.jdt.core.IJavaProject; 45 46 import java.io.File; 47 import java.io.IOException; 48 import java.util.ArrayList; 49 import java.util.Collection; 50 import java.util.Collections; 51 import java.util.List; 52 import java.util.Set; 53 import java.util.regex.Matcher; 54 import java.util.regex.Pattern; 55 56 /** 57 * A {@link SourceProcessor} for aidl files. 58 * 59 */ 60 public class AidlProcessor extends SourceProcessor { 61 62 private static final String PROPERTY_COMPILE_AIDL = "compileAidl"; //$NON-NLS-1$ 63 64 /** 65 * Single line aidl error<br> 66 * {@code <path>:<line>: <error>}<br> 67 * or<br> 68 * {@code <path>:<line> <error>}<br> 69 */ 70 private static Pattern sAidlPattern1 = Pattern.compile("^(.+?):(\\d+):?\\s(.+)$"); //$NON-NLS-1$ 71 72 private final static Set<String> EXTENSIONS = Collections.singleton(SdkConstants.EXT_AIDL); 73 74 private enum AidlType { 75 UNKNOWN, INTERFACE, PARCELABLE; 76 } 77 78 // See comment in #getAidlType() 79 // private final static Pattern sParcelablePattern = Pattern.compile( 80 // "^\\s*parcelable\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*;\\s*$"); 81 // 82 // private final static Pattern sInterfacePattern = Pattern.compile( 83 // "^\\s*interface\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?:\\{.*)?$"); 84 85 86 public AidlProcessor(@NonNull IJavaProject javaProject, @NonNull BuildToolInfo buildToolInfo, 87 @NonNull IFolder genFolder) { 88 super(javaProject, buildToolInfo, genFolder); 89 } 90 91 @Override 92 protected Set<String> getExtensions() { 93 return EXTENSIONS; 94 } 95 96 @Override 97 protected String getSavePropertyName() { 98 return PROPERTY_COMPILE_AIDL; 99 } 100 101 @Override 102 protected void doCompileFiles(List<IFile> sources, BaseBuilder builder, 103 IProject project, IAndroidTarget projectTarget, 104 List<IPath> sourceFolders, List<IFile> notCompiledOut, List<File> libraryProjectsOut, 105 IProgressMonitor monitor) throws CoreException { 106 // create the command line 107 List<String> commandList = new ArrayList<String>( 108 4 + sourceFolders.size() + libraryProjectsOut.size()); 109 commandList.add(getBuildToolInfo().getPath(BuildToolInfo.PathId.AIDL)); 110 commandList.add(quote("-p" + projectTarget.getPath(IAndroidTarget.ANDROID_AIDL))); //$NON-NLS-1$ 111 112 // since the path are relative to the workspace and not the project itself, we need 113 // the workspace root. 114 IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); 115 for (IPath p : sourceFolders) { 116 IFolder f = wsRoot.getFolder(p); 117 if (f.exists()) { // if the resource doesn't exist, getLocation will return null. 118 commandList.add(quote("-I" + f.getLocation().toOSString())); //$NON-NLS-1$ 119 } 120 } 121 122 for (File libOut : libraryProjectsOut) { 123 // FIXME: make folder configurable 124 File aidlFile = new File(libOut, SdkConstants.FD_AIDL); 125 if (aidlFile.isDirectory()) { 126 commandList.add(quote("-I" + aidlFile.getAbsolutePath())); //$NON-NLS-1$ 127 } 128 } 129 130 // convert to array with 2 extra strings for the in/out file paths. 131 int index = commandList.size(); 132 String[] commands = commandList.toArray(new String[index + 2]); 133 134 boolean verbose = AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE; 135 136 // remove the generic marker from the project 137 builder.removeMarkersFromResource(project, AdtConstants.MARKER_AIDL); 138 139 // prepare the two output folders. 140 IFolder genFolder = getGenFolder(); 141 IFolder projectOut = BaseProjectHelper.getAndroidOutputFolder(project); 142 IFolder aidlOutFolder = projectOut.getFolder(SdkConstants.FD_AIDL); 143 if (aidlOutFolder.exists() == false) { 144 aidlOutFolder.create(true /*force*/, true /*local*/, 145 new SubProgressMonitor(monitor, 10)); 146 } 147 148 boolean success = false; 149 150 // loop until we've compile them all 151 for (IFile sourceFile : sources) { 152 if (verbose) { 153 String name = sourceFile.getName(); 154 IPath sourceFolderPath = getSourceFolderFor(sourceFile); 155 if (sourceFolderPath != null) { 156 // make a path to the source file relative to the source folder. 157 IPath relative = sourceFile.getFullPath().makeRelativeTo(sourceFolderPath); 158 name = relative.toString(); 159 } 160 AdtPlugin.printToConsole(project, "AIDL: " + name); 161 } 162 163 // Remove the AIDL error markers from the aidl file 164 builder.removeMarkersFromResource(sourceFile, AdtConstants.MARKER_AIDL); 165 166 // get the path of the source file. 167 IPath sourcePath = sourceFile.getLocation(); 168 String osSourcePath = sourcePath.toOSString(); 169 170 // look if we already know the output 171 SourceFileData data = getFileData(sourceFile); 172 if (data == null) { 173 data = new SourceFileData(sourceFile); 174 addData(data); 175 } 176 177 // if there's no output file yet, compute it. 178 if (data.getOutput() == null) { 179 IFile javaFile = getAidlOutputFile(sourceFile, genFolder, 180 true /*replaceExt*/, true /*createFolders*/, monitor); 181 data.setOutputFile(javaFile); 182 } 183 184 // finish to set the command line. 185 commands[index] = quote(osSourcePath); 186 commands[index + 1] = quote(data.getOutput().getLocation().toOSString()); 187 188 // launch the process 189 if (execAidl(builder, project, commands, sourceFile, verbose) == false) { 190 // aidl failed. File should be marked. We add the file to the list 191 // of file that will need compilation again. 192 notCompiledOut.add(sourceFile); 193 } else { 194 // Success. we'll return that we generated code 195 setCompilationStatus(COMPILE_STATUS_CODE); 196 success = true; 197 198 // Also copy the file to the bin folder. 199 IFile aidlOutFile = getAidlOutputFile(sourceFile, aidlOutFolder, 200 false /*replaceExt*/, true /*createFolders*/, monitor); 201 202 FileOp op = new FileOp(); 203 try { 204 op.copyFile(sourceFile.getLocation().toFile(), 205 aidlOutFile.getLocation().toFile()); 206 } catch (IOException e) { 207 } 208 } 209 } 210 211 if (success) { 212 aidlOutFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); 213 } 214 } 215 216 @Override 217 protected void loadOutputAndDependencies() { 218 IProgressMonitor monitor = new NullProgressMonitor(); 219 IFolder genFolder = getGenFolder(); 220 221 Collection<SourceFileData> dataList = getAllFileData(); 222 for (SourceFileData data : dataList) { 223 try { 224 IFile javaFile = getAidlOutputFile(data.getSourceFile(), genFolder, 225 true /*replaceExt*/, false /*createFolders*/, monitor); 226 data.setOutputFile(javaFile); 227 } catch (CoreException e) { 228 // ignore, we're not asking to create the folder so this won't happen anyway. 229 } 230 231 } 232 } 233 234 /** 235 * Execute the aidl command line, parse the output, and mark the aidl file 236 * with any reported errors. 237 * @param command the String array containing the command line to execute. 238 * @param file The IFile object representing the aidl file being 239 * compiled. 240 * @param verbose the build verbosity 241 * @return false if the exec failed, and build needs to be aborted. 242 */ 243 private boolean execAidl(BaseBuilder builder, IProject project, String[] command, IFile file, 244 boolean verbose) { 245 // do the exec 246 try { 247 if (verbose) { 248 StringBuilder sb = new StringBuilder(); 249 for (String c : command) { 250 sb.append(c); 251 sb.append(' '); 252 } 253 String cmd_line = sb.toString(); 254 AdtPlugin.printToConsole(project, cmd_line); 255 } 256 257 Process p = Runtime.getRuntime().exec(command); 258 259 // list to store each line of stderr 260 ArrayList<String> stdErr = new ArrayList<String>(); 261 262 // get the output and return code from the process 263 int returnCode = BuildHelper.grabProcessOutput(project, p, stdErr); 264 265 if (stdErr.size() > 0) { 266 // attempt to parse the error output 267 boolean parsingError = parseAidlOutput(stdErr, file); 268 269 // If the process failed and we couldn't parse the output 270 // we print a message, mark the project and exit 271 if (returnCode != 0) { 272 273 if (parsingError || verbose) { 274 // display the message in the console. 275 if (parsingError) { 276 AdtPlugin.printErrorToConsole(project, stdErr.toArray()); 277 278 // mark the project 279 BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL, 280 Messages.Unparsed_AIDL_Errors, IMarker.SEVERITY_ERROR); 281 } else { 282 AdtPlugin.printToConsole(project, stdErr.toArray()); 283 } 284 } 285 return false; 286 } 287 } else if (returnCode != 0) { 288 // no stderr output but exec failed. 289 String msg = String.format(Messages.AIDL_Exec_Error_d, returnCode); 290 291 BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL, 292 msg, IMarker.SEVERITY_ERROR); 293 294 return false; 295 } 296 } catch (IOException e) { 297 // mark the project and exit 298 String msg = String.format(Messages.AIDL_Exec_Error_s, command[0]); 299 BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL, msg, 300 IMarker.SEVERITY_ERROR); 301 return false; 302 } catch (InterruptedException e) { 303 // mark the project and exit 304 String msg = String.format(Messages.AIDL_Exec_Error_s, command[0]); 305 BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL, msg, 306 IMarker.SEVERITY_ERROR); 307 return false; 308 } 309 310 return true; 311 } 312 313 /** 314 * Parse the output of aidl and mark the file with any errors. 315 * @param lines The output to parse. 316 * @param file The file to mark with error. 317 * @return true if the parsing failed, false if success. 318 */ 319 private boolean parseAidlOutput(ArrayList<String> lines, IFile file) { 320 // nothing to parse? just return false; 321 if (lines.size() == 0) { 322 return false; 323 } 324 325 Matcher m; 326 327 for (int i = 0; i < lines.size(); i++) { 328 String p = lines.get(i); 329 330 m = sAidlPattern1.matcher(p); 331 if (m.matches()) { 332 // we can ignore group 1 which is the location since we already 333 // have a IFile object representing the aidl file. 334 String lineStr = m.group(2); 335 String msg = m.group(3); 336 337 // get the line number 338 int line = 0; 339 try { 340 line = Integer.parseInt(lineStr); 341 } catch (NumberFormatException e) { 342 // looks like the string we extracted wasn't a valid 343 // file number. Parsing failed and we return true 344 return true; 345 } 346 347 // mark the file 348 BaseProjectHelper.markResource(file, AdtConstants.MARKER_AIDL, msg, line, 349 IMarker.SEVERITY_ERROR); 350 351 // success, go to the next line 352 continue; 353 } 354 355 // invalid line format, flag as error, and bail 356 return true; 357 } 358 359 return false; 360 } 361 362 /** 363 * Returns the {@link IFile} handle to the destination file for a given aidl source file 364 * ({@link AidlData}). 365 * @param sourceFile The source file 366 * @param outputFolder the top level output folder (not including the package folders) 367 * @param createFolders whether or not the parent folder of the destination should be created 368 * if it does not exist. 369 * @param monitor the progress monitor 370 * @return the handle to the destination file. 371 * @throws CoreException 372 */ 373 private IFile getAidlOutputFile(IFile sourceFile, IFolder outputFolder, boolean replaceExt, 374 boolean createFolders, IProgressMonitor monitor) throws CoreException { 375 376 IPath sourceFolderPath = getSourceFolderFor(sourceFile); 377 378 // this really shouldn't happen since the sourceFile must be in a source folder 379 // since it comes from the delta visitor 380 if (sourceFolderPath != null) { 381 // make a path to the source file relative to the source folder. 382 IPath relative = sourceFile.getFullPath().makeRelativeTo(sourceFolderPath); 383 // remove the file name. This is now the destination folder. 384 relative = relative.removeLastSegments(1); 385 386 // get an IFolder for this path. 387 IFolder destinationFolder = outputFolder.getFolder(relative); 388 389 // create it if needed. 390 if (destinationFolder.exists() == false && createFolders) { 391 createFolder(destinationFolder, monitor); 392 } 393 394 // Build the Java file name from the aidl name. 395 String javaName; 396 if (replaceExt) { 397 javaName = sourceFile.getName().replaceAll( 398 AdtConstants.RE_AIDL_EXT, SdkConstants.DOT_JAVA); 399 } else { 400 javaName = sourceFile.getName(); 401 } 402 403 // get the resource for the java file. 404 IFile javaFile = destinationFolder.getFile(javaName); 405 return javaFile; 406 } 407 408 return null; 409 } 410 411 /** 412 * Creates the destination folder. Because 413 * {@link IFolder#create(boolean, boolean, IProgressMonitor)} only works if the parent folder 414 * already exists, this goes and ensure that all the parent folders actually exist, or it 415 * creates them as well. 416 * @param destinationFolder The folder to create 417 * @param monitor the {@link IProgressMonitor}, 418 * @throws CoreException 419 */ 420 private void createFolder(IFolder destinationFolder, IProgressMonitor monitor) 421 throws CoreException { 422 423 // check the parent exist and create if necessary. 424 IContainer parent = destinationFolder.getParent(); 425 if (parent.getType() == IResource.FOLDER && parent.exists() == false) { 426 createFolder((IFolder)parent, monitor); 427 } 428 429 // create the folder. 430 destinationFolder.create(true /*force*/, true /*local*/, 431 new SubProgressMonitor(monitor, 10)); 432 } 433 434 /** 435 * Returns the type of the aidl file. Aidl files can either declare interfaces, or declare 436 * parcelables. This method will attempt to parse the file and return the type. If the type 437 * cannot be determined, then it will return {@link AidlType#UNKNOWN}. 438 * @param file The aidl file 439 * @return the type of the aidl. 440 */ 441 private static AidlType getAidlType(IFile file) { 442 // At this time, parsing isn't available, so we return UNKNOWN. This will force 443 // a recompilation of all aidl file as soon as one is changed. 444 return AidlType.UNKNOWN; 445 446 // TODO: properly parse aidl file to determine type and generate dependency graphs. 447 // 448 // String className = file.getName().substring(0, 449 // file.getName().length() - SdkConstants.DOT_AIDL.length()); 450 // 451 // InputStream input = file.getContents(true /* force*/); 452 // try { 453 // BufferedReader reader = new BufferedReader(new InputStreateader(input)); 454 // String line; 455 // while ((line = reader.readLine()) != null) { 456 // if (line.length() == 0) { 457 // continue; 458 // } 459 // 460 // Matcher m = sParcelablePattern.matcher(line); 461 // if (m.matches() && m.group(1).equals(className)) { 462 // return AidlType.PARCELABLE; 463 // } 464 // 465 // m = sInterfacePattern.matcher(line); 466 // if (m.matches() && m.group(1).equals(className)) { 467 // return AidlType.INTERFACE; 468 // } 469 // } 470 // } catch (IOException e) { 471 // throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, 472 // "Error parsing aidl file", e)); 473 // } finally { 474 // try { 475 // input.close(); 476 // } catch (IOException e) { 477 // throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, 478 // "Error parsing aidl file", e)); 479 // } 480 // } 481 // 482 // return AidlType.UNKNOWN; 483 } 484 } 485