• 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.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 
setBootClassPathOut(String bootClassPathOut)68     public void setBootClassPathOut(String bootClassPathOut) {
69         mBootClassPathOut = bootClassPathOut;
70     }
71 
setAndroidJarFileOut(String androidJarFileOut)72     public void setAndroidJarFileOut(String androidJarFileOut) {
73         mAndroidJarFileOut = androidJarFileOut;
74     }
75 
setAndroidAidlFileOut(String androidAidlFileOut)76     public void setAndroidAidlFileOut(String androidAidlFileOut) {
77         mAndroidAidlFileOut = androidAidlFileOut;
78     }
79 
setTargetApiOut(String targetApiOut)80     public void setTargetApiOut(String targetApiOut) {
81         mTargetApiOut = targetApiOut;
82     }
83 
setMinSdkVersionOut(String minSdkVersionOut)84     public void setMinSdkVersionOut(String minSdkVersionOut) {
85         mMinSdkVersionOut = minSdkVersionOut;
86     }
87 
88     @Override
execute()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      */
checkManifest(Project antProject, AndroidVersion androidVersion)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