blob: 9b572d248b6c026a2e4ba158ec82f5cf335f3864 [file] [log] [blame]
/*
* 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
*
* https://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.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.apache.ivy.Ivy;
import org.apache.ivy.core.IvyPatternHelper;
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.matcher.MapMatcher;
import org.apache.ivy.plugins.matcher.PatternMatcher;
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 class BuildListModule {
private String organisation;
private String module;
private String revision;
private String branch;
private File file;
public String getOrganisation() {
return organisation;
}
public void setOrganisation(String organisation) {
this.organisation = organisation;
}
public String getModule() {
return module;
}
public void setModule(String module) {
this.module = module;
}
public String getRevision() {
return revision;
}
public void setRevision(String revision) {
this.revision = revision;
}
public String getBranch() {
return branch;
}
public void setBranch(String branch) {
this.branch = branch;
}
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
}
public static final String DESCRIPTOR_REQUIRED = "required";
private List<FileSet> buildFileSets = new ArrayList<>();
private String reference;
private boolean haltOnError = true;
private String onMissingDescriptor = OnMissingDescriptor.HEAD;
private boolean reverse = false;
private String ivyFilePath;
private String root = "*";
private List<BuildListModule> roots = new ArrayList<>();
private boolean excludeRoot = false;
private String leaf = "*";
private List<BuildListModule> leafs = new ArrayList<>();
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 BuildListModule createRoot() {
BuildListModule root = new BuildListModule();
roots.add(root);
return 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 BuildListModule createLeaf() {
BuildListModule leaf = new BuildListModule();
leafs.add(leaf);
return 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;
}
@Override
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<ModuleDescriptor, File> buildFiles = new HashMap<>();
List<File> independent = new ArrayList<>();
List<File> noDescriptor = new ArrayList<>();
Collection<ModuleDescriptor> mds = new ArrayList<>();
Set<MapMatcher> rootModules = convert(roots, root, settings);
Set<MapMatcher> leafModules = convert(leafs, leaf, settings);
Set<MapMatcher> restartFromModules = convert(Collections.<BuildListModule>emptyList(), restartFrom, settings);
for (FileSet fs : buildFileSets) {
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
for (String build : ds.getIncludedFiles()) {
File buildFile = new File(ds.getBasedir(), build);
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<ModuleDescriptor> leafModuleDescriptors =
findModuleDescriptors(mds, leafModules, "leaf");
List<ModuleDescriptor> rootModuleDescriptors =
findModuleDescriptors(mds, rootModules, "root");
List<ModuleDescriptor> restartFromModuleDescriptors =
findModuleDescriptors(mds, restartFromModules, "restartFrom");
if (!rootModuleDescriptors.isEmpty()) {
Message.info("Filtering modules based on roots [" + extractModuleNames(rootModules) + "]");
mds = filterModulesFromRoot(mds, rootModuleDescriptors);
}
if (!leafModuleDescriptors.isEmpty()) {
Message.info("Filtering modules based on leafs [" + extractModuleNames(leafModules) + "]");
mds = filterModulesFromLeaf(mds, leafModuleDescriptors);
}
List<ModuleDescriptor> sortedModules = ivy.sortModuleDescriptors(mds, SortOptions.DEFAULT);
if (!OnMissingDescriptor.TAIL.equals(onMissingDescriptor)) {
for (File buildFile : noDescriptor) {
addBuildFile(path, buildFile);
}
}
for (File buildFile : independent) {
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<ModuleDescriptor> keptModules = new ArrayList<>();
// Only accept one (first) module
ModuleDescriptor restartFromModuleDescriptor = restartFromModuleDescriptors.get(0);
for (ModuleDescriptor md : sortedModules) {
if (md.equals(restartFromModuleDescriptor)) {
foundRestartFrom = true;
}
if (foundRestartFrom) {
keptModules.add(md);
}
}
sortedModules = keptModules;
}
StringBuilder order = new StringBuilder();
for (ModuleDescriptor md : sortedModules) {
if (order.length() > 0) {
order.append(", ");
}
order.append(md.getModuleRevisionId().getModuleId());
addBuildFile(path, buildFiles.get(md));
}
if (OnMissingDescriptor.TAIL.equals(onMissingDescriptor)) {
for (File buildFile : noDescriptor) {
addBuildFile(path, buildFile);
}
}
getProject().addReference(getReference(), path);
getProject().setProperty("ivy.sorted.modules", order.toString());
}
private Set<MapMatcher> convert(List<BuildListModule> modulesList, String modulesString, IvySettings settings) {
Set<MapMatcher> result = new LinkedHashSet<>();
for (BuildListModule module : modulesList) {
File ivyFile = module.getFile();
if (ivyFile == null) {
String org = module.getOrganisation();
String name = module.getModule();
String rev = module.getRevision();
String branch = module.getBranch();
Map<String, String> attributes = new HashMap<>();
attributes.put(IvyPatternHelper.ORGANISATION_KEY, org == null ? PatternMatcher.ANY_EXPRESSION : org);
attributes.put(IvyPatternHelper.MODULE_KEY, name == null ? PatternMatcher.ANY_EXPRESSION : name);
attributes.put(IvyPatternHelper.MODULE_KEY, rev == null ? PatternMatcher.ANY_EXPRESSION : rev);
attributes.put(IvyPatternHelper.MODULE_KEY, branch == null ? PatternMatcher.ANY_EXPRESSION : branch);
result.add(new MapMatcher(attributes, settings.getMatcher(PatternMatcher.EXACT)));
} else {
try {
ModuleDescriptor md = ModuleDescriptorParserRegistry.getInstance()
.parseDescriptor(settings, ivyFile.toURI().toURL(),
doValidate(settings));
Map<String, String> attributes = new HashMap<>();
attributes.putAll(md.getModuleRevisionId().getAttributes());
attributes.put("resource", md.getResource().getName());
result.add(new MapMatcher(attributes, settings.getMatcher(PatternMatcher.EXACT)));
} catch (Exception e) {
throw new BuildException(e);
}
}
}
if (!"*".equals(modulesString)) {
StringTokenizer st = new StringTokenizer(modulesString, getDelimiter());
while (st.hasMoreTokens()) {
Map<String, String> attributes = new HashMap<>();
attributes.put(IvyPatternHelper.MODULE_KEY, st.nextToken());
result.add(new MapMatcher(attributes, settings.getMatcher(PatternMatcher.EXACT)));
}
}
return result;
}
private void onMissingDescriptor(File buildFile, File ivyFile, List<File> noDescriptor) {
switch (onMissingDescriptor) {
case OnMissingDescriptor.FAIL:
throw new BuildException("a module has no module descriptor and"
+ " onMissingDescriptor=fail. Build file: " + buildFile
+ ". Expected descriptor: " + ivyFile);
case OnMissingDescriptor.SKIP:
Message.debug("skipping " + buildFile + ": descriptor " + ivyFile
+ " doesn't exist");
break;
case OnMissingDescriptor.WARN:
Message.warn("a module has no module descriptor. " + "Build file: " + buildFile
+ ". Expected descriptor: " + ivyFile);
// fall through
default:
Message.verbose(String.format("no descriptor for %s: descriptor=%s: adding it at the %s of the path",
buildFile, ivyFile, (OnMissingDescriptor.TAIL.equals(onMissingDescriptor) ? "tail" : "head")));
Message.verbose("\t(change onMissingDescriptor if you want to take another action");
noDescriptor.add(buildFile);
break;
}
}
private List<ModuleDescriptor> findModuleDescriptors(
Collection<ModuleDescriptor> mds, Set<MapMatcher> matchers, String kind) {
List<ModuleDescriptor> result = new ArrayList<>();
Set<MapMatcher> missingMatchers = new HashSet<>(matchers);
for (ModuleDescriptor md : mds) {
Map<String, String> attributes = new HashMap<>();
attributes.putAll(md.getAttributes());
attributes.put("resource", md.getResource().getName());
for (MapMatcher matcher : matchers) {
if (matcher.matches(attributes)) {
missingMatchers.remove(matcher);
result.add(md);
}
}
}
if (!missingMatchers.isEmpty()) {
throw new BuildException("unable to find " + kind + " module(s) "
+ extractModuleNames(missingMatchers) + " in build fileset");
}
return result;
}
private String extractModuleNames(Set<MapMatcher> matchers) {
StringBuilder result = new StringBuilder();
String sep = "";
for (MapMatcher matcher : matchers) {
result.append(sep);
Map<String, String> attributes = matcher.getAttributes();
String organisation = attributes.get(IvyPatternHelper.ORGANISATION_KEY);
if (organisation != null && !PatternMatcher.ANY_EXPRESSION.equals(organisation)) {
result.append(organisation);
result.append('#');
}
result.append(attributes.get(IvyPatternHelper.MODULE_KEY));
sep = ", ";
}
return result.toString();
}
/**
* Returns a collection of ModuleDescriptors that are contained in the input collection of
* ModuleDescriptors and upon which the root module depends
*
* @param mds
* input collection of ModuleDescriptors
* @param rootmds
* root module
* @return filtered list of modules
*/
private Collection<ModuleDescriptor> filterModulesFromRoot(Collection<ModuleDescriptor> mds,
List<ModuleDescriptor> rootmds) {
Map<ModuleId, ModuleDescriptor> moduleIdMap = new HashMap<>();
for (ModuleDescriptor md : mds) {
moduleIdMap.put(md.getModuleRevisionId().getModuleId(), md);
}
// recursively process the nodes
Set<ModuleDescriptor> toKeep = new LinkedHashSet<>();
for (ModuleDescriptor rootmd : rootmds) {
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 (ModuleDescriptor md : toKeep) {
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<ModuleDescriptor> toKeep,
Map<ModuleId, ModuleDescriptor> moduleIdMap) {
// toKeep.add(node);
for (DependencyDescriptor dep : node.getDependencies()) {
ModuleId id = dep.getDependencyId();
ModuleDescriptor md = 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 contained in the input collection of
* ModuleDescriptors which depends on the leaf module
*
* @param mds
* input collection of ModuleDescriptors
* @param leafmds
* leaf module
* @return filtered list of modules
*/
private Collection<ModuleDescriptor> filterModulesFromLeaf(Collection<ModuleDescriptor> mds,
List<ModuleDescriptor> leafmds) {
Map<ModuleId, ModuleDescriptor> moduleIdMap = new HashMap<>();
for (ModuleDescriptor md : mds) {
moduleIdMap.put(md.getModuleRevisionId().getModuleId(), md);
}
// recursively process the nodes
Set<ModuleDescriptor> toKeep = new LinkedHashSet<>();
for (ModuleDescriptor leafmd : leafmds) {
// 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 (ModuleDescriptor md : toKeep) {
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<ModuleDescriptor> toKeep,
Map<ModuleId, ModuleDescriptor> moduleIdMap) {
for (ModuleDescriptor md : moduleIdMap.values()) {
for (DependencyDescriptor dep : md.getDependencies()) {
if (node.getModuleRevisionId().getModuleId().equals(dep.getDependencyId())
&& !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;
}
/**
* @return boolean
* @deprecated use {@link #getOnMissingDescriptor()} instead.
*/
@Deprecated
public boolean isSkipbuildwithoutivy() {
return OnMissingDescriptor.SKIP.equals(onMissingDescriptor);
}
/**
* @param skipBuildFilesWithoutIvy boolean
* @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;
}
}