• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
PostCompilerDeltaVisitor(BaseBuilder builder, List<IPath> sourceFolders, IFolder outputfolder)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 
getConvertToDex()126     public boolean getConvertToDex() {
127         return mConvertToDex;
128     }
129 
getUpdateCrunchCache()130     public boolean getUpdateCrunchCache() {
131         return mUpdateCrunchCache;
132     }
133 
getPackageResources()134     public boolean getPackageResources() {
135         return mPackageResources;
136     }
137 
getMakeFinalPackage()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      */
visit(IResourceDelta delta)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