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