1/*
2 * Copyright (C) 2008 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
17package com.android.sdklib.build;
18
19
20import java.io.File;
21import java.io.FilenameFilter;
22import java.util.ArrayList;
23import java.util.regex.Pattern;
24
25
26/**
27 * Command line APK builder with signing support.
28 */
29public final class ApkBuilderMain {
30
31    private final static Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$",
32            Pattern.CASE_INSENSITIVE);
33
34    /**
35     * Main method. This is meant to be called from the command line through an exec.
36     * <p/>WARNING: this will call {@link System#exit(int)} if anything goes wrong.
37     * @param args command line arguments.
38     */
39    public static void main(String[] args) {
40        if (args.length < 1) {
41            printUsageAndQuit();
42        }
43
44        System.err.println("\nTHIS TOOL IS DEPRECATED. See --help for more information.\n");
45
46        try {
47            File outApk = new File(args[0]);
48
49            File dexFile = null;
50            ArrayList<File> zipArchives = new ArrayList<File>();
51            ArrayList<File> sourceFolders = new ArrayList<File>();
52            ArrayList<File> jarFiles = new ArrayList<File>();
53            ArrayList<File> nativeFolders = new ArrayList<File>();
54
55            boolean verbose = false;
56            boolean signed = true;
57            boolean debug = false;
58
59            int index = 1;
60            do {
61                String argument = args[index++];
62
63                if ("-v".equals(argument)) {
64                    verbose = true;
65
66                } else if ("-d".equals(argument)) {
67                    debug = true;
68
69                } else if ("-u".equals(argument)) {
70                    signed = false;
71
72                } else if ("-z".equals(argument)) {
73                    // quick check on the next argument.
74                    if (index == args.length)  {
75                        printAndExit("Missing value for -z");
76                    }
77
78                    zipArchives.add(new File(args[index++]));
79                } else if ("-f". equals(argument)) {
80                    if (dexFile != null) {
81                        // can't have more than one dex file.
82                        printAndExit("Can't have more than one dex file (-f)");
83                    }
84                    // quick check on the next argument.
85                    if (index == args.length) {
86                        printAndExit("Missing value for -f");
87                    }
88
89                    dexFile = new File(args[index++]);
90                } else if ("-rf". equals(argument)) {
91                    // quick check on the next argument.
92                    if (index == args.length) {
93                        printAndExit("Missing value for -rf");
94                    }
95
96                    sourceFolders.add(new File(args[index++]));
97                } else if ("-rj". equals(argument)) {
98                    // quick check on the next argument.
99                    if (index == args.length) {
100                        printAndExit("Missing value for -rj");
101                    }
102
103                    jarFiles.add(new File(args[index++]));
104                } else if ("-nf".equals(argument)) {
105                    // quick check on the next argument.
106                    if (index == args.length) {
107                        printAndExit("Missing value for -nf");
108                    }
109
110                    nativeFolders.add(new File(args[index++]));
111                } else if ("-storetype".equals(argument)) {
112                    // quick check on the next argument.
113                    if (index == args.length) {
114                        printAndExit("Missing value for -storetype");
115                    }
116
117                    // FIXME
118                } else {
119                    printAndExit("Unknown argument: " + argument);
120                }
121            } while (index < args.length);
122
123            if (zipArchives.size() == 0) {
124                printAndExit("No zip archive, there must be one for the resources");
125            }
126
127            // create the builder with the basic files.
128            ApkBuilder builder = new ApkBuilder(outApk, zipArchives.get(0), dexFile,
129                    signed ? ApkBuilder.getDebugKeystore() : null,
130                    verbose ? System.out : null);
131            builder.setDebugMode(debug);
132
133            // add the rest of the files.
134            // first zip Archive was used in the constructor.
135            for (int i = 1 ; i < zipArchives.size() ; i++) {
136                builder.addZipFile(zipArchives.get(i));
137            }
138
139            for (File sourceFolder : sourceFolders) {
140                builder.addSourceFolder(sourceFolder);
141            }
142
143            for (File jarFile : jarFiles) {
144                if (jarFile.isDirectory()) {
145                    String[] filenames = jarFile.list(new FilenameFilter() {
146                        @Override
147                        public boolean accept(File dir, String name) {
148                            return PATTERN_JAR_EXT.matcher(name).matches();
149                        }
150                    });
151
152                    for (String filename : filenames) {
153                        builder.addResourcesFromJar(new File(jarFile, filename));
154                    }
155                } else {
156                    builder.addResourcesFromJar(jarFile);
157                }
158            }
159
160            for (File nativeFolder : nativeFolders) {
161                builder.addNativeLibraries(nativeFolder);
162            }
163
164            // seal the apk
165            builder.sealApk();
166
167
168        } catch (ApkCreationException e) {
169            printAndExit(e.getMessage());
170        } catch (DuplicateFileException e) {
171            printAndExit(String.format(
172                    "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s",
173                    e.getArchivePath(), e.getFile1(), e.getFile2()));
174        } catch (SealedApkException e) {
175            printAndExit(e.getMessage());
176        } catch (Exception e) {
177            e.printStackTrace();
178        }
179    }
180
181    private static void printUsageAndQuit() {
182        // 80 cols marker:  01234567890123456789012345678901234567890123456789012345678901234567890123456789
183        System.err.println("\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
184        System.err.println("THIS TOOL IS DEPRECATED and may stop working at any time!\n");
185        System.err.println("If you wish to use apkbuilder for a custom build system, please look at the");
186        System.err.println("com.android.sdklib.build.ApkBuilder which provides support for");
187        System.err.println("recent build improvements including library projects.");
188        System.err.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n");
189        System.err.println("A command line tool to package an Android application from various sources.");
190        System.err.println("Usage: apkbuilder <out archive> [-v][-u][-storetype STORE_TYPE] [-z inputzip]");
191        System.err.println("            [-f inputfile] [-rf input-folder] [-rj -input-path]");
192        System.err.println("");
193        System.err.println("    -v      Verbose.");
194        System.err.println("    -d      Debug Mode: Includes debug files in the APK file.");
195        System.err.println("    -u      Creates an unsigned package.");
196        System.err.println("    -storetype Forces the KeyStore type. If ommited the default is used.");
197        System.err.println("");
198        System.err.println("    -z      Followed by the path to a zip archive.");
199        System.err.println("            Adds the content of the application package.");
200        System.err.println("");
201        System.err.println("    -f      Followed by the path to a file.");
202        System.err.println("            Adds the file to the application package.");
203        System.err.println("");
204        System.err.println("    -rf     Followed by the path to a source folder.");
205        System.err.println("            Adds the java resources found in that folder to the application");
206        System.err.println("            package, while keeping their path relative to the source folder.");
207        System.err.println("");
208        System.err.println("    -rj     Followed by the path to a jar file or a folder containing");
209        System.err.println("            jar files.");
210        System.err.println("            Adds the java resources found in the jar file(s) to the application");
211        System.err.println("            package.");
212        System.err.println("");
213        System.err.println("    -nf     Followed by the root folder containing native libraries to");
214        System.err.println("            include in the application package.");
215
216        System.exit(1);
217    }
218
219    private static void printAndExit(String... messages) {
220        for (String message : messages) {
221            System.err.println(message);
222        }
223        System.exit(1);
224    }
225
226    private ApkBuilderMain() {
227    }
228}
229