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.IAndroidTarget; 20 import com.android.sdklib.ISdkLog; 21 import com.android.sdklib.SdkManager; 22 import com.android.sdklib.IAndroidTarget.IOptionalLibrary; 23 import com.android.sdklib.internal.project.ProjectProperties; 24 import com.android.sdklib.xml.AndroidXPathFactory; 25 import com.android.sdklib.xml.ManifestConstants; 26 27 import org.apache.tools.ant.BuildException; 28 import org.apache.tools.ant.Project; 29 import org.apache.tools.ant.taskdefs.ImportTask; 30 import org.apache.tools.ant.types.Path; 31 import org.apache.tools.ant.types.Path.PathElement; 32 import org.xml.sax.InputSource; 33 34 import java.io.File; 35 import java.io.FileInputStream; 36 import java.io.FileNotFoundException; 37 import java.util.ArrayList; 38 import java.util.HashSet; 39 40 import javax.xml.xpath.XPath; 41 import javax.xml.xpath.XPathExpressionException; 42 43 /** 44 * Setup/Import Ant task. This task accomplishes: 45 * <ul> 46 * <li>Gets the project target hash string from {@link ProjectProperties#PROPERTY_TARGET}, 47 * and resolves it to get the project's {@link IAndroidTarget}.</li> 48 * <li>Sets up properties so that aapt can find the android.jar in the resolved target.</li> 49 * <li>Sets up the boot classpath ref so that the <code>javac</code> task knows where to find 50 * the libraries. This includes the default android.jar from the resolved target but also optional 51 * libraries provided by the target (if any, when the target is an add-on).</li> 52 * <li>Imports the build rules located in the resolved target so that the build actually does 53 * something. This can be disabled with the attribute <var>import</var> set to <code>false</code> 54 * </li></ul> 55 * 56 * This is used in build.xml/template. 57 * 58 */ 59 public final class SetupTask extends ImportTask { 60 private final static String ANDROID_RULES = "android_rules.xml"; 61 62 // ant property with the path to the android.jar 63 private final static String PROPERTY_ANDROID_JAR = "android-jar"; 64 // ant property with the path to the framework.jar 65 private final static String PROPERTY_ANDROID_AIDL = "android-aidl"; 66 // ant property with the path to the aapt tool 67 private final static String PROPERTY_AAPT = "aapt"; 68 // ant property with the path to the aidl tool 69 private final static String PROPERTY_AIDL = "aidl"; 70 // ant property with the path to the dx tool 71 private final static String PROPERTY_DX = "dx"; 72 // ref id to the <path> object containing all the boot classpaths. 73 private final static String REF_CLASSPATH = "android.target.classpath"; 74 75 private boolean mDoImport = true; 76 77 @Override execute()78 public void execute() throws BuildException { 79 Project antProject = getProject(); 80 81 // get the SDK location 82 String sdkLocation = antProject.getProperty(ProjectProperties.PROPERTY_SDK); 83 84 // check if it's valid and exists 85 if (sdkLocation == null || sdkLocation.length() == 0) { 86 throw new BuildException("SDK Location is not set."); 87 } 88 89 File sdk = new File(sdkLocation); 90 if (sdk.isDirectory() == false) { 91 throw new BuildException(String.format("SDK Location '%s' is not valid.", sdkLocation)); 92 } 93 94 // get the target property value 95 String targetHashString = antProject.getProperty(ProjectProperties.PROPERTY_TARGET); 96 if (targetHashString == null) { 97 throw new BuildException("Android Target is not set."); 98 } 99 100 // load up the sdk targets. 101 final ArrayList<String> messages = new ArrayList<String>(); 102 SdkManager manager = SdkManager.createManager(sdkLocation, new ISdkLog() { 103 public void error(Throwable t, String errorFormat, Object... args) { 104 if (errorFormat != null) { 105 messages.add(String.format("Error: " + errorFormat, args)); 106 } 107 if (t != null) { 108 messages.add("Error: " + t.getMessage()); 109 } 110 } 111 112 public void printf(String msgFormat, Object... args) { 113 messages.add(String.format(msgFormat, args)); 114 } 115 116 public void warning(String warningFormat, Object... args) { 117 messages.add(String.format("Warning: " + warningFormat, args)); 118 } 119 }); 120 121 if (manager == null) { 122 // since we failed to parse the SDK, lets display the parsing output. 123 for (String msg : messages) { 124 System.out.println(msg); 125 } 126 throw new BuildException("Failed to parse SDK content."); 127 } 128 129 // resolve it 130 IAndroidTarget androidTarget = manager.getTargetFromHashString(targetHashString); 131 132 if (androidTarget == null) { 133 throw new BuildException(String.format( 134 "Unable to resolve target '%s'", targetHashString)); 135 } 136 137 // display it 138 System.out.println("Project Target: " + androidTarget.getName()); 139 if (androidTarget.isPlatform() == false) { 140 System.out.println("Vendor: " + androidTarget.getVendor()); 141 System.out.println("Platform Version: " + androidTarget.getVersionName()); 142 } 143 System.out.println("API level: " + androidTarget.getVersion().getApiString()); 144 145 // if needed check the manifest so that it matches the target 146 if (androidTarget.getVersion().isPreview()) { 147 // for preview, the manifest minSdkVersion node *must* match the target codename 148 checkManifest(antProject, androidTarget.getVersion().getCodename()); 149 } 150 151 // sets up the properties to find android.jar/framework.aidl/target tools 152 String androidJar = androidTarget.getPath(IAndroidTarget.ANDROID_JAR); 153 antProject.setProperty(PROPERTY_ANDROID_JAR, androidJar); 154 155 antProject.setProperty(PROPERTY_ANDROID_AIDL, 156 androidTarget.getPath(IAndroidTarget.ANDROID_AIDL)); 157 antProject.setProperty(PROPERTY_AAPT, androidTarget.getPath(IAndroidTarget.AAPT)); 158 antProject.setProperty(PROPERTY_AIDL, androidTarget.getPath(IAndroidTarget.AIDL)); 159 antProject.setProperty(PROPERTY_DX, androidTarget.getPath(IAndroidTarget.DX)); 160 161 // sets up the boot classpath 162 163 // create the Path object 164 Path bootclasspath = new Path(antProject); 165 166 // create a PathElement for the framework jar 167 PathElement element = bootclasspath.createPathElement(); 168 element.setPath(androidJar); 169 170 // create PathElement for each optional library. 171 IOptionalLibrary[] libraries = androidTarget.getOptionalLibraries(); 172 if (libraries != null) { 173 HashSet<String> visitedJars = new HashSet<String>(); 174 for (IOptionalLibrary library : libraries) { 175 String jarPath = library.getJarPath(); 176 if (visitedJars.contains(jarPath) == false) { 177 visitedJars.add(jarPath); 178 179 element = bootclasspath.createPathElement(); 180 element.setPath(library.getJarPath()); 181 } 182 } 183 } 184 185 // finally sets the path in the project with a reference 186 antProject.addReference(REF_CLASSPATH, bootclasspath); 187 188 // find the file to import, and import it. 189 String templateFolder = androidTarget.getPath(IAndroidTarget.TEMPLATES); 190 191 // Now the import section. This is only executed if the task actually has to import a file. 192 if (mDoImport) { 193 // make sure the file exists. 194 File templates = new File(templateFolder); 195 if (templates.isDirectory() == false) { 196 throw new BuildException(String.format("Template directory '%s' is missing.", 197 templateFolder)); 198 } 199 200 // now check the rules file exists. 201 File rules = new File(templateFolder, ANDROID_RULES); 202 if (rules.isFile() == false) { 203 throw new BuildException(String.format("Build rules file '%s' is missing.", 204 templateFolder)); 205 } 206 207 // set the file location to import 208 setFile(rules.getAbsolutePath()); 209 210 // and import 211 super.execute(); 212 } 213 } 214 215 /** 216 * Sets the value of the "import" attribute. 217 * @param value the value. 218 */ setImport(boolean value)219 public void setImport(boolean value) { 220 mDoImport = value; 221 } 222 checkManifest(Project antProject, String codename)223 private void checkManifest(Project antProject, String codename) { 224 try { 225 File manifest = new File(antProject.getBaseDir(), "AndroidManifest.xml"); 226 227 XPath xPath = AndroidXPathFactory.newXPath(); 228 229 String value = xPath.evaluate("/" + ManifestConstants.NODE_MANIFEST +"/" + 230 ManifestConstants.NODE_USES_SDK + "/@" + 231 AndroidXPathFactory.DEFAULT_NS_PREFIX + ":" + 232 ManifestConstants.ATTRIBUTE_MIN_SDK_VERSION, 233 new InputSource(new FileInputStream(manifest))); 234 235 if (codename.equals(value) == false) { 236 throw new BuildException(String.format("For '%1$s' SDK Preview, application manifest must declare minSdkVersion to '%1$s'", 237 codename)); 238 } 239 } catch (XPathExpressionException e) { 240 throw new BuildException(e); 241 } catch (FileNotFoundException e) { 242 throw new BuildException(e); 243 } 244 } 245 } 246