• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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