/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.ivy.ant; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.apache.ivy.Ivy; import org.apache.ivy.core.module.descriptor.DependencyDescriptor; import org.apache.ivy.core.module.descriptor.ModuleDescriptor; import org.apache.ivy.core.module.id.ModuleId; import org.apache.ivy.core.settings.IvySettings; import org.apache.ivy.core.sort.SortOptions; import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry; import org.apache.ivy.util.Message; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Path; /** * Creates an ant filelist of files (usually build.xml) ordered according to the dependencies * declared in ivy files. */ public class IvyBuildList extends IvyTask { public static final class OnMissingDescriptor { public static final String HEAD = "head"; public static final String TAIL = "tail"; public static final String SKIP = "skip"; public static final String FAIL = "fail"; public static final String WARN = "warn"; private OnMissingDescriptor() { } } public static final String DESCRIPTOR_REQUIRED = "required"; private List buildFileSets = new ArrayList(); // List (FileSet) private String reference; private boolean haltOnError = true; private String onMissingDescriptor = OnMissingDescriptor.HEAD; private boolean reverse = false; private String ivyFilePath; private String root = "*"; private boolean excludeRoot = false; private String leaf = "*"; private String delimiter = ","; private boolean excludeLeaf = false; private boolean onlydirectdep = false; private String restartFrom = "*"; public void addFileset(FileSet buildFiles) { buildFileSets.add(buildFiles); } public String getReference() { return reference; } public void setReference(String reference) { this.reference = reference; } public String getRoot() { return root; } public void setRoot(String root) { this.root = root; } public boolean isExcludeRoot() { return excludeRoot; } public void setExcludeRoot(boolean root) { excludeRoot = root; } public String getLeaf() { return leaf; } public void setLeaf(String leaf) { this.leaf = leaf; } public boolean isExcludeLeaf() { return excludeLeaf; } public void setExcludeLeaf(boolean excludeLeaf) { this.excludeLeaf = excludeLeaf; } public String getDelimiter() { return delimiter; } public void setDelimiter(String delimiter) { this.delimiter = delimiter; } public boolean getOnlydirectdep() { return onlydirectdep; } public void setOnlydirectdep(boolean onlydirectdep) { this.onlydirectdep = onlydirectdep; } public void doExecute() throws BuildException { if (reference == null) { throw new BuildException("reference should be provided in ivy build list"); } if (buildFileSets.isEmpty()) { throw new BuildException( "at least one nested fileset should be provided in ivy build list"); } Ivy ivy = getIvyInstance(); IvySettings settings = ivy.getSettings(); ivyFilePath = getProperty(ivyFilePath, settings, "ivy.buildlist.ivyfilepath"); Path path = new Path(getProject()); Map buildFiles = new HashMap(); // Map (ModuleDescriptor -> File buildFile) List independent = new ArrayList(); List noDescriptor = new ArrayList(); Collection mds = new ArrayList(); Set rootModuleNames = new LinkedHashSet(); if (!"*".equals(root)) { StringTokenizer st = new StringTokenizer(root, delimiter); while (st.hasMoreTokens()) { rootModuleNames.add(st.nextToken()); } } Set leafModuleNames = new LinkedHashSet(); if (!"*".equals(leaf)) { StringTokenizer st = new StringTokenizer(leaf, delimiter); while (st.hasMoreTokens()) { leafModuleNames.add(st.nextToken()); } } Set restartFromModuleNames = new LinkedHashSet(); if (!"*".equals(restartFrom)) { StringTokenizer st = new StringTokenizer(restartFrom, delimiter); // Only accept one (first) module restartFromModuleNames.add(st.nextToken()); } for (ListIterator iter = buildFileSets.listIterator(); iter.hasNext();) { FileSet fs = (FileSet) iter.next(); DirectoryScanner ds = fs.getDirectoryScanner(getProject()); String[] builds = ds.getIncludedFiles(); for (int i = 0; i < builds.length; i++) { File buildFile = new File(ds.getBasedir(), builds[i]); File ivyFile = getIvyFileFor(buildFile); if (!ivyFile.exists()) { onMissingDescriptor(buildFile, ivyFile, noDescriptor); } else { try { ModuleDescriptor md = ModuleDescriptorParserRegistry.getInstance() .parseDescriptor(settings, ivyFile.toURI().toURL(), doValidate(settings)); buildFiles.put(md, buildFile); mds.add(md); Message.debug("Add " + md.getModuleRevisionId().getModuleId()); } catch (Exception ex) { if (haltOnError) { throw new BuildException("impossible to parse ivy file for " + buildFile + ": ivyfile=" + ivyFile + " exception=" + ex, ex); } else { Message.warn("impossible to parse ivy file for " + buildFile + ": ivyfile=" + ivyFile + " exception=" + ex.getMessage()); Message.info("\t=> adding it at the beginning of the path"); independent.add(buildFile); } } } } } List leafModuleDescriptors = convertModuleNamesToModuleDescriptors(mds, leafModuleNames, "leaf"); List rootModuleDescriptors = convertModuleNamesToModuleDescriptors(mds, rootModuleNames, "root"); List restartFromModuleDescriptors = convertModuleNamesToModuleDescriptors(mds, restartFromModuleNames, "restartFrom"); if (!rootModuleDescriptors.isEmpty()) { Message.info("Filtering modules based on roots " + rootModuleNames); mds = filterModulesFromRoot(mds, rootModuleDescriptors); } if (!leafModuleDescriptors.isEmpty()) { Message.info("Filtering modules based on leafs " + leafModuleNames); mds = filterModulesFromLeaf(mds, leafModuleDescriptors); } List sortedModules = ivy.sortModuleDescriptors(mds, SortOptions.DEFAULT); if (!OnMissingDescriptor.TAIL.equals(onMissingDescriptor)) { for (ListIterator iter = noDescriptor.listIterator(); iter.hasNext();) { File buildFile = (File) iter.next(); addBuildFile(path, buildFile); } } for (ListIterator iter = independent.listIterator(); iter.hasNext();) { File buildFile = (File) iter.next(); addBuildFile(path, buildFile); } if (isReverse()) { Collections.reverse(sortedModules); } // Remove modules that are before the restartFrom point // Independent modules (without valid ivy file) can not be addressed // so they are not removed from build path. if (!restartFromModuleDescriptors.isEmpty()) { boolean foundRestartFrom = false; List keptModules = new ArrayList(); ModuleDescriptor restartFromModuleDescriptor = (ModuleDescriptor) restartFromModuleDescriptors .get(0); for (ListIterator iter = sortedModules.listIterator(); iter.hasNext();) { ModuleDescriptor md = (ModuleDescriptor) iter.next(); if (md.equals(restartFromModuleDescriptor)) { foundRestartFrom = true; } if (foundRestartFrom) { keptModules.add(md); } } sortedModules = keptModules; } StringBuffer order = new StringBuffer(); for (ListIterator iter = sortedModules.listIterator(); iter.hasNext();) { ModuleDescriptor md = (ModuleDescriptor) iter.next(); order.append(md.getModuleRevisionId().getModuleId()); if (iter.hasNext()) { order.append(", "); } File buildFile = (File) buildFiles.get(md); addBuildFile(path, buildFile); } if (OnMissingDescriptor.TAIL.equals(onMissingDescriptor)) { for (ListIterator iter = noDescriptor.listIterator(); iter.hasNext();) { File buildFile = (File) iter.next(); addBuildFile(path, buildFile); } } getProject().addReference(getReference(), path); getProject().setProperty("ivy.sorted.modules", order.toString()); } private void onMissingDescriptor(File buildFile, File ivyFile, List noDescriptor) { if (OnMissingDescriptor.SKIP.equals(onMissingDescriptor)) { Message.debug("skipping " + buildFile + ": descriptor " + ivyFile + " doesn't exist"); } else if (OnMissingDescriptor.FAIL.equals(onMissingDescriptor)) { throw new BuildException( "a module has no module descriptor and onMissingDescriptor=fail. " + "Build file: " + buildFile + ". Expected descriptor: " + ivyFile); } else { if (OnMissingDescriptor.WARN.equals(onMissingDescriptor)) { Message.warn("a module has no module descriptor. " + "Build file: " + buildFile + ". Expected descriptor: " + ivyFile); } Message.verbose("no descriptor for " + buildFile + ": descriptor=" + ivyFile + ": adding it at the " + (OnMissingDescriptor.TAIL.equals(onMissingDescriptor) ? "tail" : "head" + " of the path")); Message.verbose("\t(change onMissingDescriptor if you want to take another action"); noDescriptor.add(buildFile); } } private List convertModuleNamesToModuleDescriptors(Collection mds, Set moduleNames, String kind) { List result = new ArrayList(); Set foundModuleNames = new HashSet(); for (Iterator it = mds.iterator(); it.hasNext();) { ModuleDescriptor md = (ModuleDescriptor) it.next(); String name = md.getModuleRevisionId().getModuleId().getName(); if (moduleNames.contains(name)) { foundModuleNames.add(name); result.add(md); } } if (foundModuleNames.size() < moduleNames.size()) { Set missingModules = new HashSet(moduleNames); missingModules.removeAll(foundModuleNames); StringBuffer missingNames = new StringBuffer(); String sep = ""; for (Iterator it = missingModules.iterator(); it.hasNext();) { missingNames.append(sep); missingNames.append(it.next()); sep = ", "; } throw new BuildException("unable to find " + kind + " module(s) " + missingNames.toString() + " in build fileset"); } return result; } /** * Returns a collection of ModuleDescriptors that are conatined in the input collection of * ModuleDescriptors and upon which the root module depends * * @param mds * input collection of ModuleDescriptors * @param rootmd * root module * @return filtered list of modules */ private Collection filterModulesFromRoot(Collection mds, List rootmds) { // Make a map of ModuleId objects -> ModuleDescriptors Map moduleIdMap = new HashMap(); for (Iterator iter = mds.iterator(); iter.hasNext();) { ModuleDescriptor md = ((ModuleDescriptor) iter.next()); moduleIdMap.put(md.getModuleRevisionId().getModuleId(), md); } // recursively process the nodes Set toKeep = new LinkedHashSet(); Iterator it = rootmds.iterator(); while (it.hasNext()) { ModuleDescriptor rootmd = (ModuleDescriptor) it.next(); processFilterNodeFromRoot(rootmd, toKeep, moduleIdMap); // With the excluderoot attribute set to true, take the rootmd out of the toKeep set. if (excludeRoot) { // Only for logging purposes Message.verbose("Excluded module " + rootmd.getModuleRevisionId().getModuleId().getName()); } else { toKeep.add(rootmd); } } // just for logging for (Iterator iter = toKeep.iterator(); iter.hasNext();) { ModuleDescriptor md = ((ModuleDescriptor) iter.next()); Message.verbose("Kept module " + md.getModuleRevisionId().getModuleId().getName()); } return toKeep; } /** * Adds the current node to the toKeep collection and then processes the each of the direct * dependencies of this node that appear in the moduleIdMap (indicating that the dependency is * part of this BuildList) * * @param node * the node to be processed * @param toKeep * the set of ModuleDescriptors that should be kept * @param moduleIdMap * reference mapping of moduleId to ModuleDescriptor that are part of the BuildList */ private void processFilterNodeFromRoot(ModuleDescriptor node, Set toKeep, Map moduleIdMap) { // toKeep.add(node); DependencyDescriptor[] deps = node.getDependencies(); for (int i = 0; i < deps.length; i++) { ModuleId id = deps[i].getDependencyId(); ModuleDescriptor md = (ModuleDescriptor) moduleIdMap.get(id); // we test if this module id has a module descriptor, and if it isn't already in the // toKeep Set, in which there's probably a circular dependency if (md != null && !toKeep.contains(md)) { toKeep.add(md); if (!getOnlydirectdep()) { processFilterNodeFromRoot(md, toKeep, moduleIdMap); } } } } /** * Returns a collection of ModuleDescriptors that are conatined in the input collection of * ModuleDescriptors which depends on the leaf module * * @param mds * input collection of ModuleDescriptors * @param leafmd * leaf module * @return filtered list of modules */ private Collection filterModulesFromLeaf(Collection mds, List leafmds) { // Make a map of ModuleId objects -> ModuleDescriptors Map moduleIdMap = new HashMap(); for (Iterator iter = mds.iterator(); iter.hasNext();) { ModuleDescriptor md = ((ModuleDescriptor) iter.next()); moduleIdMap.put(md.getModuleRevisionId().getModuleId(), md); } // recursively process the nodes Set toKeep = new LinkedHashSet(); Iterator it = leafmds.iterator(); while (it.hasNext()) { ModuleDescriptor leafmd = (ModuleDescriptor) it.next(); // With the excludeleaf attribute set to true, take the rootmd out of the toKeep set. if (excludeLeaf) { Message.verbose("Excluded module " + leafmd.getModuleRevisionId().getModuleId().getName()); } else { toKeep.add(leafmd); } processFilterNodeFromLeaf(leafmd, toKeep, moduleIdMap); } // just for logging for (Iterator iter = toKeep.iterator(); iter.hasNext();) { ModuleDescriptor md = ((ModuleDescriptor) iter.next()); Message.verbose("Kept module " + md.getModuleRevisionId().getModuleId().getName()); } return toKeep; } /** * Search in the moduleIdMap modules depending on node, add them to the toKeep set and process * them recursively. * * @param node * the node to be processed * @param toKeep * the set of ModuleDescriptors that should be kept * @param moduleIdMap * reference mapping of moduleId to ModuleDescriptor that are part of the BuildList */ private void processFilterNodeFromLeaf(ModuleDescriptor node, Set toKeep, Map moduleIdMap) { for (Iterator iter = moduleIdMap.values().iterator(); iter.hasNext();) { ModuleDescriptor md = (ModuleDescriptor) iter.next(); DependencyDescriptor[] deps = md.getDependencies(); for (int i = 0; i < deps.length; i++) { ModuleId id = deps[i].getDependencyId(); if (node.getModuleRevisionId().getModuleId().equals(id) && !toKeep.contains(md)) { toKeep.add(md); if (!getOnlydirectdep()) { processFilterNodeFromLeaf(md, toKeep, moduleIdMap); } } } } } private void addBuildFile(Path path, File buildFile) { FileSet fs = new FileSet(); fs.setFile(buildFile); path.addFileset(fs); } private File getIvyFileFor(File buildFile) { return new File(buildFile.getParentFile(), ivyFilePath); } public boolean isHaltonerror() { return haltOnError; } public void setHaltonerror(boolean haltOnError) { this.haltOnError = haltOnError; } public String getIvyfilepath() { return ivyFilePath; } public void setIvyfilepath(String ivyFilePath) { this.ivyFilePath = ivyFilePath; } public String getOnMissingDescriptor() { return onMissingDescriptor; } public void setOnMissingDescriptor(String onMissingDescriptor) { this.onMissingDescriptor = onMissingDescriptor; } /** * @deprecated use {@link #getOnMissingDescriptor()} instead. */ @Deprecated public boolean isSkipbuildwithoutivy() { return onMissingDescriptor == OnMissingDescriptor.SKIP; } /** * @deprecated use {@link #setOnMissingDescriptor(String)} instead. */ @Deprecated public void setSkipbuildwithoutivy(boolean skipBuildFilesWithoutIvy) { Message.deprecated("skipbuildwithoutivy is deprecated, use onMissingDescriptor instead."); this.onMissingDescriptor = skipBuildFilesWithoutIvy ? OnMissingDescriptor.SKIP : OnMissingDescriptor.FAIL; } public boolean isReverse() { return reverse; } public void setReverse(boolean reverse) { this.reverse = reverse; } public String getRestartFrom() { return restartFrom; } public void setRestartFrom(String restartFrom) { this.restartFrom = restartFrom; } }