• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
execute()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      */
getAntRulesFromTools(int rulesRev)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      */
setImport(boolean value)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      */
checkManifest(Project antProject, AndroidVersion androidVersion)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 
processReferencedLibraries(Project antProject, IAndroidTarget androidTarget)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      */
getProjectLibraries(final Project antProject)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      */
resolveFullLibraryDependencies(ArrayList<File> inLibraries, ArrayList<File> outLibraries)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 {
getProperty(String name)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      */
getDirectDependencies(File baseFolder, IPropertySource source)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