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.io.FileWrapper;
     20 import com.android.io.FolderWrapper;
     21 import com.android.sdklib.AndroidVersion;
     22 import com.android.sdklib.IAndroidTarget;
     23 import com.android.sdklib.ISdkLog;
     24 import com.android.sdklib.SdkConstants;
     25 import com.android.sdklib.SdkManager;
     26 import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
     27 import com.android.sdklib.internal.project.ProjectProperties;
     28 import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
     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.Task;
     35 import org.apache.tools.ant.types.Path;
     36 import org.apache.tools.ant.types.Path.PathElement;
     37 import org.apache.tools.ant.util.DeweyDecimal;
     38 import org.xml.sax.InputSource;
     39 
     40 import java.io.File;
     41 import java.io.FileInputStream;
     42 import java.io.FileNotFoundException;
     43 import java.io.FilenameFilter;
     44 import java.io.IOException;
     45 import java.util.ArrayList;
     46 import java.util.HashSet;
     47 import java.util.List;
     48 
     49 import javax.xml.xpath.XPath;
     50 import javax.xml.xpath.XPathExpressionException;
     51 
     52 /**
     53  * Setup Ant task. This task accomplishes:
     54  * <ul>
     55  * <li>Gets the project target hash string from {@link ProjectProperties#PROPERTY_TARGET},
     56  * and resolves it to get the project's {@link IAndroidTarget}.</li>
     57  *
     58  * <li>Sets up properties so that aapt can find the android.jar and other files/folders in
     59  * the resolved target.</li>
     60  *
     61  * <li>Sets up the boot classpath ref so that the <code>javac</code> task knows where to find
     62  * the libraries. This includes the default android.jar from the resolved target but also optional
     63  * libraries provided by the target (if any, when the target is an add-on).</li>
     64  *
     65  * <li>Resolve library dependencies and setup various Path references for them</li>
     66  * </ul>
     67  *
     68  * This is used in the main rules file only.
     69  *
     70  */
     71 public class NewSetupTask extends Task {
     72     private final static String ANT_MIN_VERSION = "1.8.0";
     73 
     74     private String mProjectTypeOut;
     75     private String mAndroidJarFileOut;
     76     private String mAndroidAidlFileOut;
     77     private String mRenderScriptExeOut;
     78     private String mRenderScriptIncludeDirOut;
     79     private String mBootclasspathrefOut;
     80     private String mProjectLibrariesRootOut;
     81     private String mProjectLibrariesResOut;
     82     private String mProjectLibrariesPackageOut;
     83     private String mProjectLibrariesJarsOut;
     84     private String mProjectLibrariesLibsOut;
     85     private String mTargetApiOut;
     86 
     87     public void setProjectTypeOut(String projectTypeOut) {
     88         mProjectTypeOut = projectTypeOut;
     89     }
     90 
     91     public void setAndroidJarFileOut(String androidJarFileOut) {
     92         mAndroidJarFileOut = androidJarFileOut;
     93     }
     94 
     95     public void setAndroidAidlFileOut(String androidAidlFileOut) {
     96         mAndroidAidlFileOut = androidAidlFileOut;
     97     }
     98 
     99     public void setRenderScriptExeOut(String renderScriptExeOut) {
    100         mRenderScriptExeOut = renderScriptExeOut;
    101     }
    102 
    103     public void setRenderScriptIncludeDirOut(String renderScriptIncludeDirOut) {
    104         mRenderScriptIncludeDirOut = renderScriptIncludeDirOut;
    105     }
    106 
    107     public void setBootclasspathrefOut(String bootclasspathrefOut) {
    108         mBootclasspathrefOut = bootclasspathrefOut;
    109     }
    110 
    111     public void setProjectLibrariesRootOut(String projectLibrariesRootOut) {
    112         mProjectLibrariesRootOut = projectLibrariesRootOut;
    113     }
    114 
    115     public void setProjectLibrariesResOut(String projectLibrariesResOut) {
    116         mProjectLibrariesResOut = projectLibrariesResOut;
    117     }
    118 
    119     public void setProjectLibrariesPackageOut(String projectLibrariesPackageOut) {
    120         mProjectLibrariesPackageOut = projectLibrariesPackageOut;
    121     }
    122 
    123     public void setProjectLibrariesJarsOut(String projectLibrariesJarsOut) {
    124         mProjectLibrariesJarsOut = projectLibrariesJarsOut;
    125     }
    126 
    127     public void setProjectLibrariesLibsOut(String projectLibrariesLibsOut) {
    128         mProjectLibrariesLibsOut = projectLibrariesLibsOut;
    129     }
    130 
    131     public void setTargetApiOut(String targetApiOut) {
    132         mTargetApiOut = targetApiOut;
    133     }
    134 
    135     @Override
    136     public void execute() throws BuildException {
    137         if (mProjectTypeOut == null) {
    138             throw new BuildException("Missing attribute projectTypeOut");
    139         }
    140         if (mAndroidJarFileOut == null) {
    141             throw new BuildException("Missing attribute androidJarFileOut");
    142         }
    143         if (mAndroidAidlFileOut == null) {
    144             throw new BuildException("Missing attribute androidAidlFileOut");
    145         }
    146         if (mRenderScriptExeOut == null) {
    147             throw new BuildException("Missing attribute renderScriptExeOut");
    148         }
    149         if (mRenderScriptIncludeDirOut == null) {
    150             throw new BuildException("Missing attribute renderScriptIncludeDirOut");
    151         }
    152         if (mBootclasspathrefOut == null) {
    153             throw new BuildException("Missing attribute bootclasspathrefOut");
    154         }
    155         if (mProjectLibrariesRootOut == null) {
    156             throw new BuildException("Missing attribute projectLibrariesRootOut");
    157         }
    158         if (mProjectLibrariesResOut == null) {
    159             throw new BuildException("Missing attribute projectLibrariesResOut");
    160         }
    161         if (mProjectLibrariesPackageOut == null) {
    162             throw new BuildException("Missing attribute projectLibrariesPackageOut");
    163         }
    164         if (mProjectLibrariesJarsOut == null) {
    165             throw new BuildException("Missing attribute projectLibrariesJarsOut");
    166         }
    167         if (mProjectLibrariesLibsOut == null) {
    168             throw new BuildException("Missing attribute projectLibrariesLibsOut");
    169         }
    170         if (mTargetApiOut == null) {
    171             throw new BuildException("Missing attribute targetApiOut");
    172         }
    173 
    174 
    175         Project antProject = getProject();
    176 
    177         // check the Ant version
    178         DeweyDecimal version = getVersion(antProject);
    179         DeweyDecimal atLeast = new DeweyDecimal(ANT_MIN_VERSION);
    180         if (atLeast.isGreaterThan(version)) {
    181             throw new BuildException(
    182                     "The Android Ant-based build system requires Ant " +
    183                     ANT_MIN_VERSION +
    184                     " or later. Current version is " +
    185                     version);
    186         }
    187 
    188         // get the SDK location
    189         File sdkDir = TaskHelper.getSdkLocation(antProject);
    190         String sdkOsPath = sdkDir.getPath();
    191 
    192         // Make sure the OS sdk path ends with a directory separator
    193         if (sdkOsPath.length() > 0 && !sdkOsPath.endsWith(File.separator)) {
    194             sdkOsPath += File.separator;
    195         }
    196 
    197         // display SDK Tools revision
    198         int toolsRevison = TaskHelper.getToolsRevision(sdkDir);
    199         if (toolsRevison != -1) {
    200             System.out.println("Android SDK Tools Revision " + toolsRevison);
    201         }
    202 
    203         // detect that the platform tools is there.
    204         File platformTools = new File(sdkDir, SdkConstants.FD_PLATFORM_TOOLS);
    205         if (platformTools.isDirectory() == false) {
    206             throw new BuildException(String.format(
    207                     "SDK Platform Tools component is missing. " +
    208                     "Please install it with the SDK Manager (%1$s%2$c%3$s)",
    209                     SdkConstants.FD_TOOLS,
    210                     File.separatorChar,
    211                     SdkConstants.androidCmdName()));
    212         }
    213 
    214         // get the target property value
    215         String targetHashString = antProject.getProperty(ProjectProperties.PROPERTY_TARGET);
    216 
    217         boolean isTestProject = false;
    218 
    219         if (antProject.getProperty(ProjectProperties.PROPERTY_TESTED_PROJECT) != null) {
    220             isTestProject = true;
    221         }
    222 
    223         if (targetHashString == null) {
    224             throw new BuildException("Android Target is not set.");
    225         }
    226 
    227         // load up the sdk targets.
    228         final ArrayList<String> messages = new ArrayList<String>();
    229         SdkManager manager = SdkManager.createManager(sdkOsPath, new ISdkLog() {
    230             public void error(Throwable t, String errorFormat, Object... args) {
    231                 if (errorFormat != null) {
    232                     messages.add(String.format("Error: " + errorFormat, args));
    233                 }
    234                 if (t != null) {
    235                     messages.add("Error: " + t.getMessage());
    236                 }
    237             }
    238 
    239             public void printf(String msgFormat, Object... args) {
    240                 messages.add(String.format(msgFormat, args));
    241             }
    242 
    243             public void warning(String warningFormat, Object... args) {
    244                 messages.add(String.format("Warning: " + warningFormat, args));
    245             }
    246         });
    247 
    248         if (manager == null) {
    249             // since we failed to parse the SDK, lets display the parsing output.
    250             for (String msg : messages) {
    251                 System.out.println(msg);
    252             }
    253             throw new BuildException("Failed to parse SDK content.");
    254         }
    255 
    256         // resolve it
    257         IAndroidTarget androidTarget = manager.getTargetFromHashString(targetHashString);
    258 
    259         if (androidTarget == null) {
    260             throw new BuildException(String.format(
    261                     "Unable to resolve target '%s'", targetHashString));
    262         }
    263 
    264         // display the project info
    265         System.out.println("Project Target: " + androidTarget.getName());
    266         if (androidTarget.isPlatform() == false) {
    267             System.out.println("Vendor: " + androidTarget.getVendor());
    268             System.out.println("Platform Version: " + androidTarget.getVersionName());
    269         }
    270         System.out.println("API level: " + androidTarget.getVersion().getApiString());
    271 
    272         // check if the project is a library
    273         boolean isLibrary = false;
    274 
    275         String libraryProp = antProject.getProperty(ProjectProperties.PROPERTY_LIBRARY);
    276         if (libraryProp != null) {
    277             isLibrary = Boolean.valueOf(libraryProp).booleanValue();
    278         }
    279 
    280         if (isLibrary) {
    281             System.out.println("Project Type: Android Library");
    282         }
    283 
    284         // look for referenced libraries.
    285         processReferencedLibraries(antProject, androidTarget);
    286 
    287         // always check the manifest minSdkVersion.
    288         checkManifest(antProject, androidTarget.getVersion());
    289 
    290         // sets up the properties to find android.jar/framework.aidl/target tools
    291         String androidJar = androidTarget.getPath(IAndroidTarget.ANDROID_JAR);
    292         antProject.setProperty(mAndroidJarFileOut, androidJar);
    293 
    294         String androidAidl = androidTarget.getPath(IAndroidTarget.ANDROID_AIDL);
    295         antProject.setProperty(mAndroidAidlFileOut, androidAidl);
    296 
    297         Path includePath = new Path(antProject);
    298         PathElement element = includePath.createPathElement();
    299         element.setPath(androidTarget.getPath(IAndroidTarget.ANDROID_RS));
    300         element = includePath.createPathElement();
    301         element.setPath(androidTarget.getPath(IAndroidTarget.ANDROID_RS_CLANG));
    302         antProject.setProperty(mRenderScriptIncludeDirOut, includePath.toString());
    303 
    304         // TODO: figure out the actual compiler to use based on the minSdkVersion
    305         antProject.setProperty(mRenderScriptExeOut,
    306                 sdkOsPath + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER +
    307                 SdkConstants.FN_RENDERSCRIPT);
    308 
    309         // sets up the boot classpath
    310 
    311         // create the Path object
    312         Path bootclasspath = new Path(antProject);
    313 
    314         // create a PathElement for the framework jar
    315         element = bootclasspath.createPathElement();
    316         element.setPath(androidJar);
    317 
    318         // create PathElement for each optional library.
    319         IOptionalLibrary[] libraries = androidTarget.getOptionalLibraries();
    320         if (libraries != null) {
    321             HashSet<String> visitedJars = new HashSet<String>();
    322             for (IOptionalLibrary library : libraries) {
    323                 String jarPath = library.getJarPath();
    324                 if (visitedJars.contains(jarPath) == false) {
    325                     visitedJars.add(jarPath);
    326 
    327                     element = bootclasspath.createPathElement();
    328                     element.setPath(library.getJarPath());
    329                 }
    330             }
    331         }
    332 
    333         // sets the path in the project with a reference
    334         antProject.addReference(mBootclasspathrefOut, bootclasspath);
    335 
    336         // finally set the project type.
    337         if (isLibrary) {
    338             antProject.setProperty(mProjectTypeOut, "library");
    339         } else if (isTestProject) {
    340             antProject.setProperty(mProjectTypeOut, "test");
    341         } else {
    342             antProject.setProperty(mProjectTypeOut, "project");
    343         }
    344     }
    345 
    346     /**
    347      * Checks the manifest <code>minSdkVersion</code> attribute.
    348      * @param antProject the ant project
    349      * @param androidVersion the version of the platform the project is compiling against.
    350      */
    351     private void checkManifest(Project antProject, AndroidVersion androidVersion) {
    352         try {
    353             File manifest = new File(antProject.getBaseDir(), SdkConstants.FN_ANDROID_MANIFEST_XML);
    354 
    355             XPath xPath = AndroidXPathFactory.newXPath();
    356 
    357             // check the package name.
    358             String value = xPath.evaluate(
    359                     "/"  + AndroidManifest.NODE_MANIFEST +
    360                     "/@" + AndroidManifest.ATTRIBUTE_PACKAGE,
    361                     new InputSource(new FileInputStream(manifest)));
    362             if (value != null) { // aapt will complain if it's missing.
    363                 // only need to check that the package has 2 segments
    364                 if (value.indexOf('.') == -1) {
    365                     throw new BuildException(String.format(
    366                             "Application package '%1$s' must have a minimum of 2 segments.",
    367                             value));
    368                 }
    369             }
    370 
    371             // check the minSdkVersion value
    372             value = xPath.evaluate(
    373                     "/"  + AndroidManifest.NODE_MANIFEST +
    374                     "/"  + AndroidManifest.NODE_USES_SDK +
    375                     "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX + ":" +
    376                             AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
    377                     new InputSource(new FileInputStream(manifest)));
    378 
    379             if (androidVersion.isPreview()) {
    380                 // in preview mode, the content of the minSdkVersion must match exactly the
    381                 // platform codename.
    382                 String codeName = androidVersion.getCodename();
    383                 if (codeName.equals(value) == false) {
    384                     throw new BuildException(String.format(
    385                             "For '%1$s' SDK Preview, attribute minSdkVersion in AndroidManifest.xml must be '%1$s' (current: %2$s)",
    386                             codeName, value));
    387                 }
    388 
    389                 // set the API level to the previous API level (which is actually the value in
    390                 // androidVersion.)
    391                 antProject.setProperty(mTargetApiOut,
    392                         Integer.toString(androidVersion.getApiLevel()));
    393 
    394             } else if (value.length() > 0) {
    395                 // for normal platform, we'll only display warnings if the value is lower or higher
    396                 // than the target api level.
    397                 // First convert to an int.
    398                 int minSdkValue = -1;
    399                 try {
    400                     minSdkValue = Integer.parseInt(value);
    401                 } catch (NumberFormatException e) {
    402                     // looks like it's not a number: error!
    403                     throw new BuildException(String.format(
    404                             "Attribute %1$s in AndroidManifest.xml must be an Integer!",
    405                             AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION));
    406                 }
    407 
    408                 // set the target api to the value
    409                 antProject.setProperty(mTargetApiOut, value);
    410 
    411                 int projectApiLevel = androidVersion.getApiLevel();
    412                 if (minSdkValue < projectApiLevel) {
    413                     System.out.println(String.format(
    414                             "WARNING: Attribute %1$s in AndroidManifest.xml (%2$d) is lower than the project target API level (%3$d)",
    415                             AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
    416                             minSdkValue, projectApiLevel));
    417                 } else if (minSdkValue > androidVersion.getApiLevel()) {
    418                     System.out.println(String.format(
    419                             "WARNING: Attribute %1$s in AndroidManifest.xml (%2$d) is higher than the project target API level (%3$d)",
    420                             AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
    421                             minSdkValue, projectApiLevel));
    422                 }
    423             } else {
    424                 // no minSdkVersion? display a warning
    425                 System.out.println(
    426                         "WARNING: No minSdkVersion value set. Application will install on all Android versions.");
    427 
    428                 // set the target api to 1
    429                 antProject.setProperty(mTargetApiOut, "1");
    430             }
    431 
    432         } catch (XPathExpressionException e) {
    433             throw new BuildException(e);
    434         } catch (FileNotFoundException e) {
    435             throw new BuildException(e);
    436         }
    437     }
    438 
    439     private void processReferencedLibraries(Project antProject, IAndroidTarget androidTarget) {
    440         // prepare several paths for future tasks
    441         Path rootPath = new Path(antProject);
    442         Path resPath = new Path(antProject);
    443         Path libsPath = new Path(antProject);
    444         Path jarsPath = new Path(antProject);
    445         StringBuilder packageStrBuilder = new StringBuilder();
    446 
    447         FilenameFilter filter = new FilenameFilter() {
    448             public boolean accept(File dir, String name) {
    449                 return name.toLowerCase().endsWith(".jar");
    450             }
    451         };
    452 
    453         System.out.println("\n------------------\nResolving library dependencies:");
    454 
    455         ArrayList<File> libraries = getProjectLibraries(antProject);
    456 
    457         if (libraries.size() > 0) {
    458             System.out.println("------------------\nOrdered libraries:");
    459 
    460             for (File library : libraries) {
    461                 String libRootPath = library.getAbsolutePath();
    462                 System.out.println(libRootPath);
    463 
    464                 // get the root path.
    465                 PathElement element = rootPath.createPathElement();
    466                 element.setPath(libRootPath);
    467 
    468                 // get the res path. Always $PROJECT/res as well as the crunch cache.
    469                 element = resPath.createPathElement();
    470                 element.setPath(libRootPath + "/" + SdkConstants.FD_OUTPUT +
    471                         "/" + SdkConstants.FD_RES);
    472                 element = resPath.createPathElement();
    473                 element.setPath(libRootPath + "/" + SdkConstants.FD_RESOURCES);
    474 
    475                 // get the libs path. Always $PROJECT/libs
    476                 element = libsPath.createPathElement();
    477                 element.setPath(libRootPath + "/" + SdkConstants.FD_NATIVE_LIBS);
    478 
    479                 // get the jars from it too.
    480                 // 1. the library code jar
    481                 element = jarsPath.createPathElement();
    482                 element.setPath(libRootPath + "/" + SdkConstants.FD_OUTPUT +
    483                         "/" + SdkConstants.FN_CLASSES_JAR);
    484 
    485                 // 2. the 3rd party jar files
    486                 File libsFolder = new File(library, SdkConstants.FD_NATIVE_LIBS);
    487                 File[] jarFiles = libsFolder.listFiles(filter);
    488                 if (jarFiles != null) {
    489                     for (File jarFile : jarFiles) {
    490                         element = jarsPath.createPathElement();
    491                         element.setPath(jarFile.getAbsolutePath());
    492                     }
    493                 }
    494 
    495                 // get the package from the manifest.
    496                 FileWrapper manifest = new FileWrapper(library,
    497                         SdkConstants.FN_ANDROID_MANIFEST_XML);
    498 
    499                 try {
    500                     String value = AndroidManifest.getPackage(manifest);
    501                     if (value != null) { // aapt will complain if it's missing.
    502                         packageStrBuilder.append(';');
    503                         packageStrBuilder.append(value);
    504                     }
    505                 } catch (Exception e) {
    506                     throw new BuildException(e);
    507                 }
    508             }
    509         } else {
    510             System.out.println("No library dependencies.\n");
    511         }
    512 
    513         System.out.println("------------------\n");
    514 
    515         // even with no libraries, always setup these so that various tasks in Ant don't complain
    516         // (the task themselves can handle a ref to an empty Path)
    517         antProject.addReference(mProjectLibrariesJarsOut, jarsPath);
    518         antProject.addReference(mProjectLibrariesLibsOut, libsPath);
    519 
    520         // the rest is done only if there's a library.
    521         if (jarsPath.list().length > 0) {
    522             antProject.addReference(mProjectLibrariesRootOut, rootPath);
    523             antProject.addReference(mProjectLibrariesResOut, resPath);
    524             antProject.setProperty(mProjectLibrariesPackageOut, packageStrBuilder.toString());
    525         }
    526     }
    527 
    528     /**
    529      * Returns all the library dependencies of a given Ant project.
    530      * @param antProject the Ant project
    531      * @return a list of properties, sorted from highest priority to lowest.
    532      */
    533     private ArrayList<File> getProjectLibraries(final Project antProject) {
    534         ArrayList<File> libraries = new ArrayList<File>();
    535         File baseDir = antProject.getBaseDir();
    536 
    537         // get the top level list of library dependencies.
    538         List<File> topLevelLibraries = getDirectDependencies(baseDir, new IPropertySource() {
    539             public String getProperty(String name) {
    540                 return antProject.getProperty(name);
    541             }
    542         });
    543 
    544         // process the libraries in case they depend on other libraries.
    545         resolveFullLibraryDependencies(topLevelLibraries, libraries);
    546 
    547         return libraries;
    548     }
    549 
    550     /**
    551      * Resolves a given list of libraries, finds out if they depend on other libraries, and
    552      * returns a full list of all the direct and indirect dependencies in the proper order (first
    553      * is higher priority when calling aapt).
    554      * @param inLibraries the libraries to resolve
    555      * @param outLibraries where to store all the libraries.
    556      */
    557     private void resolveFullLibraryDependencies(List<File> inLibraries, List<File> outLibraries) {
    558         // loop in the inverse order to resolve dependencies on the libraries, so that if a library
    559         // is required by two higher level libraries it can be inserted in the correct place
    560         for (int i = inLibraries.size() - 1  ; i >= 0 ; i--) {
    561             File library = inLibraries.get(i);
    562 
    563             // get the default.property file for it
    564             final ProjectProperties projectProp = ProjectProperties.load(
    565                     new FolderWrapper(library), PropertyType.PROJECT);
    566 
    567             // get its libraries
    568             List<File> dependencies = getDirectDependencies(library, new IPropertySource() {
    569                 public String getProperty(String name) {
    570                     return projectProp.getProperty(name);
    571                 }
    572             });
    573 
    574             // resolve the dependencies for those libraries
    575             resolveFullLibraryDependencies(dependencies, outLibraries);
    576 
    577             // and add the current one (if needed) in front (higher priority)
    578             if (outLibraries.contains(library) == false) {
    579                 outLibraries.add(0, library);
    580             }
    581         }
    582     }
    583 
    584     public interface IPropertySource {
    585         String getProperty(String name);
    586     }
    587 
    588     /**
    589      * Returns the top level library dependencies of a given <var>source</var> representing a
    590      * project properties.
    591      * @param baseFolder the base folder of the project (to resolve relative paths)
    592      * @param source a source of project properties.
    593      */
    594     private List<File> getDirectDependencies(File baseFolder, IPropertySource source) {
    595         ArrayList<File> libraries = new ArrayList<File>();
    596 
    597         // first build the list. they are ordered highest priority first.
    598         int index = 1;
    599         while (true) {
    600             String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++);
    601             String rootPath = source.getProperty(propName);
    602 
    603             if (rootPath == null) {
    604                 break;
    605             }
    606 
    607             try {
    608                 File library = new File(baseFolder, rootPath).getCanonicalFile();
    609 
    610                 // check for validity
    611                 File projectProp = new File(library, PropertyType.PROJECT.getFilename());
    612                 if (projectProp.isFile() == false) {
    613                     // error!
    614                     throw new BuildException(String.format(
    615                             "%1$s resolve to a path with no %2$s file for project %3$s", rootPath,
    616                             PropertyType.PROJECT.getFilename(), baseFolder.getAbsolutePath()));
    617                 }
    618 
    619                 if (libraries.contains(library) == false) {
    620                     System.out.println(String.format("%1$s: %2$s => %3$s",
    621                             baseFolder.getAbsolutePath(), rootPath, library.getAbsolutePath()));
    622 
    623                     libraries.add(library);
    624                 }
    625             } catch (IOException e) {
    626                 throw new BuildException("Failed to resolve library path: " + rootPath, e);
    627             }
    628         }
    629 
    630         return libraries;
    631     }
    632 
    633     /**
    634      * Returns the Ant version as a {@link DeweyDecimal} object.
    635      *
    636      * This is based on the implementation of
    637      * org.apache.tools.ant.taskdefs.condition.AntVersion.getVersion()
    638      *
    639      * @param antProject the current ant project.
    640      * @return the ant version.
    641      */
    642     private DeweyDecimal getVersion(Project antProject) {
    643         char[] versionString = antProject.getProperty("ant.version").toCharArray();
    644         StringBuilder sb = new StringBuilder();
    645         boolean foundFirstDigit = false;
    646         for (int i = 0; i < versionString.length; i++) {
    647             if (Character.isDigit(versionString[i])) {
    648                 sb.append(versionString[i]);
    649                 foundFirstDigit = true;
    650             }
    651             if (versionString[i] == '.' && foundFirstDigit) {
    652                 sb.append(versionString[i]);
    653             }
    654             if (Character.isLetter(versionString[i]) && foundFirstDigit) {
    655                 break;
    656             }
    657         }
    658         return new DeweyDecimal(sb.toString());
    659     }
    660 }
    661