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