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