1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ant; 18 19 import com.android.io.FolderWrapper; 20 import com.android.sdklib.SdkConstants; 21 import com.android.sdklib.build.JarListSanitizer; 22 import com.android.sdklib.build.JarListSanitizer.DifferentLibException; 23 import com.android.sdklib.build.JarListSanitizer.Sha1Exception; 24 import com.android.sdklib.internal.project.IPropertySource; 25 import com.android.sdklib.internal.project.ProjectProperties; 26 import com.android.sdklib.internal.project.ProjectProperties.PropertyType; 27 28 import org.apache.tools.ant.BuildException; 29 30 import java.io.File; 31 import java.io.FilenameFilter; 32 import java.io.IOException; 33 import java.util.ArrayList; 34 import java.util.List; 35 import java.util.Locale; 36 37 /** 38 * Helper class to manage dependency for projects. 39 * 40 */ 41 public class DependencyHelper { 42 43 private final boolean mVerbose; 44 private final File mProjectFolder; 45 private final IPropertySource mProperties; 46 private final List<File> mLibraries = new ArrayList<File>(); 47 48 /** 49 * A Library Processor. Used in {@link DependencyHelper#processLibraries(LibraryProcessor)} 50 * 51 */ 52 protected interface LibraryProcessor { processLibrary(String libRootPath)53 void processLibrary(String libRootPath); 54 } 55 56 /** 57 * Basic implementation of {@link LibraryProcessor} that builds a list of sanitized list 58 * of 3rd party jar files from all the Library Projects. 59 */ 60 public static class LibraryProcessorFor3rdPartyJars implements LibraryProcessor { 61 62 private final List<File> mJars = new ArrayList<File>(); 63 64 private final FilenameFilter mFilter = new FilenameFilter() { 65 @Override 66 public boolean accept(File dir, String name) { 67 return name.toLowerCase(Locale.US).endsWith(".jar"); 68 } 69 }; 70 getJars()71 public List<File> getJars() { 72 return mJars; 73 } 74 getFilter()75 public FilenameFilter getFilter() { 76 return mFilter; 77 } 78 79 @Override processLibrary(String libRootPath)80 public void processLibrary(String libRootPath) { 81 // get the library output 82 // FIXME: support renamed folder. 83 mJars.add(new File(libRootPath + "/" + SdkConstants.FD_OUTPUT + 84 "/" + SdkConstants.FN_CLASSES_JAR)); 85 86 // Get the 3rd party jar files. 87 // FIXME: support renamed folder. 88 File libsFolder = new File(libRootPath, SdkConstants.FD_NATIVE_LIBS); 89 File[] jarFiles = libsFolder.listFiles(mFilter); 90 if (jarFiles != null) { 91 for (File jarFile : jarFiles) { 92 mJars.add(jarFile); 93 } 94 } 95 } 96 } 97 sanitizePaths(File projectFolder, IPropertySource properties, List<File> paths)98 public static List<File> sanitizePaths(File projectFolder, IPropertySource properties, 99 List<File> paths) { 100 // first get the non-files. 101 List<File> results = new ArrayList<File>(); 102 for (int i = 0 ; i < paths.size() ;) { 103 File f = paths.get(i); 104 // TEMP WORKAROUND: ignore classes.jar as all the output of libraries are 105 // called the same (in Ant) but are not actually the same jar file. 106 // TODO: Be aware of library output vs. regular jar dependency. 107 if (f.isFile() && f.getName().equals(SdkConstants.FN_CLASSES_JAR) == false) { 108 i++; 109 } else { 110 results.add(f); 111 paths.remove(i); 112 } 113 } 114 115 116 File outputFile = new File(projectFolder, getOutDir(properties)); 117 JarListSanitizer sanitizer = new JarListSanitizer(outputFile); 118 119 try { 120 results.addAll(sanitizer.sanitize(paths)); 121 } catch (DifferentLibException e) { 122 String[] details = e.getDetails(); 123 for (String s : details) { 124 System.err.println(s); 125 } 126 throw new BuildException(e.getMessage(), e); 127 } catch (Sha1Exception e) { 128 throw new BuildException( 129 "Failed to compute sha1 for " + e.getJarFile().getAbsolutePath(), e); 130 } 131 132 return results; 133 } 134 135 /** 136 * 137 * @param projectFolder the project root folder. 138 */ DependencyHelper(File projectFolder, boolean verbose)139 public DependencyHelper(File projectFolder, boolean verbose) { 140 mProjectFolder = projectFolder; 141 mVerbose = verbose; 142 143 ProjectProperties properties = ProjectProperties.load(projectFolder.getAbsolutePath(), 144 PropertyType.ANT); 145 146 if (properties == null) { 147 properties = ProjectProperties.load(projectFolder.getAbsolutePath(), 148 PropertyType.PROJECT); 149 } else { 150 properties.makeWorkingCopy().merge(PropertyType.PROJECT); 151 } 152 153 mProperties = properties; 154 155 init(projectFolder); 156 } 157 158 /** 159 * 160 * @param projectFolder the project root folder. 161 * @param source an {@link IPropertySource} that can provide the project properties values. 162 */ DependencyHelper(File projectFolder, IPropertySource properties, boolean verbose)163 public DependencyHelper(File projectFolder, IPropertySource properties, boolean verbose) { 164 mProjectFolder = projectFolder; 165 mProperties = properties; 166 mVerbose = verbose; 167 168 init(projectFolder); 169 } 170 init(File projectFolder)171 private void init(File projectFolder) { 172 // get the top level list of library dependencies. 173 List<File> topLevelLibraries = getDirectDependencies(projectFolder, mProperties); 174 175 // process the libraries in case they depend on other libraries. 176 resolveFullLibraryDependencies(topLevelLibraries, mLibraries); 177 } 178 getLibraries()179 public List<File> getLibraries() { 180 return mLibraries; 181 } 182 getLibraryCount()183 public int getLibraryCount() { 184 return mLibraries.size(); 185 } 186 getProperty(String name)187 public String getProperty(String name) { 188 return mProperties.getProperty(name); 189 } 190 processLibraries(LibraryProcessor processor)191 public void processLibraries(LibraryProcessor processor) { 192 // use that same order to process the libraries. 193 for (File library : mLibraries) { 194 // get the root path. 195 String libRootPath = library.getAbsolutePath(); 196 if (mVerbose) { 197 System.out.println(libRootPath); 198 } 199 200 if (processor != null) { 201 processor.processLibrary(libRootPath); 202 } 203 } 204 } 205 sanitizePaths(List<File> paths)206 public List<File> sanitizePaths(List<File> paths) { 207 return sanitizePaths(mProjectFolder, mProperties, paths); 208 } 209 getOutDir()210 public String getOutDir() { 211 return getOutDir(mProperties); 212 } 213 214 215 /** 216 * Returns the top level library dependencies of a given <var>source</var> representing a 217 * project properties. 218 * @param baseFolder the base folder of the project (to resolve relative paths) 219 * @param properties a source of project properties. 220 */ getDirectDependencies(File baseFolder, IPropertySource properties)221 private List<File> getDirectDependencies(File baseFolder, IPropertySource properties) { 222 ArrayList<File> libraries = new ArrayList<File>(); 223 224 // first build the list. they are ordered highest priority first. 225 int index = 1; 226 while (true) { 227 String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++); 228 String rootPath = properties.getProperty(propName); 229 230 if (rootPath == null) { 231 break; 232 } 233 234 try { 235 File library = new File(baseFolder, rootPath).getCanonicalFile(); 236 237 // check for validity 238 File projectProp = new File(library, PropertyType.PROJECT.getFilename()); 239 if (projectProp.isFile() == false) { 240 // error! 241 throw new BuildException(String.format( 242 "%1$s resolve to a path with no %2$s file for project %3$s", rootPath, 243 PropertyType.PROJECT.getFilename(), baseFolder.getAbsolutePath())); 244 } 245 246 if (libraries.contains(library) == false) { 247 if (mVerbose) { 248 System.out.println(String.format("%1$s: %2$s => %3$s", 249 baseFolder.getAbsolutePath(), rootPath, library.getAbsolutePath())); 250 } 251 252 libraries.add(library); 253 } 254 } catch (IOException e) { 255 throw new BuildException("Failed to resolve library path: " + rootPath, e); 256 } 257 } 258 259 return libraries; 260 } 261 262 /** 263 * Resolves a given list of libraries, finds out if they depend on other libraries, and 264 * returns a full list of all the direct and indirect dependencies in the proper order (first 265 * is higher priority when calling aapt). 266 * @param inLibraries the libraries to resolve 267 * @param outLibraries where to store all the libraries. 268 */ resolveFullLibraryDependencies(List<File> inLibraries, List<File> outLibraries)269 private void resolveFullLibraryDependencies(List<File> inLibraries, List<File> outLibraries) { 270 // loop in the inverse order to resolve dependencies on the libraries, so that if a library 271 // is required by two higher level libraries it can be inserted in the correct place 272 for (int i = inLibraries.size() - 1 ; i >= 0 ; i--) { 273 File library = inLibraries.get(i); 274 275 // get the default.property file for it 276 final ProjectProperties projectProp = ProjectProperties.load( 277 new FolderWrapper(library), PropertyType.PROJECT); 278 279 // get its libraries 280 List<File> dependencies = getDirectDependencies(library, projectProp); 281 282 // resolve the dependencies for those libraries 283 resolveFullLibraryDependencies(dependencies, outLibraries); 284 285 // and add the current one (if needed) in front (higher priority) 286 if (outLibraries.contains(library) == false) { 287 outLibraries.add(0, library); 288 } 289 } 290 } 291 getOutDir(IPropertySource properties)292 private static String getOutDir(IPropertySource properties) { 293 String bin = properties.getProperty("out.dir"); 294 if (bin == null) { 295 return SdkConstants.FD_OUTPUT; 296 } 297 298 return bin; 299 } 300 301 } 302