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.AndroidConstants; 20 import com.android.ide.eclipse.adt.AdtConstants; 21 import com.android.ide.eclipse.adt.AdtPlugin; 22 import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder; 23 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 24 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; 25 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 26 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 27 import com.android.resources.ResourceFolderType; 28 import com.android.sdklib.IAndroidTarget; 29 import com.android.sdklib.SdkConstants; 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.IResourceDelta; 38 import org.eclipse.core.resources.IWorkspaceRoot; 39 import org.eclipse.core.resources.ResourcesPlugin; 40 import org.eclipse.core.runtime.CoreException; 41 import org.eclipse.core.runtime.IPath; 42 import org.eclipse.core.runtime.IProgressMonitor; 43 import org.eclipse.core.runtime.Path; 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.List; 51 import java.util.regex.Matcher; 52 import java.util.regex.Pattern; 53 54 /** 55 * A {@link SourceProcessor} for RenderScript files. 56 * 57 */ 58 public class RenderScriptProcessor extends SourceProcessor { 59 60 private static final String PROPERTY_COMPILE_RS = "compileRenderScript"; //$NON-NLS-1$ 61 62 /** 63 * Single line llvm-rs-cc error: {@code <path>:<line>:<col>: <error>} 64 */ 65 private static Pattern sLlvmPattern1 = Pattern.compile("^(.+?):(\\d+):(\\d+):\\s(.+)$"); //$NON-NLS-1$ 66 67 private static class RsChangeHandler extends SourceChangeHandler { 68 69 @Override 70 public boolean handleGeneratedFile(IFile file, int kind) { 71 boolean r = super.handleGeneratedFile(file, kind); 72 if (r == false && 73 kind == IResourceDelta.REMOVED && 74 AdtConstants.EXT_DEP.equalsIgnoreCase(file.getFileExtension())) { 75 // This looks to be a dependency file. 76 // For future-proofness let's make sure this dependency file was generated by 77 // this processor even if it's the only processor using them for now. 78 79 // look for the original file. 80 // We know we are in the gen folder, so make a path to the dependency file 81 // relative to the gen folder. Convert this into a Renderscript source file, 82 // and look to see if this file exists. 83 SourceProcessor processor = getProcessor(); 84 IFolder genFolder = processor.getGenFolder(); 85 IPath relative = file.getFullPath().makeRelativeTo(genFolder.getFullPath()); 86 // remove the file name segment 87 relative = relative.removeLastSegments(1); 88 // add the file name of a Renderscript file. 89 relative = relative.append(file.getName().replaceAll(AdtConstants.RE_DEP_EXT, 90 AdtConstants.DOT_RS)); 91 92 // now look for a match in the source folders. 93 List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths( 94 processor.getJavaProject()); 95 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); 96 97 for (IPath sourceFolderPath : sourceFolders) { 98 IFolder sourceFolder = root.getFolder(sourceFolderPath); 99 // we don't look in the 'gen' source folder as there will be no source in there. 100 if (sourceFolder.exists() && sourceFolder.equals(genFolder) == false) { 101 IFile sourceFile = sourceFolder.getFile(relative); 102 SourceFileData data = processor.getFileData(sourceFile); 103 if (data != null) { 104 addFileToCompile(sourceFile); 105 return true; 106 } 107 } 108 } 109 } 110 111 return r; 112 } 113 114 @Override 115 protected boolean filterResourceFolder(IContainer folder) { 116 return ResourceFolderType.RAW.getName().equals(folder.getName()); 117 } 118 } 119 120 public RenderScriptProcessor(IJavaProject javaProject, IFolder genFolder) { 121 super(javaProject, genFolder, new RsChangeHandler()); 122 } 123 124 @Override 125 protected String getExtension() { 126 return AdtConstants.EXT_RS; 127 } 128 129 @Override 130 protected String getSavePropertyName() { 131 return PROPERTY_COMPILE_RS; 132 } 133 134 @Override 135 protected void doCompileFiles(List<IFile> sources, BaseBuilder builder, 136 IProject project, IAndroidTarget projectTarget, int targetApi, 137 List<IPath> sourceFolders, List<IFile> notCompiledOut, IProgressMonitor monitor) 138 throws CoreException { 139 140 String sdkOsPath = Sdk.getCurrent().getSdkLocation(); 141 142 IFolder genFolder = getGenFolder(); 143 144 IFolder rawFolder = project.getFolder( 145 new Path(SdkConstants.FD_RES).append(AndroidConstants.FD_RES_RAW)); 146 147 int depIndex; 148 149 // make sure the target api value is good. Must be 11+ or llvm-rs-cc complains. 150 if (targetApi < 11) { 151 targetApi = 11; 152 } 153 154 // create the command line 155 String[] command = new String[15]; 156 int index = 0; 157 command[index++] = quote(sdkOsPath + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER 158 + SdkConstants.FN_RENDERSCRIPT); 159 command[index++] = "-I"; //$NON-NLS-1$ 160 command[index++] = quote(projectTarget.getPath(IAndroidTarget.ANDROID_RS_CLANG)); 161 command[index++] = "-I"; //$NON-NLS-1$ 162 command[index++] = quote(projectTarget.getPath(IAndroidTarget.ANDROID_RS)); 163 command[index++] = "-p"; //$NON-NLS-1$ 164 command[index++] = quote(genFolder.getLocation().toOSString()); 165 command[index++] = "-o"; //$NON-NLS-1$ 166 command[index++] = quote(rawFolder.getLocation().toOSString()); 167 168 command[index++] = "-target-api"; //$NON-NLS-1$ 169 command[index++] = Integer.toString(targetApi); 170 171 command[index++] = "-d"; //$NON-NLS-1$ 172 command[depIndex = index++] = null; 173 command[index++] = "-MD"; //$NON-NLS-1$ 174 175 boolean verbose = AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE; 176 boolean someSuccess = false; 177 178 // remove the generic marker from the project 179 builder.removeMarkersFromResource(project, AdtConstants.MARKER_RENDERSCRIPT); 180 181 // loop until we've compile them all 182 for (IFile sourceFile : sources) { 183 if (verbose) { 184 String name = sourceFile.getName(); 185 IPath sourceFolderPath = getSourceFolderFor(sourceFile); 186 if (sourceFolderPath != null) { 187 // make a path to the source file relative to the source folder. 188 IPath relative = sourceFile.getFullPath().makeRelativeTo(sourceFolderPath); 189 name = relative.toString(); 190 } 191 AdtPlugin.printToConsole(project, "RenderScript: " + name); 192 } 193 194 // Remove the RS error markers from the source file and the dependencies 195 builder.removeMarkersFromResource(sourceFile, AdtConstants.MARKER_RENDERSCRIPT); 196 SourceFileData data = getFileData(sourceFile); 197 if (data != null) { 198 for (IFile dep : data.getDependencyFiles()) { 199 builder.removeMarkersFromResource(dep, AdtConstants.MARKER_RENDERSCRIPT); 200 } 201 } 202 203 // get the path of the source file. 204 IPath sourcePath = sourceFile.getLocation(); 205 String osSourcePath = sourcePath.toOSString(); 206 207 // finish to set the command line. 208 command[depIndex] = quote(getDependencyFolder(sourceFile).getLocation().toOSString()); 209 command[index] = quote(osSourcePath); 210 211 // launch the process 212 if (execLlvmRsCc(builder, project, command, sourceFile, verbose) == false) { 213 // llvm-rs-cc failed. File should be marked. We add the file to the list 214 // of file that will need compilation again. 215 notCompiledOut.add(sourceFile); 216 } else { 217 // Success. we'll return that we generated code and resources. 218 setCompilationStatus(COMPILE_STATUS_CODE | COMPILE_STATUS_RES); 219 220 // need to parse the .d file to figure out the dependencies and the generated file 221 parseDependencyFileFor(sourceFile); 222 someSuccess = true; 223 } 224 } 225 226 if (someSuccess) { 227 rawFolder.refreshLocal(IResource.DEPTH_ONE, monitor); 228 } 229 } 230 231 private boolean execLlvmRsCc(BaseBuilder builder, IProject project, String[] command, 232 IFile sourceFile, boolean verbose) { 233 // do the exec 234 try { 235 if (verbose) { 236 StringBuilder sb = new StringBuilder(); 237 for (String c : command) { 238 sb.append(c); 239 sb.append(' '); 240 } 241 String cmd_line = sb.toString(); 242 AdtPlugin.printToConsole(project, cmd_line); 243 } 244 245 Process p = Runtime.getRuntime().exec(command); 246 247 // list to store each line of stderr 248 ArrayList<String> results = new ArrayList<String>(); 249 250 // get the output and return code from the process 251 int result = BuildHelper.grabProcessOutput(project, p, results); 252 253 // attempt to parse the error output 254 boolean error = parseLlvmOutput(results); 255 256 // If the process failed and we couldn't parse the output 257 // we print a message, mark the project and exit 258 if (result != 0) { 259 260 if (error || verbose) { 261 // display the message in the console. 262 if (error) { 263 AdtPlugin.printErrorToConsole(project, results.toArray()); 264 265 // mark the project 266 BaseProjectHelper.markResource(project, 267 AdtConstants.MARKER_RENDERSCRIPT, 268 "Unparsed Renderscript error! Check the console for output.", 269 IMarker.SEVERITY_ERROR); 270 } else { 271 AdtPlugin.printToConsole(project, results.toArray()); 272 } 273 } 274 return false; 275 } 276 } catch (IOException e) { 277 // mark the project and exit 278 String msg = String.format( 279 "Error executing Renderscript. Please check llvm-rs-cc is present at %1$s", 280 command[0]); 281 BaseProjectHelper.markResource(project, AdtConstants.MARKER_RENDERSCRIPT, msg, 282 IMarker.SEVERITY_ERROR); 283 return false; 284 } catch (InterruptedException e) { 285 // mark the project and exit 286 String msg = String.format( 287 "Error executing Renderscript. Please check llvm-rs-cc is present at %1$s", 288 command[0]); 289 BaseProjectHelper.markResource(project, AdtConstants.MARKER_RENDERSCRIPT, msg, 290 IMarker.SEVERITY_ERROR); 291 return false; 292 } 293 294 return true; 295 } 296 297 /** 298 * Parse the output of llvm-rs-cc and mark the file with any errors. 299 * @param lines The output to parse. 300 * @return true if the parsing failed, false if success. 301 */ 302 private boolean parseLlvmOutput(ArrayList<String> lines) { 303 // nothing to parse? just return false; 304 if (lines.size() == 0) { 305 return false; 306 } 307 308 // get the root folder for the project as we're going to ignore everything that's 309 // not in the project 310 IProject project = getJavaProject().getProject(); 311 String rootPath = project.getLocation().toOSString(); 312 int rootPathLength = rootPath.length(); 313 314 Matcher m; 315 316 boolean parsing = false; 317 318 for (int i = 0; i < lines.size(); i++) { 319 String p = lines.get(i); 320 321 m = sLlvmPattern1.matcher(p); 322 if (m.matches()) { 323 // get the file path. This may, or may not be the main file being compiled. 324 String filePath = m.group(1); 325 if (filePath.startsWith(rootPath) == false) { 326 // looks like the error in a non-project file. Keep parsing, but 327 // we'll return true 328 parsing = true; 329 continue; 330 } 331 332 // get the actual file. 333 filePath = filePath.substring(rootPathLength); 334 // remove starting separator since we want the path to be relative 335 if (filePath.startsWith(File.separator)) { 336 filePath = filePath.substring(1); 337 } 338 339 // get the file 340 IFile f = project.getFile(new Path(filePath)); 341 342 String lineStr = m.group(2); 343 // ignore group 3 for now, this is the col number 344 String msg = m.group(4); 345 346 // get the line number 347 int line = 0; 348 try { 349 line = Integer.parseInt(lineStr); 350 } catch (NumberFormatException e) { 351 // looks like the string we extracted wasn't a valid 352 // file number. Parsing failed and we return true 353 return true; 354 } 355 356 // mark the file 357 BaseProjectHelper.markResource(f, AdtConstants.MARKER_RENDERSCRIPT, msg, line, 358 IMarker.SEVERITY_ERROR); 359 360 // success, go to the next line 361 continue; 362 } 363 364 // invalid line format, flag as error, and keep going 365 parsing = true; 366 } 367 368 return parsing; 369 } 370 371 372 @Override 373 protected void doRemoveFiles(SourceFileData bundle) throws CoreException { 374 // call the super implementation, it will remove the output files 375 super.doRemoveFiles(bundle); 376 377 // now remove the dependency file. 378 IFile depFile = getDependencyFileFor(bundle.getSourceFile()); 379 if (depFile.exists()) { 380 depFile.getLocation().toFile().delete(); 381 } 382 } 383 384 @Override 385 protected void loadOutputAndDependencies() { 386 Collection<SourceFileData> dataList = getAllFileData(); 387 for (SourceFileData data : dataList) { 388 // parse the dependency file. If this fails, force compilation of the file. 389 if (parseDependencyFileFor(data.getSourceFile()) == false) { 390 addFileToCompile(data.getSourceFile()); 391 } 392 } 393 } 394 395 private boolean parseDependencyFileFor(IFile sourceFile) { 396 IFile depFile = getDependencyFileFor(sourceFile); 397 File f = depFile.getLocation().toFile(); 398 if (f.exists()) { 399 SourceFileData data = getFileData(sourceFile); 400 if (data == null) { 401 data = new SourceFileData(sourceFile); 402 addData(data); 403 } 404 parseDependencyFile(data, f); 405 return true; 406 } 407 408 return false; 409 } 410 411 private IFolder getDependencyFolder(IFile sourceFile) { 412 IPath sourceFolderPath = getSourceFolderFor(sourceFile); 413 414 // this really shouldn't happen since the sourceFile must be in a source folder 415 // since it comes from the delta visitor 416 if (sourceFolderPath != null) { 417 // make a path to the source file relative to the source folder. 418 IPath relative = sourceFile.getFullPath().makeRelativeTo(sourceFolderPath); 419 // remove the file name. This is now the destination folder. 420 relative = relative.removeLastSegments(1); 421 422 return getGenFolder().getFolder(relative); 423 } 424 425 return null; 426 } 427 428 private IFile getDependencyFileFor(IFile sourceFile) { 429 IFolder depFolder = getDependencyFolder(sourceFile); 430 return depFolder.getFile(sourceFile.getName().replaceAll(AdtConstants.RE_RS_EXT, 431 AdtConstants.DOT_DEP)); 432 } 433 434 /** 435 * Parses the given dependency file and fills the given {@link SourceFileData} with it. 436 * 437 * @param data the bundle to fill. 438 * @param file the dependency file 439 */ 440 private void parseDependencyFile(SourceFileData data, File dependencyFile) { 441 //contents = file.getContents(); 442 String content = AdtPlugin.readFile(dependencyFile); 443 444 // we're going to be pretty brutal here. 445 // The format is something like: 446 // output1 output2 [...]: dep1 dep2 [...] 447 // expect it's likely split on several lines. So let's move it back on a single line 448 // first 449 String[] lines = content.split("\n"); //$NON-NLS-1$ 450 StringBuilder sb = new StringBuilder(); 451 for (String line : lines) { 452 line = line.trim(); 453 if (line.endsWith("\\")) { //$NON-NLS-1$ 454 line = line.substring(0, line.length() - 1); 455 } 456 457 sb.append(line); 458 } 459 460 // split the left and right part 461 String[] files = sb.toString().split(":"); //$NON-NLS-1$ 462 463 // get the output files: 464 String[] outputs = files[0].trim().split(" "); //$NON-NLS-1$ 465 466 // and the dependency files: 467 String[] dependencies = files[1].trim().split(" "); //$NON-NLS-1$ 468 469 List<IFile> outputFiles = new ArrayList<IFile>(); 470 List<IFile> dependencyFiles = new ArrayList<IFile>(); 471 472 fillList(outputs, outputFiles); 473 fillList(dependencies, dependencyFiles); 474 475 data.setOutputFiles(outputFiles); 476 data.setDependencyFiles(dependencyFiles); 477 } 478 479 private void fillList(String[] paths, List<IFile> list) { 480 // get the root folder for the project as we're going to ignore everything that's 481 // not in the project 482 IProject project = getJavaProject().getProject(); 483 String rootPath = project.getLocation().toOSString(); 484 int rootPathLength = rootPath.length(); 485 486 // all those should really be in the project 487 for (String p : paths) { 488 489 if (p.startsWith(rootPath)) { 490 p = p.substring(rootPathLength); 491 // remove starting separator since we want the path to be relative 492 if (p.startsWith(File.separator)) { 493 p = p.substring(1); 494 } 495 496 // get the file 497 IFile f = project.getFile(new Path(p)); 498 list.add(f); 499 } 500 } 501 } 502 } 503