• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
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.ant;
18 
19 import com.android.sdklib.build.ApkBuilder;
20 import com.android.sdklib.build.ApkCreationException;
21 import com.android.sdklib.build.DuplicateFileException;
22 import com.android.sdklib.build.SealedApkException;
23 import com.android.sdklib.build.ApkBuilder.FileEntry;
24 
25 import org.apache.tools.ant.BuildException;
26 import org.apache.tools.ant.types.Path;
27 
28 import java.io.File;
29 import java.io.FilenameFilter;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.regex.Pattern;
33 
34 public class ApkBuilderTask extends BaseTask {
35 
36     private final static Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$",
37             Pattern.CASE_INSENSITIVE);
38 
39     private String mOutFolder;
40     private String mApkFilepath;
41     private String mResourceFile;
42     private boolean mVerbose = false;
43     private boolean mDebugPackaging = false;
44     private boolean mDebugSigning = false;
45     private boolean mHasCode = true;
46 
47     private Path mDexPath;
48 
49     private final ArrayList<Path> mZipList = new ArrayList<Path>();
50     private final ArrayList<Path> mSourceList = new ArrayList<Path>();
51     private final ArrayList<Path> mJarfolderList = new ArrayList<Path>();
52     private final ArrayList<Path> mJarfileList = new ArrayList<Path>();
53     private final ArrayList<Path> mNativeList = new ArrayList<Path>();
54 
55     private static class SourceFolderInputPath extends InputPath {
SourceFolderInputPath(File file)56         public SourceFolderInputPath(File file) {
57             super(file);
58         }
59 
60         @Override
ignores(File file)61         public boolean ignores(File file) {
62             if (file.isDirectory()) {
63                 return !ApkBuilder.checkFolderForPackaging(file.getName());
64             } else {
65                 return !ApkBuilder.checkFileForPackaging(file.getName());
66             }
67         }
68     }
69 
70     /**
71      * Sets the value of the "outfolder" attribute.
72      * @param outFolder the value.
73      */
setOutfolder(Path outFolder)74     public void setOutfolder(Path outFolder) {
75         mOutFolder = TaskHelper.checkSinglePath("outfolder", outFolder);
76     }
77 
78     /**
79      * Sets the full filepath to the apk to generate.
80      * @param filepath
81      */
setApkfilepath(String filepath)82     public void setApkfilepath(String filepath) {
83         mApkFilepath = filepath;
84     }
85 
86     /**
87      * Sets the resourcefile attribute
88      * @param resourceFile
89      */
setResourcefile(String resourceFile)90     public void setResourcefile(String resourceFile) {
91         mResourceFile = resourceFile;
92     }
93 
94     /**
95      * Sets the value of the "verbose" attribute.
96      * @param verbose the value.
97      */
setVerbose(boolean verbose)98     public void setVerbose(boolean verbose) {
99         mVerbose = verbose;
100     }
101 
102     /**
103      * Sets the value of the "debug" attribute.
104      * @param debug the debug mode value.
105      */
setDebug(boolean debug)106     public void setDebug(boolean debug) {
107         System.out.println("WARNNG: Using deprecated 'debug' attribute in ApkBuilderTask." +
108         "Use 'debugpackaging' and 'debugsigning' instead.");
109         mDebugPackaging = debug;
110         mDebugSigning = debug;
111     }
112 
113     /**
114      * Sets the value of the "debugpackaging" attribute.
115      * @param debug the debug mode value.
116      */
setDebugpackaging(boolean debug)117     public void setDebugpackaging(boolean debug) {
118         mDebugPackaging = debug;
119     }
120 
121     /**
122      * Sets the value of the "debugsigning" attribute.
123      * @param debug the debug mode value.
124      */
setDebugsigning(boolean debug)125     public void setDebugsigning(boolean debug) {
126         mDebugSigning = debug;
127     }
128 
129     /**
130      * Sets the hascode attribute. Default is true.
131      * If set to false, then <dex> and <sourcefolder> nodes are ignored and not processed.
132      * @param hasCode the value of the attribute.
133      */
setHascode(boolean hasCode)134     public void setHascode(boolean hasCode) {
135         mHasCode   = hasCode;
136     }
137 
138     /**
139      * Returns an object representing a nested <var>zip</var> element.
140      */
createZip()141     public Object createZip() {
142         Path path = new Path(getProject());
143         mZipList.add(path);
144         return path;
145     }
146 
147     /**
148      * Returns an object representing a nested <var>dex</var> element.
149      * This is similar to a nested <var>file</var> element, except when {@link #mHasCode}
150      * is <code>false</code> in which case it's ignored.
151      */
createDex()152     public Object createDex() {
153         if (mDexPath == null) {
154             return mDexPath = new Path(getProject());
155         } else {
156             throw new BuildException("Only one <dex> inner element can be provided");
157         }
158     }
159 
160     /**
161      * Returns an object representing a nested <var>sourcefolder</var> element.
162      */
createSourcefolder()163     public Object createSourcefolder() {
164         Path path = new Path(getProject());
165         mSourceList.add(path);
166         return path;
167     }
168 
169     /**
170      * Returns an object representing a nested <var>jarfolder</var> element.
171      */
createJarfolder()172     public Object createJarfolder() {
173         Path path = new Path(getProject());
174         mJarfolderList.add(path);
175         return path;
176     }
177 
178     /**
179      * Returns an object representing a nested <var>jarfile</var> element.
180      */
createJarfile()181     public Object createJarfile() {
182         Path path = new Path(getProject());
183         mJarfileList.add(path);
184         return path;
185     }
186 
187     /**
188      * Returns an object representing a nested <var>nativefolder</var> element.
189      */
createNativefolder()190     public Object createNativefolder() {
191         Path path = new Path(getProject());
192         mNativeList.add(path);
193         return path;
194     }
195 
196     @Override
execute()197     public void execute() throws BuildException {
198 
199         File outputFile;
200         if (mApkFilepath != null) {
201             outputFile = new File(mApkFilepath);
202         } else {
203             throw new BuildException("missing attribute 'apkFilepath'");
204         }
205 
206         if (mResourceFile == null) {
207             throw new BuildException("missing attribute 'resourcefile'");
208         }
209 
210         if (mOutFolder == null) {
211             throw new BuildException("missing attribute 'outfolder'");
212         }
213 
214         // check dexPath is only one file.
215         File dexFile = null;
216         if (mHasCode) {
217             String[] dexFiles = mDexPath.list();
218             if (dexFiles.length != 1) {
219                 throw new BuildException(String.format(
220                         "Expected one dex file but path value resolve to %d files.",
221                         dexFiles.length));
222             }
223             dexFile = new File(dexFiles[0]);
224         }
225 
226         try {
227             // build list of input files/folders to compute dependencies
228             // add the content of the zip files.
229             List<InputPath> inputPaths = new ArrayList<InputPath>();
230 
231             // resource file
232             InputPath resourceInputPath = new InputPath(new File(mOutFolder, mResourceFile));
233             inputPaths.add(resourceInputPath);
234 
235             // dex file
236             inputPaths.add(new InputPath(dexFile));
237 
238             // zip input files
239             List<File> zipFiles = new ArrayList<File>();
240             for (Path pathList : mZipList) {
241                 for (String path : pathList.list()) {
242                     File f =  new File(path);
243                     zipFiles.add(f);
244                     inputPaths.add(new InputPath(f));
245                 }
246             }
247 
248             // now go through the list of source folders used to add non java files.
249             List<File> sourceFolderList = new ArrayList<File>();
250             if (mHasCode) {
251                 for (Path pathList : mSourceList) {
252                     for (String path : pathList.list()) {
253                         File f =  new File(path);
254                         sourceFolderList.add(f);
255                         // because this is a source folder but we only care about non
256                         // java files.
257                         inputPaths.add(new SourceFolderInputPath(f));
258                     }
259                 }
260             }
261 
262             // now go through the list of jar folders.
263             List<File> jarFileList = new ArrayList<File>();
264             for (Path pathList : mJarfolderList) {
265                 for (String path : pathList.list()) {
266                     // it's ok if top level folders are missing
267                     File folder = new File(path);
268                     if (folder.isDirectory()) {
269                         String[] filenames = folder.list(new FilenameFilter() {
270                             public boolean accept(File dir, String name) {
271                                 return PATTERN_JAR_EXT.matcher(name).matches();
272                             }
273                         });
274 
275                         for (String filename : filenames) {
276                             File f = new File(folder, filename);
277                             jarFileList.add(f);
278                             inputPaths.add(new InputPath(f));
279                         }
280                     }
281                 }
282             }
283 
284             // now go through the list of jar files.
285             for (Path pathList : mJarfileList) {
286                 for (String path : pathList.list()) {
287                     File f = new File(path);
288                     jarFileList.add(f);
289                     inputPaths.add(new InputPath(f));
290                 }
291             }
292 
293             // now the native lib folder.
294             List<FileEntry> nativeFileList = new ArrayList<FileEntry>();
295             for (Path pathList : mNativeList) {
296                 for (String path : pathList.list()) {
297                     // it's ok if top level folders are missing
298                     File folder = new File(path);
299                     if (folder.isDirectory()) {
300                         List<FileEntry> entries = ApkBuilder.getNativeFiles(folder,
301                                 mDebugPackaging);
302                         // add the list to the list of native files and then create an input
303                         // path for each file
304                         nativeFileList.addAll(entries);
305 
306                         for (FileEntry entry : entries) {
307                             inputPaths.add(new InputPath(entry.mFile));
308                         }
309                     }
310                 }
311             }
312 
313             // Finally figure out the path to the dependency file.
314             String depFile = outputFile.getAbsolutePath() + ".d";
315 
316             // check dependencies
317             if (initDependencies(depFile, inputPaths) && dependenciesHaveChanged() == false) {
318                 System.out.println(
319                         "No changes. No need to create apk.");
320                 return;
321             }
322 
323             if (mDebugSigning) {
324                 System.out.println(String.format(
325                         "Creating %s and signing it with a debug key...", outputFile.getName()));
326             } else {
327                 System.out.println(String.format(
328                         "Creating %s for release...", outputFile.getName()));
329             }
330 
331             ApkBuilder apkBuilder = new ApkBuilder(
332                     outputFile,
333                     resourceInputPath.getFile(),
334                     dexFile,
335                     mDebugSigning ? ApkBuilder.getDebugKeystore() : null,
336                     mVerbose ? System.out : null);
337             apkBuilder.setDebugMode(mDebugPackaging);
338 
339 
340             // add the content of the zip files.
341             for (File f : zipFiles) {
342                 apkBuilder.addZipFile(f);
343             }
344 
345             // now go through the list of file to directly add the to the list.
346             for (File f : sourceFolderList) {
347                 apkBuilder.addSourceFolder(f);
348             }
349 
350             // now go through the list of jar folders.
351             for (Path pathList : mJarfolderList) {
352                 for (String path : pathList.list()) {
353                     // it's ok if top level folders are missing
354                     File folder = new File(path);
355                     if (folder.isDirectory()) {
356                         String[] filenames = folder.list(new FilenameFilter() {
357                             public boolean accept(File dir, String name) {
358                                 return PATTERN_JAR_EXT.matcher(name).matches();
359                             }
360                         });
361 
362                         for (String filename : filenames) {
363                             apkBuilder.addResourcesFromJar(new File(folder, filename));
364                         }
365                     }
366                 }
367             }
368 
369             // now go through the list of jar files.
370             for (File f : jarFileList) {
371                 apkBuilder.addResourcesFromJar(f);
372             }
373 
374             // and finally the native files
375             apkBuilder.addNativeLibraries(nativeFileList);
376 
377             // close the archive
378             apkBuilder.sealApk();
379 
380             // and generate the dependency file
381             generateDependencyFile(depFile, inputPaths, outputFile.getAbsolutePath());
382         } catch (DuplicateFileException e) {
383             System.err.println(String.format(
384                     "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s",
385                     e.getArchivePath(), e.getFile1(), e.getFile2()));
386             throw new BuildException(e);
387         } catch (ApkCreationException e) {
388             throw new BuildException(e);
389         } catch (SealedApkException e) {
390             throw new BuildException(e);
391         } catch (IllegalArgumentException e) {
392             throw new BuildException(e);
393         }
394     }
395 
396     @Override
getExecTaskName()397     protected String getExecTaskName() {
398         return "apkbuilder";
399     }
400 }
401