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