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 execute()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 */ setImport(boolean value)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 */ checkManifest(Project antProject, AndroidVersion androidVersion)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 } 322