Home | History | Annotate | Download | only in build
      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