Home | History | Annotate | Download | only in ant
      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.AndroidVersion;
     20 import com.android.sdklib.IAndroidTarget;
     21 import com.android.sdklib.ISdkLog;
     22 import com.android.sdklib.SdkConstants;
     23 import com.android.sdklib.SdkManager;
     24 import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
     25 import com.android.sdklib.internal.project.ProjectProperties;
     26 import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
     27 import com.android.sdklib.io.FileWrapper;
     28 import com.android.sdklib.io.FolderWrapper;
     29 import com.android.sdklib.xml.AndroidManifest;
     30 import com.android.sdklib.xml.AndroidXPathFactory;
     31 
     32 import org.apache.tools.ant.BuildException;
     33 import org.apache.tools.ant.Project;
     34 import org.apache.tools.ant.taskdefs.ImportTask;
     35 import org.apache.tools.ant.types.Path;
     36 import org.apache.tools.ant.types.Path.PathElement;
     37 import org.xml.sax.InputSource;
     38 
     39 import java.io.File;
     40 import java.io.FileInputStream;
     41 import java.io.FileNotFoundException;
     42 import java.io.FilenameFilter;
     43 import java.io.IOException;
     44 import java.util.ArrayList;
     45 import java.util.HashSet;
     46 
     47 import javax.xml.xpath.XPath;
     48 import javax.xml.xpath.XPathExpressionException;
     49 
     50 /**
     51  * Setup/Import Ant task. This task accomplishes:
     52  * <ul>
     53  * <li>Gets the project target hash string from {@link ProjectProperties#PROPERTY_TARGET},
     54  * and resolves it to get the project's {@link IAndroidTarget}.</li>
     55  * <li>Sets up properties so that aapt can find the android.jar in the resolved target.</li>
     56  * <li>Sets up the boot classpath ref so that the <code>javac</code> task knows where to find
     57  * the libraries. This includes the default android.jar from the resolved target but also optional
     58  * libraries provided by the target (if any, when the target is an add-on).</li>
     59  * <li>Imports the build rules located in the resolved target so that the build actually does
     60  * something. This can be disabled with the attribute <var>import</var> set to <code>false</code>
     61  * </li></ul>
     62  *
     63  * This is used in build.xml/template.
     64  *
     65  */
     66 public final class SetupTask extends ImportTask {
     67     /** current max version of the Ant rules that is supported */
     68     private final static int ANT_RULES_MAX_VERSION = 3;
     69 
     70     // legacy main rules file.
     71     private final static String RULES_LEGACY_MAIN = "android_rules.xml";
     72     // legacy test rules file - depends on android_rules.xml
     73     private final static String RULES_LEGACY_TEST = "android_test_rules.xml";
     74 
     75     // main rules file
     76     private final static String RULES_MAIN = "ant_rules_r%1$d.xml";
     77     // test rules file - depends on android_rules.xml
     78     private final static String RULES_TEST = "ant_test_rules_r%1$d.xml";
     79     // library rules file.
     80     private final static String RULES_LIBRARY = "ant_lib_rules_r%1$d.xml";
     81 
     82     // ant property with the path to the android.jar
     83     private final static String PROPERTY_ANDROID_JAR = "android.jar";
     84 
     85     // ant property with the path to the framework.jar
     86     private final static String PROPERTY_ANDROID_AIDL = "android.aidl";
     87 
     88     // ant property with the path to the aapt tool
     89     private final static String PROPERTY_AAPT = "aapt";
     90     // ant property with the path to the aidl tool
     91     private final static String PROPERTY_AIDL = "aidl";
     92     // ant property with the path to the dx tool
     93     private final static String PROPERTY_DX = "dx";
     94     // ref id to the <path> object containing all the boot classpaths.
     95     private final static String REF_CLASSPATH = "android.target.classpath";
     96 
     97     /**
     98      * Compatibility range for the Ant rules.
     99      * The goal is to specify range of the rules that are compatible between them. For instance if
    100      * a range is 10-15 and a platform indicate that it supports rev 12, but the tools have rules
    101      * revision 15, then the rev 15 will be used.
    102      * Compatibility is broken when a new rev of the rules relies on a new option in the external
    103      * tools contained in the platform.
    104      *
    105      * For instance if rules 10 uses a newly introduced aapt option, then it would be considered
    106      * incompatible with 9, and therefore would be the start of a new compatibility range.
    107      * A platform declaring it supports 9 would not be made to use 10, as its aapt version wouldn't
    108      * support it.
    109      */
    110     private final static int ANT_COMPATIBILITY_RANGES[][] = new int[][] {
    111         new int[] { 1, 1 },
    112         new int[] { 2, ANT_RULES_MAX_VERSION },
    113     };
    114 
    115     private boolean mDoImport = true;
    116 
    117     @Override
    118     public void execute() throws BuildException {
    119         Project antProject = getProject();
    120 
    121         // get the SDK location
    122         File sdk = TaskHelper.getSdkLocation(antProject);
    123         String sdkLocation = sdk.getPath();
    124 
    125         // display SDK Tools revision
    126         int toolsRevison = TaskHelper.getToolsRevision(sdk);
    127         if (toolsRevison != -1) {
    128             System.out.println("Android SDK Tools Revision " + toolsRevison);
    129         }
    130 
    131         // get the target property value
    132         String targetHashString = antProject.getProperty(ProjectProperties.PROPERTY_TARGET);
    133 
    134         boolean isTestProject = false;
    135 
    136         if (antProject.getProperty("tested.project.dir") != null) {
    137             isTestProject = true;
    138         }
    139 
    140         if (targetHashString == null) {
    141             throw new BuildException("Android Target is not set.");
    142         }
    143 
    144         // load up the sdk targets.
    145         final ArrayList<String> messages = new ArrayList<String>();
    146         SdkManager manager = SdkManager.createManager(sdkLocation, new ISdkLog() {
    147             public void error(Throwable t, String errorFormat, Object... args) {
    148                 if (errorFormat != null) {
    149                     messages.add(String.format("Error: " + errorFormat, args));
    150                 }
    151                 if (t != null) {
    152                     messages.add("Error: " + t.getMessage());
    153                 }
    154             }
    155 
    156             public void printf(String msgFormat, Object... args) {
    157                 messages.add(String.format(msgFormat, args));
    158             }
    159 
    160             public void warning(String warningFormat, Object... args) {
    161                 messages.add(String.format("Warning: " + warningFormat, args));
    162             }
    163         });
    164 
    165         if (manager == null) {
    166             // since we failed to parse the SDK, lets display the parsing output.
    167             for (String msg : messages) {
    168                 System.out.println(msg);
    169             }
    170             throw new BuildException("Failed to parse SDK content.");
    171         }
    172 
    173         // resolve it
    174         IAndroidTarget androidTarget = manager.getTargetFromHashString(targetHashString);
    175 
    176         if (androidTarget == null) {
    177             throw new BuildException(String.format(
    178                     "Unable to resolve target '%s'", targetHashString));
    179         }
    180 
    181         // display the project info
    182         System.out.println("Project Target: " + androidTarget.getName());
    183         if (androidTarget.isPlatform() == false) {
    184             System.out.println("Vendor: " + androidTarget.getVendor());
    185             System.out.println("Platform Version: " + androidTarget.getVersionName());
    186         }
    187         System.out.println("API level: " + androidTarget.getVersion().getApiString());
    188 
    189         // check that this version of the custom Ant task can build this target
    190         int antBuildVersion = androidTarget.getProperty(SdkConstants.PROP_SDK_ANT_BUILD_REVISION,
    191                 1);
    192         if (antBuildVersion > ANT_RULES_MAX_VERSION) {
    193             antBuildVersion = ANT_RULES_MAX_VERSION;
    194             System.out.println("\n\n\n"
    195                     + "***********************************************************\n"
    196                     + "WARNING: This platform requires Ant build rules not supported by your SDK Tools.\n"
    197                     + "WARNING: Attempting to use older build rules instead, but result may not be correct.\n"
    198                     + "WARNING: Please update to the newest revisions of the SDK Tools.\n"
    199                     + "***********************************************************\n\n\n");
    200         }
    201 
    202         if (antBuildVersion < 2) {
    203             // these older rules are obselete, and not versioned, and therefore it's hard
    204             // to maintain compatibility.
    205 
    206             // if the platform itself is obsolete, display a different warning
    207             if (androidTarget.getVersion().getApiLevel() < 3 ||
    208                     androidTarget.getVersion().getApiLevel() == 5 ||
    209                     androidTarget.getVersion().getApiLevel() == 6) {
    210                 System.out.println("\n\n\n"
    211                         + "***********************************************************\n"
    212                         + "WARNING: This platform is obsolete and its Ant rules may not work properly.\n"
    213                         + "WARNING: It is recommended to develop against a newer version of Android.\n"
    214                         + "WARNING: For more information about active versions of Android see:\n"
    215                         + "WARNING: http://developer.android.com/resources/dashboard/platform-versions.html\n"
    216                         + "***********************************************************\n\n\n");
    217             } else {
    218                 IAndroidTarget baseTarget =
    219                     androidTarget.getParent() != null ? androidTarget.getParent() : androidTarget;
    220                 System.out.println(String.format("\n\n\n"
    221                         + "***********************************************************\n"
    222                         + "WARNING: Revision %1$d of %2$s uses obsolete Ant rules which may not work properly.\n"
    223                         + "WARNING: It is recommended that you download a newer revision if available.\n"
    224                         + "WARNING: For more information about updating your SDK, see:\n"
    225                         + "WARNING: http://developer.android.com/sdk/adding-components.html\n"
    226                         + "***********************************************************\n\n\n",
    227                         baseTarget.getRevision(), baseTarget.getFullName()));
    228             }
    229         }
    230 
    231         // set a property that contains the rules revision. This can be used by other custom
    232         // tasks later.
    233         antProject.setProperty(TaskHelper.PROP_RULES_REV, Integer.toString(antBuildVersion));
    234 
    235         // check if the project is a library
    236         boolean isLibrary = false;
    237 
    238         String libraryProp = antProject.getProperty(ProjectProperties.PROPERTY_LIBRARY);
    239         if (libraryProp != null) {
    240             isLibrary = Boolean.valueOf(libraryProp).booleanValue();
    241         }
    242 
    243         if (isLibrary) {
    244             System.out.println("Project Type: Android Library");
    245         }
    246 
    247         // do a quick check to make sure the target supports library.
    248         if (isLibrary &&
    249                 androidTarget.getProperty(SdkConstants.PROP_SDK_SUPPORT_LIBRARY, false) == false) {
    250             throw new BuildException(String.format(
    251                     "Project target '%1$s' does not support building libraries.",
    252                     androidTarget.getFullName()));
    253         }
    254 
    255         // look for referenced libraries.
    256         processReferencedLibraries(antProject, androidTarget);
    257 
    258         // always check the manifest minSdkVersion.
    259         checkManifest(antProject, androidTarget.getVersion());
    260 
    261         // sets up the properties to find android.jar/framework.aidl/target tools
    262         String androidJar = androidTarget.getPath(IAndroidTarget.ANDROID_JAR);
    263         antProject.setProperty(PROPERTY_ANDROID_JAR, androidJar);
    264 
    265         String androidAidl = androidTarget.getPath(IAndroidTarget.ANDROID_AIDL);
    266         antProject.setProperty(PROPERTY_ANDROID_AIDL, androidAidl);
    267 
    268         antProject.setProperty(PROPERTY_AAPT, androidTarget.getPath(IAndroidTarget.AAPT));
    269         antProject.setProperty(PROPERTY_AIDL, androidTarget.getPath(IAndroidTarget.AIDL));
    270         antProject.setProperty(PROPERTY_DX, androidTarget.getPath(IAndroidTarget.DX));
    271 
    272         // sets up the boot classpath
    273 
    274         // create the Path object
    275         Path bootclasspath = new Path(antProject);
    276 
    277         // create a PathElement for the framework jar
    278         PathElement element = bootclasspath.createPathElement();
    279         element.setPath(androidJar);
    280 
    281         // create PathElement for each optional library.
    282         IOptionalLibrary[] libraries = androidTarget.getOptionalLibraries();
    283         if (libraries != null) {
    284             HashSet<String> visitedJars = new HashSet<String>();
    285             for (IOptionalLibrary library : libraries) {
    286                 String jarPath = library.getJarPath();
    287                 if (visitedJars.contains(jarPath) == false) {
    288                     visitedJars.add(jarPath);
    289 
    290                     element = bootclasspath.createPathElement();
    291                     element.setPath(library.getJarPath());
    292                 }
    293             }
    294         }
    295 
    296         // finally sets the path in the project with a reference
    297         antProject.addReference(REF_CLASSPATH, bootclasspath);
    298 
    299         // Now the import section. This is only executed if the task actually has to import a file.
    300         if (mDoImport) {
    301             // check if there's a more recent version of the rules in the tools folder.
    302             int toolsRulesRev = getAntRulesFromTools(antBuildVersion);
    303 
    304             File rulesFolder;
    305             if (toolsRulesRev == -1) {
    306                 // no more recent Ant rules from the tools, folder. Find them inside the platform.
    307                 // find the folder containing the file to import
    308                 int folderID = antBuildVersion == 1 ? IAndroidTarget.TEMPLATES : IAndroidTarget.ANT;
    309                 String rulesOSPath = androidTarget.getPath(folderID);
    310                 rulesFolder = new File(rulesOSPath);
    311             } else {
    312                 // in this case we import the rules from the ant folder in the tools.
    313                 rulesFolder = new File(new File(sdkLocation, SdkConstants.FD_TOOLS),
    314                         SdkConstants.FD_ANT);
    315                 // the new rev is:
    316                 antBuildVersion = toolsRulesRev;
    317             }
    318 
    319             // make sure the file exists.
    320             if (rulesFolder.isDirectory() == false) {
    321                 throw new BuildException(String.format("Rules directory '%s' is missing.",
    322                         rulesFolder.getAbsolutePath()));
    323             }
    324 
    325             String importedRulesFileName;
    326             if (antBuildVersion == 1) {
    327                 // legacy mode
    328                 importedRulesFileName = isTestProject ? RULES_LEGACY_TEST : RULES_LEGACY_MAIN;
    329             } else {
    330                 importedRulesFileName = String.format(
    331                         isLibrary ? RULES_LIBRARY : isTestProject ? RULES_TEST : RULES_MAIN,
    332                         antBuildVersion);;
    333             }
    334 
    335             // now check the rules file exists.
    336             File rules = new File(rulesFolder, importedRulesFileName);
    337 
    338             if (rules.isFile() == false) {
    339                 throw new BuildException(String.format("Build rules file '%s' is missing.",
    340                         rules));
    341             }
    342 
    343             // display the file being imported.
    344             // figure out the path relative to the SDK
    345             String rulesLocation = rules.getAbsolutePath();
    346             if (rulesLocation.startsWith(sdkLocation)) {
    347                 rulesLocation = rulesLocation.substring(sdkLocation.length());
    348                 if (rulesLocation.startsWith(File.separator)) {
    349                     rulesLocation = rulesLocation.substring(1);
    350                 }
    351             }
    352             System.out.println("\nImporting rules file: " + rulesLocation);
    353 
    354             // set the file location to import
    355             setFile(rules.getAbsolutePath());
    356 
    357             // and import
    358             super.execute();
    359         }
    360     }
    361 
    362     /**
    363      * Returns the revision number of a newer but still compatible Ant rules available in the
    364      * tools folder of the SDK, or -1 if none is found.
    365      * @param rulesRev the revision of the rules file on which compatibility is based.
    366      */
    367     private int getAntRulesFromTools(int rulesRev) {
    368         for (int[] range : ANT_COMPATIBILITY_RANGES) {
    369             if (range[0] <= rulesRev && rulesRev <= range[1]) {
    370                 return range[1];
    371             }
    372         }
    373 
    374         return -1;
    375     }
    376 
    377     /**
    378      * Sets the value of the "import" attribute.
    379      * @param value the value.
    380      */
    381     public void setImport(boolean value) {
    382         mDoImport = value;
    383     }
    384 
    385     /**
    386      * Checks the manifest <code>minSdkVersion</code> attribute.
    387      * @param antProject the ant project
    388      * @param androidVersion the version of the platform the project is compiling against.
    389      */
    390     private void checkManifest(Project antProject, AndroidVersion androidVersion) {
    391         try {
    392             File manifest = new File(antProject.getBaseDir(), SdkConstants.FN_ANDROID_MANIFEST_XML);
    393 
    394             XPath xPath = AndroidXPathFactory.newXPath();
    395 
    396             // check the package name.
    397             String value = xPath.evaluate(
    398                     "/"  + AndroidManifest.NODE_MANIFEST +
    399                     "/@" + AndroidManifest.ATTRIBUTE_PACKAGE,
    400                     new InputSource(new FileInputStream(manifest)));
    401             if (value != null) { // aapt will complain if it's missing.
    402                 // only need to check that the package has 2 segments
    403                 if (value.indexOf('.') == -1) {
    404                     throw new BuildException(String.format(
    405                             "Application package '%1$s' must have a minimum of 2 segments.",
    406                             value));
    407                 }
    408             }
    409 
    410             // check the minSdkVersion value
    411             value = xPath.evaluate(
    412                     "/"  + AndroidManifest.NODE_MANIFEST +
    413                     "/"  + AndroidManifest.NODE_USES_SDK +
    414                     "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX + ":" +
    415                             AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
    416                     new InputSource(new FileInputStream(manifest)));
    417 
    418             if (androidVersion.isPreview()) {
    419                 // in preview mode, the content of the minSdkVersion must match exactly the
    420                 // platform codename.
    421                 String codeName = androidVersion.getCodename();
    422                 if (codeName.equals(value) == false) {
    423                     throw new BuildException(String.format(
    424                             "For '%1$s' SDK Preview, attribute minSdkVersion in AndroidManifest.xml must be '%1$s'",
    425                             codeName));
    426                 }
    427             } else if (value.length() > 0) {
    428                 // for normal platform, we'll only display warnings if the value is lower or higher
    429                 // than the target api level.
    430                 // First convert to an int.
    431                 int minSdkValue = -1;
    432                 try {
    433                     minSdkValue = Integer.parseInt(value);
    434                 } catch (NumberFormatException e) {
    435                     // looks like it's not a number: error!
    436                     throw new BuildException(String.format(
    437                             "Attribute %1$s in AndroidManifest.xml must be an Integer!",
    438                             AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION));
    439                 }
    440 
    441                 int projectApiLevel = androidVersion.getApiLevel();
    442                 if (minSdkValue < projectApiLevel) {
    443                     System.out.println(String.format(
    444                             "WARNING: Attribute %1$s in AndroidManifest.xml (%2$d) is lower than the project target API level (%3$d)",
    445                             AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
    446                             minSdkValue, projectApiLevel));
    447                 } else if (minSdkValue > androidVersion.getApiLevel()) {
    448                     System.out.println(String.format(
    449                             "WARNING: Attribute %1$s in AndroidManifest.xml (%2$d) is higher than the project target API level (%3$d)",
    450                             AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
    451                             minSdkValue, projectApiLevel));
    452                 }
    453             } else {
    454                 // no minSdkVersion? display a warning
    455                 System.out.println(
    456                         "WARNING: No minSdkVersion value set. Application will install on all Android versions.");
    457             }
    458 
    459         } catch (XPathExpressionException e) {
    460             throw new BuildException(e);
    461         } catch (FileNotFoundException e) {
    462             throw new BuildException(e);
    463         }
    464     }
    465 
    466     private void processReferencedLibraries(Project antProject, IAndroidTarget androidTarget) {
    467         // prepare several paths for future tasks
    468         Path sourcePath = new Path(antProject);
    469         Path resPath = new Path(antProject);
    470         Path libsPath = new Path(antProject);
    471         Path jarsPath = new Path(antProject);
    472         StringBuilder sb = new StringBuilder();
    473 
    474         FilenameFilter filter = new FilenameFilter() {
    475             public boolean accept(File dir, String name) {
    476                 return name.toLowerCase().endsWith(".jar");
    477             }
    478         };
    479 
    480         System.out.println("\n------------------\nResolving library dependencies:");
    481 
    482         ArrayList<File> libraries = getProjectLibraries(antProject);
    483 
    484         final int libCount = libraries.size();
    485         if (libCount > 0 && androidTarget.getProperty(SdkConstants.PROP_SDK_SUPPORT_LIBRARY,
    486                 false) == false) {
    487             throw new BuildException(String.format(
    488                     "The build system for this project target (%1$s) does not support libraries",
    489                     androidTarget.getFullName()));
    490         }
    491 
    492         System.out.println("------------------\nOrdered libraries:");
    493 
    494         for (File library : libraries) {
    495             System.out.println(library.getAbsolutePath());
    496 
    497             // get the source path. default is src but can be overriden by the property
    498             // "source.dir" in build.properties.
    499             PathElement element = sourcePath.createPathElement();
    500             ProjectProperties prop = ProjectProperties.load(new FolderWrapper(library),
    501                     PropertyType.BUILD);
    502 
    503             String sourceDir = SdkConstants.FD_SOURCES;
    504             if (prop != null) {
    505                 String value = prop.getProperty(ProjectProperties.PROPERTY_BUILD_SOURCE_DIR);
    506                 if (value != null) {
    507                     sourceDir = value;
    508                 }
    509             }
    510 
    511             String path = library.getAbsolutePath();
    512 
    513             element.setPath(path + "/" + sourceDir);
    514 
    515             // get the res path. Always $PROJECT/res
    516             element = resPath.createPathElement();
    517             element.setPath(path + "/" + SdkConstants.FD_RESOURCES);
    518 
    519             // get the libs path. Always $PROJECT/libs
    520             element = libsPath.createPathElement();
    521             element.setPath(path + "/" + SdkConstants.FD_NATIVE_LIBS);
    522 
    523             // get the jars from it too
    524             File libsFolder = new File(library, SdkConstants.FD_NATIVE_LIBS);
    525             File[] jarFiles = libsFolder.listFiles(filter);
    526             if (jarFiles != null) {
    527                 for (File jarFile : jarFiles) {
    528                     element = jarsPath.createPathElement();
    529                     element.setPath(jarFile.getAbsolutePath());
    530                 }
    531             }
    532 
    533             // get the package from the manifest.
    534             FileWrapper manifest = new FileWrapper(library, SdkConstants.FN_ANDROID_MANIFEST_XML);
    535             try {
    536                 String value = AndroidManifest.getPackage(manifest);
    537                 if (value != null) { // aapt will complain if it's missing.
    538                     sb.append(';');
    539                     sb.append(value);
    540                 }
    541             } catch (Exception e) {
    542                 throw new BuildException(e);
    543             }
    544         }
    545 
    546         System.out.println("------------------\n");
    547 
    548         // even with no libraries, always setup these so that various tasks in Ant don't complain
    549         // (the task themselves can handle a ref to an empty Path)
    550         antProject.addReference("android.libraries.src", sourcePath);
    551         antProject.addReference("android.libraries.jars", jarsPath);
    552         antProject.addReference("android.libraries.libs", libsPath);
    553 
    554         // the rest is done only if there's a library.
    555         if (sourcePath.list().length > 0) {
    556             antProject.addReference("android.libraries.res", resPath);
    557             antProject.setProperty("android.libraries.package", sb.toString());
    558         }
    559     }
    560 
    561     /**
    562      * Returns all the library dependencies of a given Ant project.
    563      * @param antProject the Ant project
    564      * @return a list of properties, sorted from highest priority to lowest.
    565      */
    566     private ArrayList<File> getProjectLibraries(final Project antProject) {
    567         ArrayList<File> libraries = new ArrayList<File>();
    568         File baseDir = antProject.getBaseDir();
    569 
    570         // get the top level list of library dependencies.
    571         ArrayList<File> topLevelLibraries = getDirectDependencies(baseDir, new IPropertySource() {
    572             public String getProperty(String name) {
    573                 return antProject.getProperty(name);
    574             }
    575         });
    576 
    577         // process the libraries in case they depend on other libraries.
    578         resolveFullLibraryDependencies(topLevelLibraries, libraries);
    579 
    580         return libraries;
    581     }
    582 
    583     /**
    584      * Resolves a given list of libraries, finds out if they depend on other libraries, and
    585      * returns a full list of all the direct and indirect dependencies in the proper order (first
    586      * is higher priority when calling aapt).
    587      * @param inLibraries the libraries to resolve
    588      * @param outLibraries where to store all the libraries.
    589      */
    590     private void resolveFullLibraryDependencies(ArrayList<File> inLibraries,
    591             ArrayList<File> outLibraries) {
    592         // loop in the inverse order to resolve dependencies on the libraries, so that if a library
    593         // is required by two higher level libraries it can be inserted in the correct place
    594         for (int i = inLibraries.size() - 1  ; i >= 0 ; i--) {
    595             File library = inLibraries.get(i);
    596 
    597             // get the default.property file for it
    598             final ProjectProperties defaultProp = ProjectProperties.load(
    599                     new FolderWrapper(library), PropertyType.DEFAULT);
    600 
    601             // get its libraries
    602             ArrayList<File> dependencies = getDirectDependencies(library, new IPropertySource() {
    603                 public String getProperty(String name) {
    604                     return defaultProp.getProperty(name);
    605                 }
    606             });
    607 
    608             // resolve the dependencies for those libraries
    609             resolveFullLibraryDependencies(dependencies, outLibraries);
    610 
    611             // and add the current one (if needed) in front (higher priority)
    612             if (outLibraries.contains(library) == false) {
    613                 outLibraries.add(0, library);
    614             }
    615         }
    616     }
    617 
    618     public interface IPropertySource {
    619         String getProperty(String name);
    620     }
    621 
    622     /**
    623      * Returns the top level library dependencies of a given <var>source</var> representing a
    624      * project properties.
    625      * @param baseFolder the base folder of the project (to resolve relative paths)
    626      * @param source a source of project properties.
    627      * @return
    628      */
    629     private ArrayList<File> getDirectDependencies(File baseFolder, IPropertySource source) {
    630         ArrayList<File> libraries = new ArrayList<File>();
    631 
    632         // first build the list. they are ordered highest priority first.
    633         int index = 1;
    634         while (true) {
    635             String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++);
    636             String rootPath = source.getProperty(propName);
    637 
    638             if (rootPath == null) {
    639                 break;
    640             }
    641 
    642             try {
    643                 File library = new File(baseFolder, rootPath).getCanonicalFile();
    644 
    645                 // check for validity
    646                 File defaultProp = new File(library, PropertyType.DEFAULT.getFilename());
    647                 if (defaultProp.isFile() == false) {
    648                     // error!
    649                     throw new BuildException(String.format(
    650                             "%1$s resolve to a path with no %2$s file for project %3$s", rootPath,
    651                             PropertyType.DEFAULT.getFilename(), baseFolder.getAbsolutePath()));
    652                 }
    653 
    654                 if (libraries.contains(library) == false) {
    655                     System.out.println(String.format("%1$s: %2$s => %3$s",
    656                             baseFolder.getAbsolutePath(), rootPath, library.getAbsolutePath()));
    657 
    658                     libraries.add(library);
    659                 }
    660             } catch (IOException e) {
    661                 throw new BuildException("Failed to resolve library path: " + rootPath, e);
    662             }
    663         }
    664 
    665         return libraries;
    666     }
    667 }
    668