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.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