/* * 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.resources.manager.ResourceFolderType; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; 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.jdt.core.IJavaProject; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A {@link SourceProcessor} for RenderScript files. * */ public class RenderScriptProcessor extends SourceProcessor { private static final String PROPERTY_COMPILE_RS = "compileRenderScript"; //$NON-NLS-1$ /** * Single line llvm-rs-cc error: {@code <path>:<line>:<col>: <error>} */ private static Pattern sLlvmPattern1 = Pattern.compile("^(.+?):(\\d+):(\\d+):\\s(.+)$"); //$NON-NLS-1$ private static class RsChangeHandler extends SourceChangeHandler { @Override public boolean handleGeneratedFile(IFile file, int kind) { boolean r = super.handleGeneratedFile(file, kind); if (r == false && kind == IResourceDelta.REMOVED && AndroidConstants.EXT_DEP.equalsIgnoreCase(file.getFileExtension())) { // This looks to be an extension file. // For futureproofness let's make sure this dependency file was generated by // this processor even if it's the only processor using them for now. // look for the original file. // We know we are in the gen folder, so make a path to the dependency file // relative to the gen folder. Convert this into a Renderscript source file, // and look to see if this file exists. SourceProcessor processor = getProcessor(); IFolder genFolder = processor.getGenFolder(); IPath relative = file.getFullPath().makeRelativeTo(genFolder.getFullPath()); // remove the file name segment relative = relative.removeLastSegments(1); // add the file name of a Renderscript file. relative = relative.append(file.getName().replaceAll(AndroidConstants.RE_DEP_EXT, AndroidConstants.DOT_RS)); // now look for a match in the source folders. List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths( processor.getJavaProject()); IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); for (IPath sourceFolderPath : sourceFolders) { 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(genFolder) == false) { IFile sourceFile = sourceFolder.getFile(relative); SourceFileData data = processor.getFileData(sourceFile); if (data != null) { addFileToCompile(sourceFile); return true; } } } } return r; } @Override protected boolean filterResourceFolder(IContainer folder) { return ResourceFolderType.RAW.getName().equals(folder.getName()); } } public RenderScriptProcessor(IJavaProject javaProject, IFolder genFolder) { super(javaProject, genFolder, new RsChangeHandler()); } @Override protected String getExtension() { return AndroidConstants.EXT_RS; } @Override protected String getSavePropertyName() { return PROPERTY_COMPILE_RS; } @Override protected void doCompileFiles(List<IFile> sources, BaseBuilder builder, IProject project, IAndroidTarget projectTarget, List<IPath> sourceFolders, List<IFile> notCompiledOut, IProgressMonitor monitor) throws CoreException { String sdkOsPath = Sdk.getCurrent().getSdkLocation(); IFolder genFolder = getGenFolder(); IFolder rawFolder = project.getFolder( new Path(SdkConstants.FD_RES).append(SdkConstants.FD_RAW)); int depIndex; // create the command line String[] command = new String[13]; int index = 0; command[index++] = quote(sdkOsPath + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER + SdkConstants.FN_RENDERSCRIPT); command[index++] = "-I"; //$NON-NLS-1$ command[index++] = quote(projectTarget.getPath(IAndroidTarget.ANDROID_RS_CLANG)); command[index++] = "-I"; //$NON-NLS-1$ command[index++] = quote(projectTarget.getPath(IAndroidTarget.ANDROID_RS)); command[index++] = "-p"; //$NON-NLS-1$ command[index++] = quote(genFolder.getLocation().toOSString()); command[index++] = "-o"; //$NON-NLS-1$ command[index++] = quote(rawFolder.getLocation().toOSString()); command[index++] = "-d"; //$NON-NLS-1$ command[depIndex = index++] = null; command[index++] = "-MD"; //$NON-NLS-1$ boolean verbose = AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE; boolean someSuccess = false; // remove the generic marker from the project builder.removeMarkersFromResource(project, AndroidConstants.MARKER_RENDERSCRIPT); // 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, "RenderScript: " + name); } // Remove the RS error markers from the source file and the dependencies builder.removeMarkersFromResource(sourceFile, AndroidConstants.MARKER_RENDERSCRIPT); SourceFileData data = getFileData(sourceFile); if (data != null) { for (IFile dep : data.getDependencyFiles()) { builder.removeMarkersFromResource(dep, AndroidConstants.MARKER_RENDERSCRIPT); } } // get the path of the source file. IPath sourcePath = sourceFile.getLocation(); String osSourcePath = sourcePath.toOSString(); // finish to set the command line. command[depIndex] = quote(getDependencyFolder(sourceFile).getLocation().toOSString()); command[index] = quote(osSourcePath); // launch the process if (execLlvmRsCc(builder, project, command, sourceFile, verbose) == false) { // llvm-rs-cc 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 and resources. setCompilationStatus(COMPILE_STATUS_CODE | COMPILE_STATUS_RES); // need to parse the .d file to figure out the dependencies and the generated file parseDependencyFileFor(sourceFile); someSuccess = true; } } if (someSuccess) { rawFolder.refreshLocal(IResource.DEPTH_ONE, monitor); } } private boolean execLlvmRsCc(BaseBuilder builder, IProject project, String[] command, IFile sourceFile, 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> 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 = parseLlvmOutput(results); // If the process failed and we couldn't parse the output // we print a message, mark the project and exit if (result != 0) { if (error || verbose) { // display the message in the console. if (error) { AdtPlugin.printErrorToConsole(project, results.toArray()); // mark the project BaseProjectHelper.markResource(project, AndroidConstants.MARKER_RENDERSCRIPT, "Unparsed Renderscript error! Check the console for output.", IMarker.SEVERITY_ERROR); } else { AdtPlugin.printToConsole(project, results.toArray()); } } return false; } } catch (IOException e) { // mark the project and exit String msg = String.format( "Error executing Renderscript. Please check llvm-rs-cc is present at %1$s", command[0]); BaseProjectHelper.markResource(project, AndroidConstants.MARKER_RENDERSCRIPT, msg, IMarker.SEVERITY_ERROR); return false; } catch (InterruptedException e) { // mark the project and exit String msg = String.format( "Error executing Renderscript. Please check llvm-rs-cc is present at %1$s", command[0]); BaseProjectHelper.markResource(project, AndroidConstants.MARKER_RENDERSCRIPT, msg, IMarker.SEVERITY_ERROR); return false; } return true; } /** * Parse the output of llvm-rs-cc and mark the file with any errors. * @param lines The output to parse. * @return true if the parsing failed, false if success. */ private boolean parseLlvmOutput(ArrayList<String> lines) { // nothing to parse? just return false; if (lines.size() == 0) { return false; } // get the root folder for the project as we're going to ignore everything that's // not in the project IProject project = getJavaProject().getProject(); String rootPath = project.getLocation().toOSString(); int rootPathLength = rootPath.length(); Matcher m; boolean parsing = false; for (int i = 0; i < lines.size(); i++) { String p = lines.get(i); m = sLlvmPattern1.matcher(p); if (m.matches()) { // get the file path. This may, or may not be the main file being compiled. String filePath = m.group(1); if (filePath.startsWith(rootPath) == false) { // looks like the error in a non-project file. Keep parsing, but // we'll return true parsing = true; continue; } // get the actual file. filePath = filePath.substring(rootPathLength); // remove starting separator since we want the path to be relative if (filePath.startsWith(File.separator)) { filePath = filePath.substring(1); } // get the file IFile f = project.getFile(new Path(filePath)); String lineStr = m.group(2); // ignore group 3 for now, this is the col number String msg = m.group(4); // 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(f, AndroidConstants.MARKER_RENDERSCRIPT, msg, line, IMarker.SEVERITY_ERROR); // success, go to the next line continue; } // invalid line format, flag as error, and keep going parsing = true; } return parsing; } @Override protected void doRemoveFiles(SourceFileData bundle) throws CoreException { // call the super implementation, it will remove the output files super.doRemoveFiles(bundle); // now remove the dependency file. IFile depFile = getDependencyFileFor(bundle.getSourceFile()); if (depFile.exists()) { depFile.getLocation().toFile().delete(); } } @Override protected void loadOutputAndDependencies() { Collection<SourceFileData> dataList = getAllFileData(); for (SourceFileData data : dataList) { // parse the dependency file. If this fails, force compilation of the file. if (parseDependencyFileFor(data.getSourceFile()) == false) { addFileToCompile(data.getSourceFile()); } } } private boolean parseDependencyFileFor(IFile sourceFile) { IFile depFile = getDependencyFileFor(sourceFile); File f = depFile.getLocation().toFile(); if (f.exists()) { SourceFileData data = getFileData(sourceFile); if (data == null) { data = new SourceFileData(sourceFile); addData(data); } parseDependencyFile(data, f); return true; } return false; } private IFolder getDependencyFolder(IFile sourceFile) { 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); return getGenFolder().getFolder(relative); } return null; } private IFile getDependencyFileFor(IFile sourceFile) { IFolder depFolder = getDependencyFolder(sourceFile); return depFolder.getFile(sourceFile.getName().replaceAll(AndroidConstants.RE_RS_EXT, AndroidConstants.DOT_DEP)); } /** * Parses the given dependency file and fills the given {@link SourceFileData} with it. * * @param data the bundle to fill. * @param file the dependency file */ private void parseDependencyFile(SourceFileData data, File dependencyFile) { //contents = file.getContents(); String content = AdtPlugin.readFile(dependencyFile); // we're going to be pretty brutal here. // The format is something like: // output1 output2 [...]: dep1 dep2 [...] // expect it's likely split on several lines. So let's move it back on a single line // first String[] lines = content.split("\n"); //$NON-NLS-1$ StringBuilder sb = new StringBuilder(); for (String line : lines) { line = line.trim(); if (line.endsWith("\\")) { //$NON-NLS-1$ line = line.substring(0, line.length() - 1); } sb.append(line); } // split the left and right part String[] files = sb.toString().split(":"); //$NON-NLS-1$ // get the output files: String[] outputs = files[0].trim().split(" "); //$NON-NLS-1$ // and the dependency files: String[] dependencies = files[1].trim().split(" "); //$NON-NLS-1$ List<IFile> outputFiles = new ArrayList<IFile>(); List<IFile> dependencyFiles = new ArrayList<IFile>(); fillList(outputs, outputFiles); fillList(dependencies, dependencyFiles); data.setOutputFiles(outputFiles); data.setDependencyFiles(dependencyFiles); } private void fillList(String[] paths, List<IFile> list) { // get the root folder for the project as we're going to ignore everything that's // not in the project IProject project = getJavaProject().getProject(); String rootPath = project.getLocation().toOSString(); int rootPathLength = rootPath.length(); // all those should really be in the project for (String p : paths) { if (p.startsWith(rootPath)) { p = p.substring(rootPathLength); // remove starting separator since we want the path to be relative if (p.startsWith(File.separator)) { p = p.substring(1); } // get the file IFile f = project.getFile(new Path(p)); list.add(f); } } } }