/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ant; import com.android.SdkConstants; import com.android.annotations.Nullable; import com.android.io.FolderWrapper; import com.android.sdklib.build.JarListSanitizer; import com.android.sdklib.build.JarListSanitizer.DifferentLibException; import com.android.sdklib.build.JarListSanitizer.Sha1Exception; import com.android.sdklib.internal.project.IPropertySource; import com.android.sdklib.internal.project.ProjectProperties; import com.android.sdklib.internal.project.ProjectProperties.PropertyType; import org.apache.tools.ant.BuildException; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; /** * Helper class to manage dependency for projects. * */ public class DependencyHelper { private final boolean mVerbose; private final File mProjectFolder; private final IPropertySource mProperties; private final List<File> mLibraries = new ArrayList<File>(); /** * A Library Processor. Used in {@link DependencyHelper#processLibraries(LibraryProcessor)} * */ protected interface LibraryProcessor { void processLibrary(String libRootPath); } /** * Advanced version of the {@link LibraryProcessor} that provides the library properties * to the processor. */ public abstract static class AdvancedLibraryProcessor implements LibraryProcessor { public abstract void processLibrary(String libRootPath, IPropertySource properties); @Override public final void processLibrary(String libRootPath) { ProjectProperties properties = TaskHelper.getProperties(libRootPath); processLibrary(libRootPath, properties); } } /** * Implementation of {@link AdvancedLibraryProcessor} that builds a list of sanitized list * of 3rd party jar files from all the Library Projects. */ public static class JarProcessor extends AdvancedLibraryProcessor { private final List<File> mJars = new ArrayList<File>(); private final FilenameFilter mFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase(Locale.US).endsWith(".jar"); } }; public List<File> getJars() { return mJars; } public FilenameFilter getFilter() { return mFilter; } @Override public void processLibrary(String libRootPath, IPropertySource properties) { // get the library output // FIXME: support renamed folder. mJars.add(new File(libRootPath + "/" + SdkConstants.FD_OUTPUT + "/" + SdkConstants.FN_CLASSES_JAR)); // Get the 3rd party jar files. // FIXME: support renamed folder. File libsFolder = new File(libRootPath, SdkConstants.FD_NATIVE_LIBS); File[] jarFiles = libsFolder.listFiles(mFilter); if (jarFiles != null) { for (File jarFile : jarFiles) { mJars.add(jarFile); } } } } public static List<File> sanitizePaths(File projectFolder, IPropertySource properties, List<File> paths) { // first get the non-files. List<File> results = new ArrayList<File>(); for (int i = 0 ; i < paths.size() ;) { File f = paths.get(i); // TEMP WORKAROUND: ignore classes.jar as all the output of libraries are // called the same (in Ant) but are not actually the same jar file. // TODO: Be aware of library output vs. regular jar dependency. if (f.isFile() && f.getName().equals(SdkConstants.FN_CLASSES_JAR) == false) { i++; } else { results.add(f); paths.remove(i); } } File outputFile = new File(projectFolder, getOutDir(properties)); JarListSanitizer sanitizer = new JarListSanitizer(outputFile); try { results.addAll(sanitizer.sanitize(paths)); } catch (DifferentLibException e) { String[] details = e.getDetails(); for (String s : details) { System.err.println(s); } throw new BuildException(e.getMessage(), e); } catch (Sha1Exception e) { throw new BuildException( "Failed to compute sha1 for " + e.getJarFile().getAbsolutePath(), e); } return results; } /** * * @param projectFolder the project root folder. */ public DependencyHelper(File projectFolder, boolean verbose) { mProjectFolder = projectFolder; mVerbose = verbose; mProperties = TaskHelper.getProperties(projectFolder.getAbsolutePath()); init(projectFolder); } /** * * @param projectFolder the project root folder. * @param properties an {@link IPropertySource} that can provide the project properties values. * @param verbose whether the output is verbose */ public DependencyHelper(File projectFolder, IPropertySource properties, boolean verbose) { mProjectFolder = projectFolder; mProperties = properties; mVerbose = verbose; init(projectFolder); } private void init(File projectFolder) { // get the top level list of library dependencies. List<File> topLevelLibraries = getDirectDependencies(projectFolder, mProperties); // process the libraries in case they depend on other libraries. resolveFullLibraryDependencies(topLevelLibraries, mLibraries); } public List<File> getLibraries() { return mLibraries; } public int getLibraryCount() { return mLibraries.size(); } public String getProperty(String name) { return mProperties.getProperty(name); } public void processLibraries(@Nullable LibraryProcessor processor) { // use that same order to process the libraries. for (File library : mLibraries) { // get the root path. String libRootPath = library.getAbsolutePath(); if (mVerbose) { System.out.println(libRootPath); } if (processor != null) { processor.processLibrary(libRootPath); } } } public List<File> sanitizePaths(List<File> paths) { return sanitizePaths(mProjectFolder, mProperties, paths); } public String getOutDir() { return getOutDir(mProperties); } /** * Returns the top level library dependencies of a given <var>source</var> representing a * project properties. * @param baseFolder the base folder of the project (to resolve relative paths) * @param properties a source of project properties. */ private List<File> getDirectDependencies(File baseFolder, IPropertySource properties) { ArrayList<File> libraries = new ArrayList<File>(); // first build the list. they are ordered highest priority first. int index = 1; while (true) { String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index++); String rootPath = properties.getProperty(propName); if (rootPath == null) { break; } try { File library = new File(baseFolder, rootPath).getCanonicalFile(); // check for validity File projectProp = new File(library, PropertyType.PROJECT.getFilename()); if (projectProp.isFile() == false) { // error! throw new BuildException(String.format( "%1$s resolve to a path with no %2$s file for project %3$s", rootPath, PropertyType.PROJECT.getFilename(), baseFolder.getAbsolutePath())); } if (libraries.contains(library) == false) { if (mVerbose) { System.out.println(String.format("%1$s: %2$s => %3$s", baseFolder.getAbsolutePath(), rootPath, library.getAbsolutePath())); } libraries.add(library); } } catch (IOException e) { throw new BuildException("Failed to resolve library path: " + rootPath, e); } } return libraries; } /** * Resolves a given list of libraries, finds out if they depend on other libraries, and * returns a full list of all the direct and indirect dependencies in the proper order (first * is higher priority when calling aapt). * @param inLibraries the libraries to resolve * @param outLibraries where to store all the libraries. */ private void resolveFullLibraryDependencies(List<File> inLibraries, List<File> outLibraries) { // loop in the inverse order to resolve dependencies on the libraries, so that if a library // is required by two higher level libraries it can be inserted in the correct place for (int i = inLibraries.size() - 1 ; i >= 0 ; i--) { File library = inLibraries.get(i); // get the default.property file for it final ProjectProperties projectProp = ProjectProperties.load( new FolderWrapper(library), PropertyType.PROJECT); // get its libraries List<File> dependencies = getDirectDependencies(library, projectProp); // resolve the dependencies for those libraries resolveFullLibraryDependencies(dependencies, outLibraries); // and add the current one (if needed) in front (higher priority) if (outLibraries.contains(library) == false) { outLibraries.add(0, library); } } } private static String getOutDir(IPropertySource properties) { String bin = properties.getProperty("out.dir"); if (bin == null) { return SdkConstants.FD_OUTPUT; } return bin; } }