2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.ant;
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;
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;
36 import java.io.FileInputStream;
37 import java.io.FileNotFoundException;
38 import java.util.ArrayList;
39 import java.util.HashSet;
41 import javax.xml.xpath.XPath;
42 import javax.xml.xpath.XPathExpressionException;
45 * Setup/Import Ant task. This task accomplishes:
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>
57 * This is used in build.xml/template.
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";
81 private boolean mDoImport = true;
84 public void execute() throws BuildException {
85 Project antProject = getProject();
87 // get the SDK location
88 String sdkLocation = antProject.getProperty(ProjectProperties.PROPERTY_SDK);
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.");
101 File sdk = new File(sdkLocation);
102 if (sdk.isDirectory() == false) {
103 throw new BuildException(String.format("SDK Location '%s' is not valid.", sdkLocation));
106 // get the target property value
107 String targetHashString = antProject.getProperty(ProjectProperties.PROPERTY_TARGET);
109 boolean isTestProject = false;
111 if (antProject.getProperty("tested.project.dir") != null) {
112 isTestProject = true;
115 if (targetHashString == null) {
116 throw new BuildException("Android Target is not set.");
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));
127 messages.add("Error: " + t.getMessage());
131 public void printf(String msgFormat, Object... args) {
132 messages.add(String.format(msgFormat, args));
135 public void warning(String warningFormat, Object... args) {
136 messages.add(String.format("Warning: " + warningFormat, args));
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);
145 throw new BuildException("Failed to parse SDK content.");
149 IAndroidTarget androidTarget = manager.getTargetFromHashString(targetHashString);
151 if (androidTarget == null) {
152 throw new BuildException(String.format(
153 "Unable to resolve target '%s'", targetHashString));
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());
162 System.out.println("API level: " + androidTarget.getVersion().getApiString());
164 // always check the manifest minSdkVersion.
165 checkManifest(antProject, androidTarget.getVersion());
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);
171 String androidAidl = androidTarget.getPath(IAndroidTarget.ANDROID_AIDL);
172 antProject.setProperty(PROPERTY_ANDROID_AIDL, androidAidl);
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));
178 // sets up the boot classpath
180 // create the Path object
181 Path bootclasspath = new Path(antProject);
183 // create a PathElement for the framework jar
184 PathElement element = bootclasspath.createPathElement();
185 element.setPath(androidJar);
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);
196 element = bootclasspath.createPathElement();
197 element.setPath(library.getJarPath());
202 // finally sets the path in the project with a reference
203 antProject.addReference(REF_CLASSPATH, bootclasspath);
205 // find the file to import, and import it.
206 String templateFolder = androidTarget.getPath(IAndroidTarget.TEMPLATES);
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);
220 // Now the import section. This is only executed if the task actually has to import a file.
222 // make sure the file exists.
223 File templates = new File(templateFolder);
225 if (templates.isDirectory() == false) {
226 throw new BuildException(String.format("Template directory '%s' is missing.",
230 String importedRulesFileName = isTestProject ? ANDROID_TEST_RULES : ANDROID_RULES;
232 // now check the rules file exists.
233 File rules = new File(templateFolder, importedRulesFileName);
235 if (rules.isFile() == false) {
236 throw new BuildException(String.format("Build rules file '%s' is missing.",
240 // set the file location to import
241 setFile(rules.getAbsolutePath());
249 * Sets the value of the "import" attribute.
250 * @param value the value.
252 public void setImport(boolean value) {
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.
261 private void checkManifest(Project antProject, AndroidVersion androidVersion) {
263 File manifest = new File(antProject.getBaseDir(), "AndroidManifest.xml");
265 XPath xPath = AndroidXPathFactory.newXPath();
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)));
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'",
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;
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));
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));
310 // no minSdkVersion? display a warning
312 "WARNING: No minSdkVersion value set. Application will install on all Android versions.");
315 } catch (XPathExpressionException e) {
316 throw new BuildException(e);
317 } catch (FileNotFoundException e) {
318 throw new BuildException(e);