• 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;
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