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