Home | History | Annotate | Download | only in ant
      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