OSDN Git Service

android-2.1_r1 snapshot
[android-x86/sdk.git] / anttasks / src / com / android / ant / SetupTask.java
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.AndroidVersion;
20 import com.android.sdklib.IAndroidTarget;
21 import com.android.sdklib.ISdkLog;
22 import com.android.sdklib.SdkManager;
23 import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
24 import com.android.sdklib.internal.project.ProjectProperties;
25 import com.android.sdklib.xml.AndroidXPathFactory;
26 import com.android.sdklib.xml.AndroidManifest;
27
28 import org.apache.tools.ant.BuildException;
29 import org.apache.tools.ant.Project;
30 import org.apache.tools.ant.taskdefs.ImportTask;
31 import org.apache.tools.ant.types.Path;
32 import org.apache.tools.ant.types.Path.PathElement;
33 import org.xml.sax.InputSource;
34
35 import java.io.File;
36 import java.io.FileInputStream;
37 import java.io.FileNotFoundException;
38 import java.util.ArrayList;
39 import java.util.HashSet;
40
41 import javax.xml.xpath.XPath;
42 import javax.xml.xpath.XPathExpressionException;
43
44 /**
45  * Setup/Import Ant task. This task accomplishes:
46  * <ul>
47  * <li>Gets the project target hash string from {@link ProjectProperties#PROPERTY_TARGET},
48  * and resolves it to get the project's {@link IAndroidTarget}.</li>
49  * <li>Sets up properties so that aapt can find the android.jar in the resolved target.</li>
50  * <li>Sets up the boot classpath ref so that the <code>javac</code> task knows where to find
51  * the libraries. This includes the default android.jar from the resolved target but also optional
52  * libraries provided by the target (if any, when the target is an add-on).</li>
53  * <li>Imports the build rules located in the resolved target so that the build actually does
54  * something. This can be disabled with the attribute <var>import</var> set to <code>false</code>
55  * </li></ul>
56  *
57  * This is used in build.xml/template.
58  *
59  */
60 public final class SetupTask extends ImportTask {
61     private final static String ANDROID_RULES = "android_rules.xml";
62     // additional android rules for test project - depends on android_rules.xml
63     private final static String ANDROID_TEST_RULES = "android_test_rules.xml";
64     // ant property with the path to the android.jar
65     private final static String PROPERTY_ANDROID_JAR = "android.jar";
66     // LEGACY - compatibility with 1.6 and before
67     private final static String PROPERTY_ANDROID_JAR_LEGACY = "android-jar";
68     // ant property with the path to the framework.jar
69     private final static String PROPERTY_ANDROID_AIDL = "android.aidl";
70     // LEGACY - compatibility with 1.6 and before
71     private final static String PROPERTY_ANDROID_AIDL_LEGACY = "android-aidl";
72     // ant property with the path to the aapt tool
73     private final static String PROPERTY_AAPT = "aapt";
74     // ant property with the path to the aidl tool
75     private final static String PROPERTY_AIDL = "aidl";
76     // ant property with the path to the dx tool
77     private final static String PROPERTY_DX = "dx";
78     // ref id to the <path> object containing all the boot classpaths.
79     private final static String REF_CLASSPATH = "android.target.classpath";
80
81     private boolean mDoImport = true;
82
83     @Override
84     public void execute() throws BuildException {
85         Project antProject = getProject();
86
87         // get the SDK location
88         String sdkLocation = antProject.getProperty(ProjectProperties.PROPERTY_SDK);
89
90         // check if it's valid and exists
91         if (sdkLocation == null || sdkLocation.length() == 0) {
92             // LEGACY support: project created with 1.6 or before may be using a different
93             // property to declare the location of the SDK. At this point, we cannot
94             // yet check which target is running so we check both always.
95             sdkLocation = antProject.getProperty(ProjectProperties.PROPERTY_SDK_LEGACY);
96             if (sdkLocation == null || sdkLocation.length() == 0) {
97                 throw new BuildException("SDK Location is not set.");
98             }
99         }
100
101         File sdk = new File(sdkLocation);
102         if (sdk.isDirectory() == false) {
103             throw new BuildException(String.format("SDK Location '%s' is not valid.", sdkLocation));
104         }
105
106         // get the target property value
107         String targetHashString = antProject.getProperty(ProjectProperties.PROPERTY_TARGET);
108
109         boolean isTestProject = false;
110
111         if (antProject.getProperty("tested.project.dir") != null) {
112             isTestProject = true;
113         }
114
115         if (targetHashString == null) {
116             throw new BuildException("Android Target is not set.");
117         }
118
119         // load up the sdk targets.
120         final ArrayList<String> messages = new ArrayList<String>();
121         SdkManager manager = SdkManager.createManager(sdkLocation, new ISdkLog() {
122             public void error(Throwable t, String errorFormat, Object... args) {
123                 if (errorFormat != null) {
124                     messages.add(String.format("Error: " + errorFormat, args));
125                 }
126                 if (t != null) {
127                     messages.add("Error: " + t.getMessage());
128                 }
129             }
130
131             public void printf(String msgFormat, Object... args) {
132                 messages.add(String.format(msgFormat, args));
133             }
134
135             public void warning(String warningFormat, Object... args) {
136                 messages.add(String.format("Warning: " + warningFormat, args));
137             }
138         });
139
140         if (manager == null) {
141             // since we failed to parse the SDK, lets display the parsing output.
142             for (String msg : messages) {
143                 System.out.println(msg);
144             }
145             throw new BuildException("Failed to parse SDK content.");
146         }
147
148         // resolve it
149         IAndroidTarget androidTarget = manager.getTargetFromHashString(targetHashString);
150
151         if (androidTarget == null) {
152             throw new BuildException(String.format(
153                     "Unable to resolve target '%s'", targetHashString));
154         }
155
156         // display it
157         System.out.println("Project Target: " + androidTarget.getName());
158         if (androidTarget.isPlatform() == false) {
159             System.out.println("Vendor: " + androidTarget.getVendor());
160             System.out.println("Platform Version: " + androidTarget.getVersionName());
161         }
162         System.out.println("API level: " + androidTarget.getVersion().getApiString());
163
164         // always check the manifest minSdkVersion.
165         checkManifest(antProject, androidTarget.getVersion());
166
167         // sets up the properties to find android.jar/framework.aidl/target tools
168         String androidJar = androidTarget.getPath(IAndroidTarget.ANDROID_JAR);
169         antProject.setProperty(PROPERTY_ANDROID_JAR, androidJar);
170
171         String androidAidl = androidTarget.getPath(IAndroidTarget.ANDROID_AIDL);
172         antProject.setProperty(PROPERTY_ANDROID_AIDL, androidAidl);
173
174         antProject.setProperty(PROPERTY_AAPT, androidTarget.getPath(IAndroidTarget.AAPT));
175         antProject.setProperty(PROPERTY_AIDL, androidTarget.getPath(IAndroidTarget.AIDL));
176         antProject.setProperty(PROPERTY_DX, androidTarget.getPath(IAndroidTarget.DX));
177
178         // sets up the boot classpath
179
180         // create the Path object
181         Path bootclasspath = new Path(antProject);
182
183         // create a PathElement for the framework jar
184         PathElement element = bootclasspath.createPathElement();
185         element.setPath(androidJar);
186
187         // create PathElement for each optional library.
188         IOptionalLibrary[] libraries = androidTarget.getOptionalLibraries();
189         if (libraries != null) {
190             HashSet<String> visitedJars = new HashSet<String>();
191             for (IOptionalLibrary library : libraries) {
192                 String jarPath = library.getJarPath();
193                 if (visitedJars.contains(jarPath) == false) {
194                     visitedJars.add(jarPath);
195
196                     element = bootclasspath.createPathElement();
197                     element.setPath(library.getJarPath());
198                 }
199             }
200         }
201
202         // finally sets the path in the project with a reference
203         antProject.addReference(REF_CLASSPATH, bootclasspath);
204
205         // find the file to import, and import it.
206         String templateFolder = androidTarget.getPath(IAndroidTarget.TEMPLATES);
207
208         // LEGACY support. android_rules.xml in older platforms expects properties with
209         // older names. This sets those properties to make sure the rules will work.
210         if (androidTarget.getVersion().getApiLevel() <= 4) { // 1.6 and earlier
211             antProject.setProperty(PROPERTY_ANDROID_JAR_LEGACY, androidJar);
212             antProject.setProperty(PROPERTY_ANDROID_AIDL_LEGACY, androidAidl);
213             antProject.setProperty(ProjectProperties.PROPERTY_SDK_LEGACY, sdkLocation);
214             String appPackage = antProject.getProperty(ProjectProperties.PROPERTY_APP_PACKAGE);
215             if (appPackage != null && appPackage.length() > 0) {
216                 antProject.setProperty(ProjectProperties.PROPERTY_APP_PACKAGE_LEGACY, appPackage);
217             }
218         }
219
220         // Now the import section. This is only executed if the task actually has to import a file.
221         if (mDoImport) {
222             // make sure the file exists.
223             File templates = new File(templateFolder);
224
225             if (templates.isDirectory() == false) {
226                 throw new BuildException(String.format("Template directory '%s' is missing.",
227                         templateFolder));
228             }
229
230             String importedRulesFileName = isTestProject ? ANDROID_TEST_RULES : ANDROID_RULES;
231
232             // now check the rules file exists.
233             File rules = new File(templateFolder, importedRulesFileName);
234
235             if (rules.isFile() == false) {
236                 throw new BuildException(String.format("Build rules file '%s' is missing.",
237                         templateFolder));
238             }
239
240             // set the file location to import
241             setFile(rules.getAbsolutePath());
242
243             // and import
244             super.execute();
245         }
246     }
247
248     /**
249      * Sets the value of the "import" attribute.
250      * @param value the value.
251      */
252     public void setImport(boolean value) {
253         mDoImport = value;
254     }
255
256     /**
257      * Checks the manifest <code>minSdkVersion</code> attribute.
258      * @param antProject the ant project
259      * @param androidVersion the version of the platform the project is compiling against.
260      */
261     private void checkManifest(Project antProject, AndroidVersion androidVersion) {
262         try {
263             File manifest = new File(antProject.getBaseDir(), "AndroidManifest.xml");
264
265             XPath xPath = AndroidXPathFactory.newXPath();
266
267             String value = xPath.evaluate(
268                     "/"  + AndroidManifest.NODE_MANIFEST +
269                     "/"  + AndroidManifest.NODE_USES_SDK +
270                     "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX + ":" +
271                             AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
272                     new InputSource(new FileInputStream(manifest)));
273
274             if (androidVersion.isPreview()) {
275                 // in preview mode, the content of the minSdkVersion must match exactly the
276                 // platform codename.
277                 String codeName = androidVersion.getCodename();
278                 if (codeName.equals(value) == false) {
279                     throw new BuildException(String.format(
280                             "For '%1$s' SDK Preview, attribute minSdkVersion in AndroidManifest.xml must be '%1$s'",
281                             codeName));
282                 }
283             } else if (value.length() > 0) {
284                 // for normal platform, we'll only display warnings if the value is lower or higher
285                 // than the target api level.
286                 // First convert to an int.
287                 int minSdkValue = -1;
288                 try {
289                     minSdkValue = Integer.parseInt(value);
290                 } catch (NumberFormatException e) {
291                     // looks like it's not a number: error!
292                     throw new BuildException(String.format(
293                             "Attribute %1$s in AndroidManifest.xml must be an Integer!",
294                             AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION));
295                 }
296
297                 int projectApiLevel = androidVersion.getApiLevel();
298                 if (minSdkValue < projectApiLevel) {
299                     System.out.println(String.format(
300                             "WARNING: Attribute %1$s in AndroidManifest.xml (%2$d) is lower than the project target API level (%3$d)",
301                             AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
302                             minSdkValue, projectApiLevel));
303                 } else if (minSdkValue > androidVersion.getApiLevel()) {
304                     System.out.println(String.format(
305                             "WARNING: Attribute %1$s in AndroidManifest.xml (%2$d) is higher than the project target API level (%3$d)",
306                             AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
307                             minSdkValue, projectApiLevel));
308                 }
309             } else {
310                 // no minSdkVersion? display a warning
311                 System.out.println(
312                         "WARNING: No minSdkVersion value set. Application will install on all Android versions.");
313             }
314
315         } catch (XPathExpressionException e) {
316             throw new BuildException(e);
317         } catch (FileNotFoundException e) {
318             throw new BuildException(e);
319         }
320     }
321 }