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