0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018: package org.apache.ivy.core.resolve;
0019:
0020: import java.io.File;
0021: import java.io.FileOutputStream;
0022: import java.io.IOException;
0023: import java.net.URL;
0024: import java.text.ParseException;
0025: import java.util.ArrayList;
0026: import java.util.Arrays;
0027: import java.util.Collection;
0028: import java.util.Collections;
0029: import java.util.Date;
0030: import java.util.HashSet;
0031: import java.util.Iterator;
0032: import java.util.LinkedHashSet;
0033: import java.util.List;
0034: import java.util.ListIterator;
0035: import java.util.Properties;
0036: import java.util.Set;
0037:
0038: import org.apache.ivy.Ivy;
0039: import org.apache.ivy.core.IvyContext;
0040: import org.apache.ivy.core.LogOptions;
0041: import org.apache.ivy.core.cache.ResolutionCacheManager;
0042: import org.apache.ivy.core.event.EventManager;
0043: import org.apache.ivy.core.event.download.PrepareDownloadEvent;
0044: import org.apache.ivy.core.event.resolve.EndResolveEvent;
0045: import org.apache.ivy.core.event.resolve.StartResolveEvent;
0046: import org.apache.ivy.core.module.descriptor.Artifact;
0047: import org.apache.ivy.core.module.descriptor.Configuration;
0048: import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
0049: import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
0050: import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
0051: import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
0052: import org.apache.ivy.core.module.id.ModuleId;
0053: import org.apache.ivy.core.module.id.ModuleRevisionId;
0054: import org.apache.ivy.core.report.ArtifactDownloadReport;
0055: import org.apache.ivy.core.report.ConfigurationResolveReport;
0056: import org.apache.ivy.core.report.DownloadReport;
0057: import org.apache.ivy.core.report.DownloadStatus;
0058: import org.apache.ivy.core.report.ResolveReport;
0059: import org.apache.ivy.core.resolve.IvyNodeEviction.EvictionData;
0060: import org.apache.ivy.core.sort.SortEngine;
0061: import org.apache.ivy.plugins.conflict.ConflictManager;
0062: import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
0063: import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
0064: import org.apache.ivy.plugins.repository.url.URLResource;
0065: import org.apache.ivy.plugins.resolver.CacheResolver;
0066: import org.apache.ivy.plugins.resolver.DependencyResolver;
0067: import org.apache.ivy.util.Message;
0068: import org.apache.ivy.util.filter.Filter;
0069:
0070: /**
0071: * The resolve engine which is the core of the dependency resolution mechanism used in Ivy. It
0072: * features several resolve methods, some very simple, like {@link #resolve(File)} and
0073: * {@link #resolve(URL)} which allow to simply resolve dependencies of a single module descriptor,
0074: * or more complete one, like the {@link #resolve(ModuleDescriptor, ResolveOptions)} which allows to
0075: * provide options to the resolution engine.
0076: *
0077: * @see ResolveOptions
0078: */
0079: public class ResolveEngine {
0080: private ResolveEngineSettings settings;
0081:
0082: private EventManager eventManager;
0083:
0084: private SortEngine sortEngine;
0085:
0086: private Set fetchedSet = new HashSet();
0087:
0088: private DependencyResolver dictatorResolver;
0089:
0090: /**
0091: * Constructs a ResolveEngine.
0092: *
0093: * @param settings
0094: * the settings to use to configure the engine. Must not be null.
0095: * @param eventManager
0096: * the event manager to use to send events about the resolution process. Must not be
0097: * null.
0098: * @param sortEngine
0099: * the sort engine to use to sort modules before producing the dependency resolution
0100: * report. Must not be null.
0101: */
0102: public ResolveEngine(ResolveEngineSettings settings,
0103: EventManager eventManager, SortEngine sortEngine) {
0104: this .settings = settings;
0105: this .eventManager = eventManager;
0106: this .sortEngine = sortEngine;
0107: }
0108:
0109: /**
0110: * Returns the currently configured dictator resolver, which when non null is used in place of
0111: * any specified resolver in the {@link IvySettings}
0112: *
0113: * @return the currently configured dictator resolver, may be null.
0114: */
0115: public DependencyResolver getDictatorResolver() {
0116: return dictatorResolver;
0117: }
0118:
0119: /**
0120: * Sets a dictator resolver, which is used in place of regular dependency resolver for
0121: * subsequent dependency resolution by this engine.
0122: *
0123: * @param dictatorResolver
0124: * the dictator resolver to use in this engine, null if regular settings should used
0125: */
0126: public void setDictatorResolver(DependencyResolver dictatorResolver) {
0127: this .dictatorResolver = dictatorResolver;
0128: settings.setDictatorResolver(dictatorResolver);
0129: }
0130:
0131: public ResolveReport resolve(File ivySource) throws ParseException,
0132: IOException {
0133: return resolve(ivySource.toURL());
0134: }
0135:
0136: public ResolveReport resolve(URL ivySource) throws ParseException,
0137: IOException {
0138: return resolve(ivySource, new ResolveOptions());
0139: }
0140:
0141: /**
0142: * Resolves the module identified by the given mrid with its dependencies if transitive is set
0143: * to true.
0144: */
0145: public ResolveReport resolve(final ModuleRevisionId mrid,
0146: ResolveOptions options, boolean changing)
0147: throws ParseException, IOException {
0148: DefaultModuleDescriptor md;
0149: ResolveOptions optionsToUse = new ResolveOptions(options);
0150:
0151: if (options.useSpecialConfs()) {
0152: // create new resolve options because this is a different resolve than the real resolve
0153: // (which will be a resolve of a newCallerInstance module)
0154: ResolvedModuleRevision rmr = findModule(mrid,
0155: new ResolveOptions(options));
0156: if (rmr == null) {
0157: Message.verbose("module not found " + mrid);
0158:
0159: // we will continue the resolve anyway to get a nice error message back
0160: // to the user, however reduce the amount of logging in this case
0161: optionsToUse.setLog(LogOptions.LOG_DOWNLOAD_ONLY);
0162: md = DefaultModuleDescriptor.newCallerInstance(mrid,
0163: new String[] { "default" }, options
0164: .isTransitive(), changing);
0165: } else {
0166: String[] confs = options.getConfs(rmr.getDescriptor());
0167: md = DefaultModuleDescriptor.newCallerInstance(
0168: ModuleRevisionId.newInstance(mrid, rmr.getId()
0169: .getRevision()), confs, options
0170: .isTransitive(), changing);
0171: }
0172: } else {
0173: md = DefaultModuleDescriptor.newCallerInstance(mrid,
0174: options.getConfs(), options.isTransitive(),
0175: changing);
0176: }
0177:
0178: return resolve(md, optionsToUse);
0179: }
0180:
0181: /**
0182: * Resolve dependencies of a module described by an ivy file.
0183: */
0184: public ResolveReport resolve(URL ivySource, ResolveOptions options)
0185: throws ParseException, IOException {
0186: URLResource res = new URLResource(ivySource);
0187: ModuleDescriptorParser parser = ModuleDescriptorParserRegistry
0188: .getInstance().getParser(res);
0189: Message.verbose("using " + parser + " to parse " + ivySource);
0190: ModuleDescriptor md = parser.parseDescriptor(settings,
0191: ivySource, options.isValidate());
0192: String revision = options.getRevision();
0193: if (revision == null
0194: && md.getResolvedModuleRevisionId().getRevision() == null) {
0195: revision = Ivy.getWorkingRevision();
0196: }
0197: if (revision != null) {
0198: md.setResolvedModuleRevisionId(ModuleRevisionId
0199: .newInstance(md.getModuleRevisionId(), revision));
0200: }
0201:
0202: return resolve(md, options);
0203: }
0204:
0205: /**
0206: * Resolve dependencies of a module described by a module descriptor.
0207: */
0208: public ResolveReport resolve(ModuleDescriptor md,
0209: ResolveOptions options) throws ParseException, IOException {
0210: DependencyResolver oldDictator = getDictatorResolver();
0211: IvyContext context = IvyContext.getContext();
0212: if (options.isUseCacheOnly()) {
0213: setDictatorResolver(new CacheResolver(settings));
0214: }
0215: try {
0216: String[] confs = options.getConfs(md);
0217: options.setConfs(confs);
0218:
0219: if (options.getResolveId() == null) {
0220: options.setResolveId(ResolveOptions
0221: .getDefaultResolveId(md));
0222: }
0223:
0224: eventManager.fireIvyEvent(new StartResolveEvent(md, confs));
0225:
0226: long start = System.currentTimeMillis();
0227: if (ResolveOptions.LOG_DEFAULT.equals(options.getLog())) {
0228: Message.info(":: resolving dependencies :: "
0229: + md.getResolvedModuleRevisionId()
0230: + (options.isTransitive() ? ""
0231: : " [not transitive]"));
0232: Message.info("\tconfs: " + Arrays.asList(confs));
0233: } else {
0234: Message.verbose(":: resolving dependencies :: "
0235: + md.getResolvedModuleRevisionId()
0236: + (options.isTransitive() ? ""
0237: : " [not transitive]"));
0238: Message.verbose("\tconfs: " + Arrays.asList(confs));
0239: }
0240: Message.verbose("\tvalidate = " + options.isValidate());
0241: Message.verbose("\trefresh = " + options.isRefresh());
0242:
0243: ResolveReport report = new ResolveReport(md, options
0244: .getResolveId());
0245:
0246: ResolveData data = new ResolveData(this , options);
0247: context.setResolveData(data);
0248:
0249: // resolve dependencies
0250: IvyNode[] dependencies = getDependencies(md, options,
0251: report);
0252: report.setDependencies(Arrays.asList(dependencies), options
0253: .getArtifactFilter());
0254:
0255: // produce resolved ivy file and ivy properties in cache
0256: ResolutionCacheManager cacheManager = settings
0257: .getResolutionCacheManager();
0258: File ivyFileInCache = cacheManager
0259: .getResolvedIvyFileInCache(md
0260: .getResolvedModuleRevisionId());
0261: md.toIvyFile(ivyFileInCache);
0262:
0263: // we store the resolved dependencies revisions and statuses per asked dependency
0264: // revision id,
0265: // for direct dependencies only.
0266: // this is used by the deliver task to resolve dynamic revisions to static ones
0267: File ivyPropertiesInCache = cacheManager
0268: .getResolvedIvyPropertiesInCache(md
0269: .getResolvedModuleRevisionId());
0270: Properties props = new Properties();
0271: if (dependencies.length > 0) {
0272: IvyNode root = dependencies[0].getRoot();
0273: for (int i = 0; i < dependencies.length; i++) {
0274: if (!dependencies[i].hasProblem()) {
0275: DependencyDescriptor dd = dependencies[i]
0276: .getDependencyDescriptor(root);
0277: if (dd != null) {
0278: ModuleRevisionId depResolvedId = dependencies[i]
0279: .getResolvedId();
0280: ModuleDescriptor depDescriptor = dependencies[i]
0281: .getDescriptor();
0282: ModuleRevisionId depRevisionId = dd
0283: .getDependencyRevisionId();
0284: if (depResolvedId == null) {
0285: throw new NullPointerException(
0286: "getResolvedId() is null for "
0287: + dependencies[i]
0288: .toString());
0289: }
0290: if (depRevisionId == null) {
0291: throw new NullPointerException(
0292: "getDependencyRevisionId() "
0293: + "is null for "
0294: + dd.toString());
0295: }
0296: String rev = depResolvedId.getRevision();
0297: // The evicted modules have no description, so we can't put their
0298: // status.
0299: String status = depDescriptor == null ? "?"
0300: : depDescriptor.getStatus();
0301: props.put(depRevisionId.encodeToString(),
0302: rev + " " + status);
0303: }
0304: }
0305: }
0306: }
0307: FileOutputStream out = new FileOutputStream(
0308: ivyPropertiesInCache);
0309: props.store(out, md.getResolvedModuleRevisionId()
0310: + " resolved revisions");
0311: out.close();
0312: Message.verbose("\tresolved ivy file produced in "
0313: + ivyFileInCache);
0314:
0315: report.setResolveTime(System.currentTimeMillis() - start);
0316:
0317: if (options.isDownload()) {
0318: Message.verbose(":: downloading artifacts ::");
0319:
0320: downloadArtifacts(report, options.getArtifactFilter(),
0321: (DownloadOptions) new DownloadOptions()
0322: .setLog(options.getLog()));
0323: }
0324:
0325: if (options.isOutputReport()) {
0326: outputReport(report, cacheManager, options);
0327: }
0328:
0329: Message.verbose("\tresolve done ("
0330: + report.getResolveTime() + "ms resolve - "
0331: + report.getDownloadTime() + "ms download)");
0332: Message.sumupProblems();
0333:
0334: eventManager.fireIvyEvent(new EndResolveEvent(md, confs,
0335: report));
0336: return report;
0337: } catch (RuntimeException ex) {
0338: Message.error(ex.getMessage());
0339: Message.sumupProblems();
0340: throw ex;
0341: } finally {
0342: context.setResolveData(null);
0343: setDictatorResolver(oldDictator);
0344: }
0345: }
0346:
0347: public void outputReport(ResolveReport report,
0348: ResolutionCacheManager cacheMgr, ResolveOptions options)
0349: throws IOException {
0350: if (ResolveOptions.LOG_DEFAULT.equals(options.getLog())) {
0351: Message.info(":: resolution report :: resolve "
0352: + report.getResolveTime() + "ms"
0353: + " :: artifacts dl " + report.getDownloadTime()
0354: + "ms");
0355: } else {
0356: Message.verbose(":: resolution report :: resolve "
0357: + report.getResolveTime() + "ms"
0358: + " :: artifacts dl " + report.getDownloadTime()
0359: + "ms");
0360: }
0361: report.setProblemMessages(Message.getProblems());
0362: // output report
0363: report
0364: .output(settings.getReportOutputters(), cacheMgr,
0365: options);
0366: }
0367:
0368: public void downloadArtifacts(ResolveReport report,
0369: Filter artifactFilter, DownloadOptions options) {
0370: long start = System.currentTimeMillis();
0371: IvyNode[] dependencies = (IvyNode[]) report.getDependencies()
0372: .toArray(new IvyNode[report.getDependencies().size()]);
0373:
0374: eventManager.fireIvyEvent(new PrepareDownloadEvent(
0375: (Artifact[]) report.getArtifacts().toArray(
0376: new Artifact[report.getArtifacts().size()])));
0377:
0378: long totalSize = 0;
0379: for (int i = 0; i < dependencies.length; i++) {
0380: checkInterrupted();
0381: // download artifacts required in all asked configurations
0382: if (!dependencies[i].isCompletelyEvicted()
0383: && !dependencies[i].hasProblem()) {
0384: DependencyResolver resolver = dependencies[i]
0385: .getModuleRevision().getArtifactResolver();
0386: Artifact[] selectedArtifacts = dependencies[i]
0387: .getSelectedArtifacts(artifactFilter);
0388: DownloadReport dReport = resolver.download(
0389: selectedArtifacts, options);
0390: ArtifactDownloadReport[] adrs = dReport
0391: .getArtifactsReports();
0392: for (int j = 0; j < adrs.length; j++) {
0393: if (adrs[j].getDownloadStatus() == DownloadStatus.FAILED) {
0394: Message.warn("\t" + adrs[j]);
0395: resolver.reportFailure(adrs[j].getArtifact());
0396: } else if (adrs[j].getDownloadStatus() == DownloadStatus.SUCCESSFUL) {
0397: totalSize += adrs[j].getSize();
0398: }
0399: }
0400: // update concerned reports
0401: String[] dconfs = dependencies[i]
0402: .getRootModuleConfigurations();
0403: for (int j = 0; j < dconfs.length; j++) {
0404: // the report itself is responsible to take into account only
0405: // artifacts required in its corresponding configuration
0406: // (as described by the Dependency object)
0407: if (dependencies[i].isEvicted(dconfs[j])
0408: || dependencies[i].isBlacklisted(dconfs[j])) {
0409: report.getConfigurationReport(dconfs[j])
0410: .addDependency(dependencies[i]);
0411: } else {
0412: report
0413: .getConfigurationReport(dconfs[j])
0414: .addDependency(dependencies[i], dReport);
0415: }
0416: }
0417: }
0418: }
0419: report.setDownloadTime(System.currentTimeMillis() - start);
0420: report.setDownloadSize(totalSize);
0421: }
0422:
0423: /**
0424: * Download an artifact to the cache. Not used internally, useful especially for IDE plugins
0425: * needing to download artifact one by one (for source or javadoc artifact, for instance).
0426: * <p>
0427: * Downloaded artifact file can be accessed using {@link ArtifactDownloadReport#getLocalFile()}.
0428: * </p>
0429: * <p>
0430: * It is possible to track the progression of the download using classical ivy progress
0431: * monitoring feature (see addTransferListener).
0432: * </p>
0433: *
0434: * @param artifact
0435: * the artifact to download
0436: * @return a report concerning the download
0437: */
0438: public ArtifactDownloadReport download(Artifact artifact,
0439: DownloadOptions options) {
0440: DependencyResolver resolver = settings.getResolver(artifact
0441: .getModuleRevisionId());
0442: DownloadReport r = resolver.download(
0443: new Artifact[] { artifact }, options);
0444: return r.getArtifactReport(artifact);
0445: }
0446:
0447: /**
0448: * Resolve the dependencies of a module without downloading corresponding artifacts. The module
0449: * to resolve is given by its ivy file URL. This method requires appropriate configuration of
0450: * the ivy instance, especially resolvers.
0451: *
0452: * @param ivySource
0453: * url of the ivy file to use for dependency resolving
0454: * @param confs
0455: * an array of configuration names to resolve - must not be null nor empty
0456: * @param getCache
0457: * the cache to use - default cache is used if null
0458: * @param date
0459: * the date to which resolution must be done - may be null
0460: * @return an array of the resolved dependencies
0461: * @throws ParseException
0462: * if a parsing problem occured in the ivy file
0463: * @throws IOException
0464: * if an IO problem was raised during ivy file parsing
0465: */
0466: public IvyNode[] getDependencies(URL ivySource,
0467: ResolveOptions options) throws ParseException, IOException {
0468: return getDependencies(ModuleDescriptorParserRegistry
0469: .getInstance().parseDescriptor(settings, ivySource,
0470: options.isValidate()), options, null);
0471: }
0472:
0473: /**
0474: * Resolve the dependencies of a module without downloading corresponding artifacts. The module
0475: * to resolve is given by its module descriptor.This method requires appropriate configuration
0476: * of the ivy instance, especially resolvers.
0477: *
0478: * @param md
0479: * the descriptor of the module for which we want to get dependencies - must not be
0480: * null
0481: * @param options
0482: * the resolve options to use to resolve the dependencies
0483: * @param report
0484: * a resolve report to fill during resolution - may be null
0485: * @return an array of the resolved Dependencies
0486: */
0487: public IvyNode[] getDependencies(ModuleDescriptor md,
0488: ResolveOptions options, ResolveReport report) {
0489: // check parameters
0490: if (md == null) {
0491: throw new NullPointerException(
0492: "module descriptor must not be null");
0493: }
0494: String[] confs = options.getConfs(md);
0495: Collection missingConfs = new ArrayList();
0496: for (int i = 0; i < confs.length; i++) {
0497: if (confs[i] == null) {
0498: throw new NullPointerException(
0499: "null conf not allowed: confs where: "
0500: + Arrays.asList(confs));
0501: }
0502:
0503: if (md.getConfiguration(confs[i]) == null) {
0504: missingConfs.add(" '" + confs[i] + "' ");
0505: }
0506: }
0507: if (!missingConfs.isEmpty()) {
0508: throw new IllegalArgumentException(
0509: "requested configuration"
0510: + (missingConfs.size() > 1 ? "s" : "")
0511: + " not found in "
0512: + md.getModuleRevisionId() + ": "
0513: + missingConfs);
0514: }
0515:
0516: IvyContext context = IvyContext.pushNewCopyContext();
0517: try {
0518: options.setConfs(confs);
0519:
0520: Date reportDate = new Date();
0521: ResolveData data = context.getResolveData();
0522: if (data == null) {
0523: data = new ResolveData(this , options);
0524: context.setResolveData(data);
0525: }
0526: IvyNode rootNode = new IvyNode(data, md);
0527:
0528: for (int i = 0; i < confs.length; i++) {
0529: Message
0530: .verbose("resolving dependencies for configuration '"
0531: + confs[i] + "'");
0532: // for each configuration we clear the cache of what's been fetched
0533: fetchedSet.clear();
0534:
0535: ConfigurationResolveReport confReport = null;
0536: if (report != null) {
0537: confReport = report
0538: .getConfigurationReport(confs[i]);
0539: if (confReport == null) {
0540: confReport = new ConfigurationResolveReport(
0541: this , md, confs[i], reportDate, options);
0542: report.addReport(confs[i], confReport);
0543: }
0544: }
0545: // we reuse the same resolve data with a new report for each conf
0546: data.setReport(confReport);
0547:
0548: // update the root module conf we are about to fetch
0549: VisitNode root = new VisitNode(data, rootNode, null,
0550: confs[i], null);
0551: root.setRequestedConf(confs[i]);
0552: rootNode.updateConfsToFetch(Collections
0553: .singleton(confs[i]));
0554:
0555: // go fetch !
0556: boolean fetched = false;
0557: while (!fetched) {
0558: try {
0559: fetchDependencies(root, confs[i], false);
0560: fetched = true;
0561: } catch (RestartResolveProcess restart) {
0562: Message
0563: .verbose("====================================================");
0564: Message
0565: .verbose("= RESTARTING RESOLVE PROCESS");
0566: Message.verbose("= " + restart.getMessage());
0567: Message
0568: .verbose("====================================================");
0569: fetchedSet.clear();
0570: }
0571: }
0572:
0573: // clean data
0574: for (Iterator iter = data.getNodes().iterator(); iter
0575: .hasNext();) {
0576: IvyNode dep = (IvyNode) iter.next();
0577: dep.clean();
0578: }
0579: }
0580:
0581: // prune and reverse sort fectched dependencies
0582: Collection nodes = data.getNodes();
0583: // use a Set to avoid duplicates, linked to preserve order
0584: Collection dependencies = new LinkedHashSet(nodes.size());
0585: for (Iterator iter = nodes.iterator(); iter.hasNext();) {
0586: IvyNode node = (IvyNode) iter.next();
0587: if (node != null && !node.isRoot()
0588: && !node.isCompletelyBlacklisted()) {
0589: dependencies.add(node);
0590: }
0591: }
0592: List sortedDependencies = sortEngine
0593: .sortNodes(dependencies);
0594: Collections.reverse(sortedDependencies);
0595:
0596: handleTransiviteEviction(md, confs, data,
0597: sortedDependencies);
0598:
0599: return (IvyNode[]) dependencies
0600: .toArray(new IvyNode[dependencies.size()]);
0601: } finally {
0602: IvyContext.popContext();
0603: }
0604: }
0605:
0606: private void handleTransiviteEviction(ModuleDescriptor md,
0607: String[] confs, ResolveData data, List sortedDependencies) {
0608: // handle transitive eviction now:
0609: // if a module has been evicted then all its dependencies required only by it should be
0610: // evicted too. Since nodes are now sorted from the more dependent to the less one, we
0611: // can traverse the list and check only the direct parent and not all the ancestors
0612: for (ListIterator iter = sortedDependencies.listIterator(); iter
0613: .hasNext();) {
0614: IvyNode node = (IvyNode) iter.next();
0615: if (!node.isCompletelyEvicted()) {
0616: for (int i = 0; i < confs.length; i++) {
0617: IvyNodeCallers.Caller[] callers = node
0618: .getCallers(confs[i]);
0619: if (settings.debugConflictResolution()) {
0620: Message.debug("checking if " + node.getId()
0621: + " is transitively evicted in "
0622: + confs[i]);
0623: }
0624: boolean allEvicted = callers.length > 0;
0625: for (int j = 0; j < callers.length; j++) {
0626: if (callers[j].getModuleRevisionId().equals(
0627: md.getModuleRevisionId())) {
0628: // the caller is the root module itself, it can't be evicted
0629: allEvicted = false;
0630: break;
0631: } else {
0632: IvyNode callerNode = data
0633: .getNode(callers[j]
0634: .getModuleRevisionId());
0635: if (callerNode == null) {
0636: Message
0637: .warn("ivy internal error: no node found for "
0638: + callers[j]
0639: .getModuleRevisionId()
0640: + ": looked in "
0641: + data.getNodeIds()
0642: + " and root module id was "
0643: + md
0644: .getModuleRevisionId());
0645: } else if (!callerNode.isEvicted(confs[i])) {
0646: allEvicted = false;
0647: break;
0648: } else {
0649: if (settings.debugConflictResolution()) {
0650: Message.debug("caller "
0651: + callerNode.getId()
0652: + " of " + node.getId()
0653: + " is evicted");
0654: }
0655: }
0656: }
0657: }
0658: if (allEvicted) {
0659: Message.verbose("all callers are evicted for "
0660: + node + ": evicting too");
0661: node.markEvicted(confs[i], null, null, null);
0662: } else {
0663: if (settings.debugConflictResolution()) {
0664: Message
0665: .debug(node.getId()
0666: + " isn't transitively evicted, at least one caller was"
0667: + " not evicted");
0668: }
0669: }
0670: }
0671: }
0672: }
0673: }
0674:
0675: private void fetchDependencies(VisitNode node, String conf,
0676: boolean shouldBePublic) {
0677: checkInterrupted();
0678: long start = System.currentTimeMillis();
0679: if (node.getParent() != null) {
0680: Message.verbose("== resolving dependencies "
0681: + node.getParent().getId() + "->" + node.getId()
0682: + " [" + node.getParentConf() + "->" + conf + "]");
0683: } else {
0684: Message.verbose("== resolving dependencies for "
0685: + node.getId() + " [" + conf + "]");
0686: }
0687: resolveConflict(node, conf);
0688:
0689: if (node.loadData(conf, shouldBePublic)) {
0690: // we resolve conflict again now that we have all information loaded
0691: // indeed in some cases conflict manager need more information than just asked
0692: // dependency to take the decision
0693: resolveConflict(node, conf);
0694: if (!node.isEvicted() && !node.isCircular()) {
0695: String[] confs = node.getRealConfs(conf);
0696: for (int i = 0; i < confs.length; i++) {
0697: doFetchDependencies(node, confs[i]);
0698: }
0699: }
0700: } else if (!node.hasProblem()) {
0701: // the node has not been loaded but hasn't problem: it was already loaded
0702: // => we just have to update its dependencies data
0703: if (!node.isEvicted() && !node.isCircular()) {
0704: String[] confs = node.getRealConfs(conf);
0705: for (int i = 0; i < confs.length; i++) {
0706: doFetchDependencies(node, confs[i]);
0707: }
0708: }
0709: }
0710: if (node.isEvicted()) {
0711: // update selected nodes with confs asked in evicted one
0712: EvictionData ed = node.getEvictedData();
0713: if (ed.getSelected() != null) {
0714: for (Iterator iter = ed.getSelected().iterator(); iter
0715: .hasNext();) {
0716: IvyNode selected = (IvyNode) iter.next();
0717: if (!selected.isLoaded()) {
0718: // the node is not yet loaded, we can simply update its set of
0719: // configurations to fetch
0720: selected.updateConfsToFetch(Collections
0721: .singleton(conf));
0722: } else {
0723: // the node has already been loaded, we must fetch its dependencies in the
0724: // required conf
0725: fetchDependencies(node.gotoNode(selected),
0726: conf, true);
0727: }
0728: }
0729: }
0730: }
0731: if (settings.debugConflictResolution()) {
0732: Message.debug(node.getId()
0733: + " => dependencies resolved in " + conf + " ("
0734: + (System.currentTimeMillis() - start) + "ms)");
0735: }
0736: }
0737:
0738: private void doFetchDependencies(VisitNode node, String conf) {
0739: Configuration c = node.getConfiguration(conf);
0740: if (c == null) {
0741: Message.warn("configuration not found '" + conf + "' in "
0742: + node.getResolvedId() + ": ignoring");
0743: if (node.getParent() != null) {
0744: Message.warn("it was required from "
0745: + node.getParent().getResolvedId());
0746: }
0747: return;
0748: }
0749: // we handle the case where the asked configuration extends others:
0750: // we have to first fetch the extended configurations
0751:
0752: // first we check if this is the actual requested conf (not an extended one)
0753: boolean requestedConfSet = false;
0754: if (node.getRequestedConf() == null) {
0755: node.setRequestedConf(conf);
0756: requestedConfSet = true;
0757: }
0758: // now let's recurse in extended confs
0759: String[] extendedConfs = c.getExtends();
0760: if (extendedConfs.length > 0) {
0761: node.updateConfsToFetch(Arrays.asList(extendedConfs));
0762: }
0763: for (int i = 0; i < extendedConfs.length; i++) {
0764: fetchDependencies(node, extendedConfs[i], false);
0765: }
0766:
0767: // now we can actually resolve this configuration dependencies
0768: DependencyDescriptor dd = node.getDependencyDescriptor();
0769: if (!isDependenciesFetched(node.getNode(), conf)
0770: && (dd == null || node.isTransitive())) {
0771: Collection dependencies = node.getDependencies(conf);
0772: for (Iterator iter = dependencies.iterator(); iter
0773: .hasNext();) {
0774: VisitNode dep = (VisitNode) iter.next();
0775: dep.useRealNode(); // the node may have been resolved to another real one while
0776: // resolving other deps
0777: String[] confs = dep.getRequiredConfigurations(node,
0778: conf);
0779: for (int i = 0; i < confs.length; i++) {
0780: fetchDependencies(dep, confs[i], true);
0781: }
0782: // if there are still confs to fetch (usually because they have
0783: // been updated when evicting another module), we fetch them now
0784: confs = dep.getConfsToFetch();
0785: for (int i = 0; i < confs.length; i++) {
0786: //shouldBeFixed=false to because some of those dependencies might
0787: //be private when they were actually extending public conf.
0788: //Should we keep two list of confs to fetch (private&public)?
0789: //I don't think, visibility is already checked, and a change in the
0790: //configuration between version might anyway have worse problems.
0791: fetchDependencies(dep, confs[i], false);
0792: }
0793: }
0794: }
0795: // we have finiched with this configuration, if it was the original requested conf
0796: // we can clean it now
0797: if (requestedConfSet) {
0798: node.setRequestedConf(null);
0799: }
0800:
0801: }
0802:
0803: /**
0804: * Returns true if we've already fetched the dependencies for this node and configuration
0805: *
0806: * @param node
0807: * node to check
0808: * @param conf
0809: * configuration to check
0810: * @return true if we've already fetched this dependency
0811: */
0812: private boolean isDependenciesFetched(IvyNode node, String conf) {
0813: ModuleId moduleId = node.getModuleId();
0814: ModuleRevisionId moduleRevisionId = node.getResolvedId();
0815: String key = moduleId.getOrganisation() + "|"
0816: + moduleId.getName() + "|"
0817: + moduleRevisionId.getRevision() + "|" + conf;
0818: if (fetchedSet.contains(key)) {
0819: return true;
0820: }
0821: fetchedSet.add(key);
0822: return false;
0823: }
0824:
0825: private void resolveConflict(VisitNode node, String conf) {
0826: resolveConflict(node, node.getParent(), conf,
0827: Collections.EMPTY_SET);
0828: }
0829:
0830: /**
0831: * Resolves conflict for the given node in the given ancestor. This method do conflict
0832: * resolution in ancestor parents recursively, unless not necessary.
0833: *
0834: * @param node
0835: * the node for which conflict resolution should be done
0836: * @param ancestor
0837: * the ancestor in which the conflict resolution should be done
0838: * @param toevict
0839: * a collection of IvyNode to evict (as computed by conflict resolution in
0840: * descendants of ancestor)
0841: * @return true if conflict resolution has been done, false it can't be done yet
0842: */
0843: private boolean resolveConflict(VisitNode node, VisitNode ancestor,
0844: String conf, Collection toevict) {
0845: if (ancestor == null || node == ancestor) {
0846: return true;
0847: }
0848: // check if job is not already done
0849: if (checkConflictSolvedEvicted(node, ancestor)) {
0850: // job is done and node is evicted, nothing to do
0851: return true;
0852: }
0853: if (checkConflictSolvedSelected(node, ancestor)) {
0854: // job is done and node is selected, nothing to do for this ancestor, but we still have
0855: // to check higher levels, for which conflict resolution might have been impossible
0856: // before
0857: if (resolveConflict(node, ancestor.getParent(), conf,
0858: toevict)) {
0859: // now that conflict resolution is ok in ancestors
0860: // we just have to check if the node wasn't previously evicted in root ancestor
0861: EvictionData evictionData = node.getEvictionDataInRoot(
0862: node.getRootModuleConf(), ancestor);
0863: if (evictionData != null) {
0864: // node has been previously evicted in an ancestor: we mark it as evicted
0865: if (settings.debugConflictResolution()) {
0866: Message
0867: .debug(node
0868: + " was previously evicted in root module conf "
0869: + node.getRootModuleConf());
0870: }
0871:
0872: node.markEvicted(evictionData);
0873: if (settings.debugConflictResolution()) {
0874: Message.debug("evicting " + node + " by "
0875: + evictionData);
0876: }
0877: }
0878: return true;
0879: } else {
0880: return false;
0881: }
0882: }
0883:
0884: // compute conflicts
0885: Collection resolvedNodes = new HashSet(ancestor.getNode()
0886: .getResolvedNodes(node.getModuleId(),
0887: node.getRootModuleConf()));
0888: resolvedNodes.addAll(ancestor.getNode().getPendingConflicts(
0889: node.getRootModuleConf(), node.getModuleId()));
0890: Collection conflicts = computeConflicts(node, ancestor, conf,
0891: toevict, resolvedNodes);
0892:
0893: if (settings.debugConflictResolution()) {
0894: Message.debug("found conflicting revisions for " + node
0895: + " in " + ancestor + ": " + conflicts);
0896: }
0897:
0898: ConflictManager conflictManager = ancestor.getNode()
0899: .getConflictManager(node.getModuleId());
0900: Collection resolved = conflictManager.resolveConflicts(ancestor
0901: .getNode(), conflicts);
0902:
0903: if (resolved == null) {
0904: if (settings.debugConflictResolution()) {
0905: Message.debug("impossible to resolve conflicts for "
0906: + node + " in " + ancestor + " yet");
0907: Message
0908: .debug("setting all nodes as pending conflicts for later conflict"
0909: + " resolution: " + conflicts);
0910: }
0911: ancestor.getNode().setPendingConflicts(node.getModuleId(),
0912: node.getRootModuleConf(), conflicts);
0913: return false;
0914: }
0915:
0916: if (settings.debugConflictResolution()) {
0917: Message.debug("selected revisions for " + node + " in "
0918: + ancestor + ": " + resolved);
0919: }
0920: if (resolved.contains(node.getNode())) {
0921: // node has been selected for the current parent
0922:
0923: // handle previously selected nodes that are now evicted by this new node
0924: toevict = resolvedNodes;
0925: toevict.removeAll(resolved);
0926:
0927: for (Iterator iter = toevict.iterator(); iter.hasNext();) {
0928: IvyNode te = (IvyNode) iter.next();
0929: te.markEvicted(node.getRootModuleConf(), ancestor
0930: .getNode(), conflictManager, resolved);
0931:
0932: if (settings.debugConflictResolution()) {
0933: Message.debug("evicting "
0934: + te
0935: + " by "
0936: + te.getEvictedData(node
0937: .getRootModuleConf()));
0938: }
0939: }
0940:
0941: // it's very important to update resolved and evicted nodes BEFORE recompute parent call
0942: // to allow it to recompute its resolved collection with correct data
0943: // if necessary
0944: ancestor.getNode().setResolvedNodes(node.getModuleId(),
0945: node.getRootModuleConf(), resolved);
0946:
0947: Collection evicted = new HashSet(ancestor.getNode()
0948: .getEvictedNodes(node.getModuleId(),
0949: node.getRootModuleConf()));
0950: evicted.removeAll(resolved);
0951: evicted.addAll(toevict);
0952: ancestor.getNode().setEvictedNodes(node.getModuleId(),
0953: node.getRootModuleConf(), evicted);
0954: ancestor.getNode().setPendingConflicts(node.getModuleId(),
0955: node.getRootModuleConf(), Collections.EMPTY_SET);
0956:
0957: return resolveConflict(node, ancestor.getParent(), conf,
0958: toevict);
0959: } else {
0960: // node has been evicted for the current parent
0961: if (resolved.isEmpty()) {
0962: if (settings.debugConflictResolution()) {
0963: Message.verbose("conflict manager '"
0964: + conflictManager
0965: + "' evicted all revisions among "
0966: + conflicts);
0967: }
0968: }
0969:
0970: // it's time to update parent resolved and evicted with what was found
0971:
0972: Collection evicted = new HashSet(ancestor.getNode()
0973: .getEvictedNodes(node.getModuleId(),
0974: node.getRootModuleConf()));
0975: toevict.removeAll(resolved);
0976: evicted.removeAll(resolved);
0977: evicted.addAll(toevict);
0978: evicted.add(node.getNode());
0979: ancestor.getNode().setEvictedNodes(node.getModuleId(),
0980: node.getRootModuleConf(), evicted);
0981: ancestor.getNode().setPendingConflicts(node.getModuleId(),
0982: node.getRootModuleConf(), Collections.EMPTY_SET);
0983:
0984: node.markEvicted(ancestor, conflictManager, resolved);
0985: if (settings.debugConflictResolution()) {
0986: Message.debug("evicting " + node + " by "
0987: + node.getEvictedData());
0988: }
0989:
0990: // if resolved changed we have to go up in the graph
0991: Collection prevResolved = ancestor.getNode()
0992: .getResolvedNodes(node.getModuleId(),
0993: node.getRootModuleConf());
0994: boolean solved = true;
0995: if (!prevResolved.equals(resolved)) {
0996: ancestor.getNode().setResolvedNodes(node.getModuleId(),
0997: node.getRootModuleConf(), resolved);
0998: for (Iterator iter = resolved.iterator(); iter
0999: .hasNext();) {
1000: IvyNode sel = (IvyNode) iter.next();
1001: if (!prevResolved.contains(sel)) {
1002: solved &= resolveConflict(node.gotoNode(sel),
1003: ancestor.getParent(), conf, toevict);
1004: }
1005: }
1006: }
1007: return solved;
1008: }
1009: }
1010:
1011: private Collection computeConflicts(VisitNode node,
1012: VisitNode ancestor, String conf, Collection toevict,
1013: Collection resolvedNodes) {
1014: Collection conflicts = new LinkedHashSet();
1015: conflicts.add(node.getNode());
1016: if (resolvedNodes.removeAll(toevict)) {
1017: // parent.resolved(node.mid) is not up to date:
1018: // recompute resolved from all sub nodes
1019: Collection deps = ancestor.getNode().getDependencies(
1020: node.getRootModuleConf(),
1021: ancestor.getRequiredConfigurations());
1022: for (Iterator iter = deps.iterator(); iter.hasNext();) {
1023: IvyNode dep = (IvyNode) iter.next();
1024: if (dep.getModuleId().equals(node.getModuleId())) {
1025: conflicts.add(dep);
1026: }
1027: conflicts.addAll(dep.getResolvedNodes(node
1028: .getModuleId(), node.getRootModuleConf()));
1029: }
1030: } else if (resolvedNodes.isEmpty()
1031: && node.getParent() != ancestor) {
1032: //Conflict must only be computed per root configuration at this step.
1033: Collection ancestorDepIvyNodes = node.getParent().getNode()
1034: .getDependencies(node.getRootModuleConf(),
1035: new String[] { node.getParentConf() });
1036: for (Iterator it = ancestorDepIvyNodes.iterator(); it
1037: .hasNext();) {
1038: IvyNode ancestorDep = (IvyNode) it.next();
1039: if (ancestorDep.getModuleId()
1040: .equals(node.getModuleId())) {
1041: conflicts.add(ancestorDep);
1042: }
1043: }
1044: } else {
1045: conflicts.addAll(resolvedNodes);
1046: }
1047: return conflicts;
1048: }
1049:
1050: private boolean checkConflictSolvedSelected(VisitNode node,
1051: VisitNode ancestor) {
1052: if (ancestor.getResolvedRevisions(node.getModuleId()).contains(
1053: node.getResolvedId())) {
1054: // resolve conflict has already be done with node with the same id
1055: if (settings.debugConflictResolution()) {
1056: Message.debug("conflict resolution already done for "
1057: + node + " in " + ancestor);
1058: }
1059: return true;
1060: }
1061: return false;
1062: }
1063:
1064: private boolean checkConflictSolvedEvicted(VisitNode node,
1065: VisitNode ancestor) {
1066: if (ancestor.getEvictedRevisions(node.getModuleId()).contains(
1067: node.getResolvedId())) {
1068: // resolve conflict has already be done with node with the same id
1069: if (settings.debugConflictResolution()) {
1070: Message.debug("conflict resolution already done for "
1071: + node + " in " + ancestor);
1072: }
1073: return true;
1074: }
1075: return false;
1076: }
1077:
1078: public ResolvedModuleRevision findModule(ModuleRevisionId id,
1079: ResolveOptions options) {
1080: DependencyResolver r = settings.getResolver(id);
1081: if (r == null) {
1082: throw new IllegalStateException("no resolver found for "
1083: + id.getModuleId());
1084: }
1085: DefaultModuleDescriptor md = DefaultModuleDescriptor
1086: .newCallerInstance(id, new String[] { "*" }, false,
1087: false);
1088:
1089: if (options.getResolveId() == null) {
1090: options
1091: .setResolveId(ResolveOptions
1092: .getDefaultResolveId(md));
1093: }
1094:
1095: try {
1096: return r.getDependency(new DefaultDependencyDescriptor(id,
1097: true), new ResolveData(this , options,
1098: new ConfigurationResolveReport(this , md, "default",
1099: null, options)));
1100: } catch (ParseException e) {
1101: throw new RuntimeException(
1102: "problem while parsing repository module descriptor for "
1103: + id + ": " + e, e);
1104: }
1105: }
1106:
1107: public EventManager getEventManager() {
1108: return eventManager;
1109: }
1110:
1111: public ResolveEngineSettings getSettings() {
1112: return settings;
1113: }
1114:
1115: public SortEngine getSortEngine() {
1116: return sortEngine;
1117: }
1118:
1119: private void checkInterrupted() {
1120: IvyContext.getContext().getIvy().checkInterrupted();
1121: }
1122:
1123: }
|