Home | History | Annotate | Download | only in builders
      1 /*
      2  * Copyright (C) 2007 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.builders;
     18 
     19 import com.android.ide.eclipse.adt.AdtConstants;
     20 import com.android.ide.eclipse.adt.internal.build.BuildHelper;
     21 import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder.BaseDeltaVisitor;
     22 import com.android.sdklib.SdkConstants;
     23 
     24 import org.eclipse.core.resources.IFile;
     25 import org.eclipse.core.resources.IFolder;
     26 import org.eclipse.core.resources.IResource;
     27 import org.eclipse.core.resources.IResourceDelta;
     28 import org.eclipse.core.resources.IResourceDeltaVisitor;
     29 import org.eclipse.core.runtime.CoreException;
     30 import org.eclipse.core.runtime.IPath;
     31 
     32 import java.util.List;
     33 
     34 /**
     35  * Delta resource visitor looking for changes that will trigger a new packaging of an Android
     36  * application.
     37  * <p/>
     38  * This looks for the following changes:
     39  * <ul>
     40  * <li>Any change to the AndroidManifest.xml file</li>
     41  * <li>Any change inside the assets/ folder</li>
     42  * <li>Any file change inside the res/ folder</li>
     43  * <li>Any .class file change inside the output folder</li>
     44  * <li>Any change to the classes.dex inside the output folder</li>
     45  * <li>Any change to the packaged resources file inside the output folder</li>
     46  * <li>Any change to a non java/aidl file inside the source folders</li>
     47  * <li>Any change to .so file inside the lib (native library) folder</li>
     48  * </ul>
     49  */
     50 public class PostCompilerDeltaVisitor extends BaseDeltaVisitor
     51         implements IResourceDeltaVisitor {
     52 
     53     /**
     54      * compile flag. This is set to true if one of the changed/added/removed
     55      * file is a .class file. Upon visiting all the delta resources, if this
     56      * flag is true, then we know we'll have to make the "classes.dex" file.
     57      */
     58     private boolean mConvertToDex = false;
     59 
     60     /**
     61      * compile flag. This is set to true if one of the changed/added/removed
     62      * files is a .png file. Upon visiting all the delta resources, if this
     63      * flag is true, then we know we'll have to run "aapt crunch".
     64      */
     65     private boolean mUpdateCrunchCache = false;
     66 
     67     /**
     68      * compile flag. This is set to true if one of the changed/added/removed
     69      * file is a resource file. Upon visiting all the delta resources, if
     70      * this flag is true, then we know we'll have to make the intermediate
     71      * apk file.
     72      */
     73     private boolean mPackageResources = false;
     74 
     75     /**
     76      * Final package flag. This is set to true if one of the changed/added/removed
     77      * file is a non java file (or aidl) in the resource folder. Upon visiting all the
     78      * delta resources, if this flag is true, then we know we'll have to make the final
     79      * package.
     80      */
     81     private boolean mMakeFinalPackage = false;
     82 
     83     /** List of source folders. */
     84     private List<IPath> mSourceFolders;
     85 
     86     private IPath mOutputPath;
     87 
     88     private IPath mAssetPath;
     89 
     90     private IPath mResPath;
     91 
     92     private IPath mLibFolder;
     93 
     94     /**
     95      * Builds the object with a specified output folder.
     96      * @param builder the xml builder using this object to visit the
     97      *  resource delta.
     98      * @param sourceFolders the list of source folders for the project, relative to the workspace.
     99      * @param outputfolder the output folder of the project.
    100      */
    101     public PostCompilerDeltaVisitor(BaseBuilder builder, List<IPath> sourceFolders,
    102             IFolder outputfolder) {
    103         super(builder);
    104         mSourceFolders = sourceFolders;
    105 
    106         if (outputfolder != null) {
    107             mOutputPath = outputfolder.getFullPath();
    108         }
    109 
    110         IResource assetFolder = builder.getProject().findMember(SdkConstants.FD_ASSETS);
    111         if (assetFolder != null) {
    112             mAssetPath = assetFolder.getFullPath();
    113         }
    114 
    115         IResource resFolder = builder.getProject().findMember(SdkConstants.FD_RESOURCES);
    116         if (resFolder != null) {
    117             mResPath = resFolder.getFullPath();
    118         }
    119 
    120         IResource libFolder = builder.getProject().findMember(SdkConstants.FD_NATIVE_LIBS);
    121         if (libFolder != null) {
    122             mLibFolder = libFolder.getFullPath();
    123         }
    124     }
    125 
    126     public boolean getConvertToDex() {
    127         return mConvertToDex;
    128     }
    129 
    130     public boolean getUpdateCrunchCache() {
    131         return mUpdateCrunchCache;
    132     }
    133 
    134     public boolean getPackageResources() {
    135         return mPackageResources;
    136     }
    137 
    138     public boolean getMakeFinalPackage() {
    139         return mMakeFinalPackage;
    140     }
    141 
    142     /**
    143      * {@inheritDoc}
    144      * @throws CoreException
    145      *
    146      * @see org.eclipse.core.resources.IResourceDeltaVisitor
    147      *      #visit(org.eclipse.core.resources.IResourceDelta)
    148      */
    149     public boolean visit(IResourceDelta delta) throws CoreException {
    150         // if all flags are true, we can stop going through the resource delta.
    151         if (mConvertToDex && mUpdateCrunchCache && mPackageResources && mMakeFinalPackage) {
    152             return false;
    153         }
    154 
    155         // we are only going to look for changes in res/, src/ and in
    156         // AndroidManifest.xml since the delta visitor goes through the main
    157         // folder before its children we can check when the path segment
    158         // count is 2 (format will be /$Project/folder) and make sure we are
    159         // processing res/, src/ or AndroidManifest.xml
    160         IResource resource = delta.getResource();
    161         IPath path = resource.getFullPath();
    162         String[] pathSegments = path.segments();
    163         int type = resource.getType();
    164 
    165         // since the delta visitor also visits the root we return true if
    166         // segments.length = 1
    167         if (pathSegments.length == 1) {
    168             return true;
    169         }
    170 
    171         // check the manifest.
    172         if (pathSegments.length == 2 &&
    173                 SdkConstants.FN_ANDROID_MANIFEST_XML.equalsIgnoreCase(pathSegments[1])) {
    174             // if the manifest changed we have to repackage the
    175             // resources.
    176             mPackageResources = true;
    177             mMakeFinalPackage = true;
    178 
    179             // we don't want to go to the children, not like they are
    180             // any for this resource anyway.
    181             return false;
    182         }
    183 
    184         // check the other folders.
    185         if (mOutputPath != null && mOutputPath.isPrefixOf(path)) {
    186             // a resource changed inside the output folder.
    187             if (type == IResource.FILE) {
    188                 // just check this is a .class file. Any modification will
    189                 // trigger a change in the classes.dex file
    190                 String ext = resource.getFileExtension();
    191                 if (AdtConstants.EXT_CLASS.equalsIgnoreCase(ext)) {
    192                     mConvertToDex = true;
    193                     mMakeFinalPackage = true;
    194 
    195                     // no need to check the children, as we are in a package
    196                     // and there can only be subpackage children containing
    197                     // only .class files
    198                     return false;
    199                 }
    200 
    201                 // check for a few files directly in the output folder and force
    202                 // rebuild if they have been deleted.
    203                 if (delta.getKind() == IResourceDelta.REMOVED) {
    204                     IPath parentPath = path.removeLastSegments(1);
    205                     if (mOutputPath.equals(parentPath)) {
    206                         String resourceName = resource.getName();
    207                         // check if classes.dex was removed
    208                         if (resourceName.equalsIgnoreCase(SdkConstants.FN_APK_CLASSES_DEX)) {
    209                             mConvertToDex = true;
    210                             mMakeFinalPackage = true;
    211                         } else if (resourceName.equalsIgnoreCase(
    212                                 AdtConstants.FN_RESOURCES_AP_) ||
    213                                 AdtConstants.PATTERN_RESOURCES_S_AP_.matcher(
    214                                         resourceName).matches()) {
    215                             // or if the default resources.ap_ or a configured version
    216                             // (resources-###.ap_) was removed.
    217                             mPackageResources = true;
    218                             mMakeFinalPackage = true;
    219                         }
    220                     }
    221                 }
    222             }
    223 
    224             // if this is a folder, we only go visit it if we don't already know
    225             // that we need to convert to dex already.
    226             return mConvertToDex == false;
    227         } else if (mResPath != null && mResPath.isPrefixOf(path)) {
    228             // in the res folder we are looking for any file modification
    229             // (we don't care about folder being added/removed, only content
    230             // is important)
    231             if (type == IResource.FILE) {
    232                 // Check if this is a .png file. Any modification will
    233                 // trigger a cache update
    234                 String ext = resource.getFileExtension();
    235                 if (AdtConstants.EXT_PNG.equalsIgnoreCase(ext)) {
    236                     mUpdateCrunchCache = true;
    237                 }
    238                 mPackageResources = true;
    239                 mMakeFinalPackage = true;
    240                 return false;
    241             }
    242 
    243             // for folders, return true only if we don't already know we have to
    244             // package the resources.
    245             return mPackageResources == false || mUpdateCrunchCache == false;
    246         } else if (mAssetPath != null && mAssetPath.isPrefixOf(path)) {
    247             // this is the assets folder that was modified.
    248             // we don't care what content was changed. All we care
    249             // about is that something changed inside. No need to visit
    250             // the children even.
    251             mPackageResources = true;
    252             mMakeFinalPackage = true;
    253             return false;
    254         } else if (mLibFolder != null && mLibFolder.isPrefixOf(path)) {
    255             // inside the native library folder. Test if the changed resource is a .so file.
    256             if (type == IResource.FILE &&
    257                     (AdtConstants.EXT_NATIVE_LIB.equalsIgnoreCase(path.getFileExtension())
    258                             || SdkConstants.FN_GDBSERVER.equals(resource.getName()))) {
    259                 mMakeFinalPackage = true;
    260                 return false; // return false for file.
    261             }
    262 
    263             // for folders, return true only if we don't already know we have to make the
    264             // final package.
    265             return mMakeFinalPackage == false;
    266         } else {
    267             // we are in a folder that is neither the resource folders, nor the output.
    268             // check against all the source folders, unless we already know we need to do
    269             // the final package.
    270             // This could be a source folder or a folder leading to a source folder.
    271             // However we only check this if we don't already know that we need to build the
    272             // package anyway
    273             if (mMakeFinalPackage == false) {
    274                 for (IPath sourcePath : mSourceFolders) {
    275                     if (sourcePath.isPrefixOf(path)) {
    276                         // In the source folders, we are looking for any kind of
    277                         // modification related to file that are not java files.
    278                         // Also excluded are aidl files, and package.html files
    279                         if (type == IResource.FOLDER) {
    280                             // always visit the subfolders, unless the folder is not to be included
    281                             return BuildHelper.checkFolderForPackaging((IFolder)resource);
    282                         } else if (type == IResource.FILE) {
    283                             if (BuildHelper.checkFileForPackaging((IFile)resource)) {
    284                                 mMakeFinalPackage = true;
    285                             }
    286 
    287                             return false;
    288                         }
    289 
    290                     }
    291                 }
    292             }
    293         }
    294 
    295         // if the folder is not inside one of the folders we are interested in (res, assets, output,
    296         // source folders), it could be a folder leading to them, so we return true.
    297         return true;
    298     }
    299 }
    300