1 /* 2 * Copyright (C) 2012 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.SdkConstants; 20 import com.android.annotations.NonNull; 21 import com.android.sdklib.AndroidVersion; 22 import com.android.sdklib.IAndroidTarget; 23 import com.android.sdklib.IAndroidTarget.IOptionalLibrary; 24 import com.android.sdklib.SdkManager; 25 import com.android.sdklib.internal.project.ProjectProperties; 26 import com.android.utils.ILogger; 27 import com.android.xml.AndroidManifest; 28 import com.android.xml.AndroidXPathFactory; 29 30 import org.apache.tools.ant.BuildException; 31 import org.apache.tools.ant.Project; 32 import org.apache.tools.ant.Task; 33 import org.apache.tools.ant.types.Path; 34 import org.apache.tools.ant.types.Path.PathElement; 35 import org.xml.sax.InputSource; 36 37 import java.io.File; 38 import java.io.FileInputStream; 39 import java.io.FileNotFoundException; 40 import java.util.ArrayList; 41 import java.util.HashSet; 42 43 import javax.xml.xpath.XPath; 44 import javax.xml.xpath.XPathExpressionException; 45 46 /** 47 * Task to resolve the target of the current Android project. 48 * 49 * Out params: 50 * <code>bootClassPathOut</code>: The boot class path of the project. 51 * 52 * <code>androidJarFileOut</code>: the android.jar used by the project. 53 * 54 * <code>androidAidlFileOut</code>: the framework.aidl used by the project. 55 * 56 * <code>targetApiOut</code>: the build API level. 57 * 58 * <code>minSdkVersionOut</code>: the app's minSdkVersion. 59 * 60 */ 61 public class GetTargetTask extends Task { 62 63 private String mBootClassPathOut; 64 private String mAndroidJarFileOut; 65 private String mAndroidAidlFileOut; 66 private String mTargetApiOut; 67 private String mMinSdkVersionOut; 68 setBootClassPathOut(String bootClassPathOut)69 public void setBootClassPathOut(String bootClassPathOut) { 70 mBootClassPathOut = bootClassPathOut; 71 } 72 setAndroidJarFileOut(String androidJarFileOut)73 public void setAndroidJarFileOut(String androidJarFileOut) { 74 mAndroidJarFileOut = androidJarFileOut; 75 } 76 setAndroidAidlFileOut(String androidAidlFileOut)77 public void setAndroidAidlFileOut(String androidAidlFileOut) { 78 mAndroidAidlFileOut = androidAidlFileOut; 79 } 80 setTargetApiOut(String targetApiOut)81 public void setTargetApiOut(String targetApiOut) { 82 mTargetApiOut = targetApiOut; 83 } 84 setMinSdkVersionOut(String minSdkVersionOut)85 public void setMinSdkVersionOut(String minSdkVersionOut) { 86 mMinSdkVersionOut = minSdkVersionOut; 87 } 88 89 @Override execute()90 public void execute() throws BuildException { 91 if (mBootClassPathOut == null) { 92 throw new BuildException("Missing attribute bootClassPathOut"); 93 } 94 if (mAndroidJarFileOut == null) { 95 throw new BuildException("Missing attribute androidJarFileOut"); 96 } 97 if (mAndroidAidlFileOut == null) { 98 throw new BuildException("Missing attribute androidAidlFileOut"); 99 } 100 if (mTargetApiOut == null) { 101 throw new BuildException("Missing attribute targetApiOut"); 102 } 103 if (mMinSdkVersionOut == null) { 104 throw new BuildException("Missing attribute mMinSdkVersionOut"); 105 } 106 107 Project antProject = getProject(); 108 109 // get the SDK location 110 File sdkDir = TaskHelper.getSdkLocation(antProject); 111 112 // get the target property value 113 String targetHashString = antProject.getProperty(ProjectProperties.PROPERTY_TARGET); 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(sdkDir.getPath(), new ILogger() { 122 @Override 123 public void error(Throwable t, String errorFormat, Object... args) { 124 if (errorFormat != null) { 125 messages.add(String.format("Error: " + errorFormat, args)); 126 } 127 if (t != null) { 128 messages.add("Error: " + t.getMessage()); 129 } 130 } 131 132 @Override 133 public void info(@NonNull String msgFormat, Object... args) { 134 messages.add(String.format(msgFormat, args)); 135 } 136 137 @Override 138 public void verbose(@NonNull String msgFormat, Object... args) { 139 info(msgFormat, args); 140 } 141 142 @Override 143 public void warning(@NonNull String warningFormat, Object... args) { 144 messages.add(String.format("Warning: " + warningFormat, args)); 145 } 146 }); 147 148 if (manager == null) { 149 // since we failed to parse the SDK, lets display the parsing output. 150 for (String msg : messages) { 151 System.out.println(msg); 152 } 153 throw new BuildException("Failed to parse SDK content."); 154 } 155 156 // resolve it 157 IAndroidTarget androidTarget = manager.getTargetFromHashString(targetHashString); 158 159 if (androidTarget == null) { 160 throw new BuildException(String.format( 161 "Unable to resolve project target '%s'", targetHashString)); 162 } 163 164 // display the project info 165 System.out.println( "Project Target: " + androidTarget.getName()); 166 if (androidTarget.isPlatform() == false) { 167 System.out.println("Vendor: " + androidTarget.getVendor()); 168 System.out.println("Platform Version: " + androidTarget.getVersionName()); 169 } 170 System.out.println( "API level: " + androidTarget.getVersion().getApiString()); 171 172 antProject.setProperty(mTargetApiOut, 173 Integer.toString(androidTarget.getVersion().getApiLevel())); 174 175 // always check the manifest minSdkVersion. 176 checkManifest(antProject, androidTarget.getVersion()); 177 178 // sets up the properties to find android.jar/framework.aidl/target tools 179 String androidJar = androidTarget.getPath(IAndroidTarget.ANDROID_JAR); 180 antProject.setProperty(mAndroidJarFileOut, androidJar); 181 182 String androidAidl = androidTarget.getPath(IAndroidTarget.ANDROID_AIDL); 183 antProject.setProperty(mAndroidAidlFileOut, androidAidl); 184 185 // sets up the boot classpath 186 187 // create the Path object 188 Path bootclasspath = new Path(antProject); 189 190 // create a PathElement for the framework jar 191 PathElement element = bootclasspath.createPathElement(); 192 element.setPath(androidJar); 193 194 // create PathElement for each optional library. 195 IOptionalLibrary[] libraries = androidTarget.getOptionalLibraries(); 196 if (libraries != null) { 197 HashSet<String> visitedJars = new HashSet<String>(); 198 for (IOptionalLibrary library : libraries) { 199 String jarPath = library.getJarPath(); 200 if (visitedJars.contains(jarPath) == false) { 201 visitedJars.add(jarPath); 202 203 element = bootclasspath.createPathElement(); 204 element.setPath(jarPath); 205 } 206 } 207 } 208 209 // sets the path in the project with a reference 210 antProject.addReference(mBootClassPathOut, bootclasspath); 211 } 212 213 /** 214 * Checks the manifest <code>minSdkVersion</code> attribute. 215 * @param antProject the ant project 216 * @param androidVersion the version of the platform the project is compiling against. 217 */ checkManifest(Project antProject, AndroidVersion androidVersion)218 private void checkManifest(Project antProject, AndroidVersion androidVersion) { 219 try { 220 File manifest = new File(antProject.getBaseDir(), SdkConstants.FN_ANDROID_MANIFEST_XML); 221 222 XPath xPath = AndroidXPathFactory.newXPath(); 223 224 // check the package name. 225 String value = xPath.evaluate( 226 "/" + AndroidManifest.NODE_MANIFEST + 227 "/@" + AndroidManifest.ATTRIBUTE_PACKAGE, 228 new InputSource(new FileInputStream(manifest))); 229 if (value != null) { // aapt will complain if it's missing. 230 // only need to check that the package has 2 segments 231 if (value.indexOf('.') == -1) { 232 throw new BuildException(String.format( 233 "Application package '%1$s' must have a minimum of 2 segments.", 234 value)); 235 } 236 } 237 238 // check the minSdkVersion value 239 value = xPath.evaluate( 240 "/" + AndroidManifest.NODE_MANIFEST + 241 "/" + AndroidManifest.NODE_USES_SDK + 242 "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX + ":" + 243 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, 244 new InputSource(new FileInputStream(manifest))); 245 246 if (androidVersion.isPreview()) { 247 // in preview mode, the content of the minSdkVersion must match exactly the 248 // platform codename. 249 String codeName = androidVersion.getCodename(); 250 if (codeName.equals(value) == false) { 251 throw new BuildException(String.format( 252 "For '%1$s' SDK Preview, attribute minSdkVersion in AndroidManifest.xml must be '%1$s' (current: %2$s)", 253 codeName, value)); 254 } 255 256 // set the minSdkVersion to the previous API level (which is actually the value in 257 // androidVersion.) 258 antProject.setProperty(mMinSdkVersionOut, 259 Integer.toString(androidVersion.getApiLevel())); 260 261 } else if (value.length() > 0) { 262 // for normal platform, we'll only display warnings if the value is lower or higher 263 // than the target api level. 264 // First convert to an int. 265 int minSdkValue = -1; 266 try { 267 minSdkValue = Integer.parseInt(value); 268 } catch (NumberFormatException e) { 269 // looks like it's not a number: error! 270 throw new BuildException(String.format( 271 "Attribute %1$s in AndroidManifest.xml must be an Integer!", 272 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION)); 273 } 274 275 // set the minSdkVersion to the value 276 antProject.setProperty(mMinSdkVersionOut, value); 277 278 int projectApiLevel = androidVersion.getApiLevel(); 279 if (minSdkValue > androidVersion.getApiLevel()) { 280 System.out.println(String.format( 281 "WARNING: Attribute %1$s in AndroidManifest.xml (%2$d) is higher than the project target API level (%3$d)", 282 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, 283 minSdkValue, projectApiLevel)); 284 } 285 } else { 286 // no minSdkVersion? display a warning 287 System.out.println( 288 "WARNING: No minSdkVersion value set. Application will install on all Android versions."); 289 290 // set the target api to 1 291 antProject.setProperty(mMinSdkVersionOut, "1"); 292 } 293 294 } catch (XPathExpressionException e) { 295 throw new BuildException(e); 296 } catch (FileNotFoundException e) { 297 throw new BuildException(e); 298 } 299 } 300 } 301