| /* |
| * 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.core.resolve; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.net.URL; |
| import java.text.ParseException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| |
| import org.apache.ivy.Ivy; |
| import org.apache.ivy.core.IvyContext; |
| import org.apache.ivy.core.LogOptions; |
| import org.apache.ivy.core.cache.ArtifactOrigin; |
| import org.apache.ivy.core.cache.DefaultResolutionCacheManager; |
| import org.apache.ivy.core.cache.ResolutionCacheManager; |
| import org.apache.ivy.core.event.EventManager; |
| import org.apache.ivy.core.event.download.PrepareDownloadEvent; |
| import org.apache.ivy.core.event.resolve.EndResolveEvent; |
| import org.apache.ivy.core.event.resolve.StartResolveEvent; |
| import org.apache.ivy.core.module.descriptor.Artifact; |
| import org.apache.ivy.core.module.descriptor.Configuration; |
| import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor; |
| import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor; |
| 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.module.id.ModuleRevisionId; |
| import org.apache.ivy.core.report.ArtifactDownloadReport; |
| import org.apache.ivy.core.report.ConfigurationResolveReport; |
| import org.apache.ivy.core.report.DownloadReport; |
| import org.apache.ivy.core.report.DownloadStatus; |
| import org.apache.ivy.core.report.ResolveReport; |
| import org.apache.ivy.core.resolve.IvyNodeEviction.EvictionData; |
| import org.apache.ivy.core.settings.IvySettings; |
| import org.apache.ivy.core.sort.SortEngine; |
| import org.apache.ivy.core.sort.SortOptions; |
| import org.apache.ivy.plugins.conflict.ConflictManager; |
| import org.apache.ivy.plugins.parser.ModuleDescriptorParser; |
| import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry; |
| import org.apache.ivy.plugins.repository.url.URLResource; |
| import org.apache.ivy.plugins.resolver.DependencyResolver; |
| import org.apache.ivy.plugins.version.VersionMatcher; |
| import org.apache.ivy.util.Message; |
| import org.apache.ivy.util.filter.Filter; |
| |
| /** |
| * The resolve engine which is the core of the dependency resolution mechanism used in Ivy. It |
| * features several resolve methods, some very simple, like {@link #resolve(File)} and |
| * {@link #resolve(URL)} which allow to simply resolve dependencies of a single module descriptor, |
| * or more complete one, like the {@link #resolve(ModuleDescriptor, ResolveOptions)} which allows to |
| * provide options to the resolution engine. |
| * |
| * @see ResolveOptions |
| */ |
| public class ResolveEngine { |
| private ResolveEngineSettings settings; |
| |
| private EventManager eventManager; |
| |
| private SortEngine sortEngine; |
| |
| private DependencyResolver dictatorResolver; |
| |
| /** |
| * Constructs a ResolveEngine. |
| * |
| * @param settings |
| * the settings to use to configure the engine. Must not be null. |
| * @param eventManager |
| * the event manager to use to send events about the resolution process. Must not be |
| * null. |
| * @param sortEngine |
| * the sort engine to use to sort modules before producing the dependency resolution |
| * report. Must not be null. |
| */ |
| public ResolveEngine(ResolveEngineSettings settings, EventManager eventManager, |
| SortEngine sortEngine) { |
| this.settings = settings; |
| this.eventManager = eventManager; |
| this.sortEngine = sortEngine; |
| } |
| |
| /** |
| * Returns the currently configured dictator resolver, which when non null is used in place of |
| * any specified resolver in the {@link IvySettings} |
| * |
| * @return the currently configured dictator resolver, may be null. |
| */ |
| public DependencyResolver getDictatorResolver() { |
| return dictatorResolver; |
| } |
| |
| /** |
| * Sets a dictator resolver, which is used in place of regular dependency resolver for |
| * subsequent dependency resolution by this engine. |
| * |
| * @param dictatorResolver |
| * the dictator resolver to use in this engine, null if regular settings should used |
| */ |
| public void setDictatorResolver(DependencyResolver dictatorResolver) { |
| this.dictatorResolver = dictatorResolver; |
| } |
| |
| public ResolveReport resolve(File ivySource) throws ParseException, IOException { |
| return resolve(ivySource.toURI().toURL()); |
| } |
| |
| public ResolveReport resolve(URL ivySource) throws ParseException, IOException { |
| return resolve(ivySource, new ResolveOptions()); |
| } |
| |
| /** |
| * Resolves the module identified by the given mrid with its dependencies if transitive is set |
| * to true. |
| * |
| * @param mrid ModuleRevisionId |
| * @param options ResolveOptions |
| * @param changing boolean |
| * @return ResolveReport |
| * @throws ParseException if something goes wrong |
| * @throws IOException if something goes wrong |
| */ |
| public ResolveReport resolve(final ModuleRevisionId mrid, ResolveOptions options, |
| boolean changing) throws ParseException, IOException { |
| DefaultModuleDescriptor md; |
| ResolveOptions optionsToUse = new ResolveOptions(options); |
| |
| if (options.useSpecialConfs()) { |
| // create new resolve options because this is a different resolve than the real resolve |
| // (which will be a resolve of a newCallerInstance module) |
| ResolvedModuleRevision rmr = findModule(mrid, new ResolveOptions(options)); |
| if (rmr == null) { |
| Message.verbose("module not found " + mrid); |
| |
| // we will continue the resolve anyway to get a nice error message back |
| // to the user, however reduce the amount of logging in this case |
| optionsToUse.setLog(LogOptions.LOG_DOWNLOAD_ONLY); |
| md = DefaultModuleDescriptor.newCallerInstance(mrid, new String[] {"default"}, |
| options.isTransitive(), changing); |
| } else { |
| String[] confs = options.getConfs(rmr.getDescriptor()); |
| md = DefaultModuleDescriptor.newCallerInstance( |
| ModuleRevisionId.newInstance(mrid, rmr.getId().getRevision()), confs, |
| options.isTransitive(), changing); |
| } |
| } else { |
| md = DefaultModuleDescriptor.newCallerInstance(mrid, options.getConfs(), |
| options.isTransitive(), changing); |
| } |
| |
| return resolve(md, optionsToUse); |
| } |
| |
| /** |
| * Resolve dependencies of a module described by an ivy file. |
| * |
| * @param ivySource URL |
| * @param options ResolveOptions |
| * @return ResolveReport |
| * @throws ParseException if something goes wrong |
| * @throws IOException if something goes wrong |
| */ |
| public ResolveReport resolve(URL ivySource, ResolveOptions options) throws ParseException, |
| IOException { |
| URLResource res = new URLResource(ivySource); |
| ModuleDescriptorParser parser = ModuleDescriptorParserRegistry.getInstance().getParser(res); |
| Message.verbose("using " + parser + " to parse " + ivySource); |
| ModuleDescriptor md = parser.parseDescriptor(settings, ivySource, options.isValidate()); |
| String revision = options.getRevision(); |
| if (revision == null && md.getResolvedModuleRevisionId().getRevision() == null) { |
| revision = Ivy.getWorkingRevision(); |
| } |
| if (revision != null) { |
| md.setResolvedModuleRevisionId(ModuleRevisionId.newInstance(md.getModuleRevisionId(), |
| revision)); |
| } |
| |
| return resolve(md, options); |
| } |
| |
| /** |
| * Resolve dependencies of a module described by a module descriptor. |
| * @param md ModuleDescriptor |
| * @param options ResolveOptions |
| * @return ResolveReport |
| * @throws ParseException if something goes wrong |
| * @throws IOException if something goes wrong |
| */ |
| public ResolveReport resolve(ModuleDescriptor md, ResolveOptions options) |
| throws ParseException, IOException { |
| DependencyResolver oldDictator = getDictatorResolver(); |
| IvyContext context = IvyContext.getContext(); |
| try { |
| String[] confs = options.getConfs(md); |
| options.setConfs(confs); |
| |
| if (options.getResolveId() == null) { |
| options.setResolveId(ResolveOptions.getDefaultResolveId(md)); |
| } |
| |
| eventManager.fireIvyEvent(new StartResolveEvent(md, confs)); |
| |
| long start = System.currentTimeMillis(); |
| if (ResolveOptions.LOG_DEFAULT.equals(options.getLog())) { |
| Message.info(":: resolving dependencies :: " + md.getResolvedModuleRevisionId() |
| + (options.isTransitive() ? "" : " [not transitive]")); |
| Message.info("\tconfs: " + Arrays.asList(confs)); |
| } else { |
| Message.verbose(":: resolving dependencies :: " + md.getResolvedModuleRevisionId() |
| + (options.isTransitive() ? "" : " [not transitive]")); |
| Message.verbose("\tconfs: " + Arrays.asList(confs)); |
| } |
| Message.verbose("\tvalidate = " + options.isValidate()); |
| Message.verbose("\trefresh = " + options.isRefresh()); |
| |
| ResolveReport report = new ResolveReport(md, options.getResolveId()); |
| |
| ResolveData data = new ResolveData(this, options); |
| context.setResolveData(data); |
| |
| // resolve dependencies |
| IvyNode[] dependencies = getDependencies(md, options, report); |
| report.setDependencies(Arrays.asList(dependencies), options.getArtifactFilter()); |
| |
| if (options.getCheckIfChanged()) { |
| report.checkIfChanged(); |
| } |
| |
| // produce resolved ivy file and ivy properties in cache |
| ResolutionCacheManager cacheManager = settings.getResolutionCacheManager(); |
| cacheManager.saveResolvedModuleDescriptor(md); |
| |
| // we store the resolved dependencies revisions and statuses per asked dependency |
| // revision id, for direct dependencies only. |
| // this is used by the deliver task to resolve dynamic revisions to static ones |
| File ivyPropertiesInCache = cacheManager.getResolvedIvyPropertiesInCache(md |
| .getResolvedModuleRevisionId()); |
| if (cacheManager instanceof DefaultResolutionCacheManager) { |
| ((DefaultResolutionCacheManager) cacheManager).assertInsideCache(ivyPropertiesInCache); |
| } |
| Properties props = new Properties(); |
| if (dependencies.length > 0) { |
| Map<ModuleId, ModuleRevisionId> forcedRevisions = new HashMap<>(); |
| for (IvyNode dependency : dependencies) { |
| if (dependency.getModuleRevision() != null |
| && dependency.getModuleRevision().isForce()) { |
| forcedRevisions.put(dependency.getModuleId(), |
| dependency.getResolvedId()); |
| } |
| } |
| |
| IvyNode root = dependencies[0].getRoot(); |
| |
| Map<ModuleId, IvyNode> topLevelDeps = new HashMap<>(); |
| for (IvyNode dependency : dependencies) { |
| if (!dependency.hasProblem()) { |
| DependencyDescriptor dd = dependency.getDependencyDescriptor(root); |
| if (dd != null) { |
| ModuleId orgMod = dependency.getModuleId(); |
| topLevelDeps.put(orgMod, dependency); |
| } |
| } |
| } |
| |
| for (IvyNode dependency : dependencies) { |
| if (!dependency.hasProblem() && !dependency.isCompletelyEvicted()) { |
| DependencyDescriptor dd = dependency.getDependencyDescriptor(root); |
| if (dd == null) { |
| ModuleId mid = dependency.getModuleId(); |
| IvyNode tlDep = topLevelDeps.get(mid); |
| if (tlDep != null) { |
| dd = tlDep.getDependencyDescriptor(root); |
| } |
| } |
| if (dd != null) { |
| ModuleRevisionId depResolvedId = dependency.getResolvedId(); |
| ModuleDescriptor depDescriptor = dependency.getDescriptor(); |
| ModuleRevisionId depRevisionId = dd.getDependencyRevisionId(); |
| ModuleRevisionId forcedRevisionId = forcedRevisions.get(dependency |
| .getModuleId()); |
| |
| if (dependency.getModuleRevision() != null |
| && dependency.getModuleRevision().isForce() |
| && !depResolvedId.equals(depRevisionId) |
| && !settings.getVersionMatcher().isDynamic(depRevisionId)) { |
| // if we were forced to this revision and we |
| // are not a dynamic revision, reset to the |
| // asked revision |
| depResolvedId = depRevisionId; |
| depDescriptor = null; |
| } |
| |
| if (depResolvedId == null) { |
| throw new NullPointerException("getResolvedId() is null for " |
| + dependency.toString()); |
| } |
| if (depRevisionId == null) { |
| throw new NullPointerException("getDependencyRevisionId() " |
| + "is null for " + dd.toString()); |
| } |
| String rev = depResolvedId.getRevision(); |
| String forcedRev = forcedRevisionId == null ? rev : forcedRevisionId |
| .getRevision(); |
| |
| // The evicted modules have no description, so we can't put the status |
| String status = depDescriptor == null ? "?" : depDescriptor.getStatus(); |
| Message.debug("storing dependency " + depResolvedId + " in props"); |
| props.put(depRevisionId.encodeToString(), rev + " " + status + " " |
| + forcedRev + " " + depResolvedId.getBranch()); |
| } |
| } |
| } |
| } |
| FileOutputStream out = new FileOutputStream(ivyPropertiesInCache); |
| props.store(out, md.getResolvedModuleRevisionId() + " resolved revisions"); |
| out.close(); |
| Message.verbose("\tresolved ivy file produced in cache"); |
| |
| report.setResolveTime(System.currentTimeMillis() - start); |
| |
| if (options.isDownload()) { |
| Message.verbose(":: downloading artifacts ::"); |
| |
| DownloadOptions downloadOptions = new DownloadOptions(); |
| downloadOptions.setLog(options.getLog()); |
| downloadArtifacts(report, options.getArtifactFilter(), downloadOptions); |
| } |
| |
| if (options.isOutputReport()) { |
| outputReport(report, cacheManager, options); |
| } |
| |
| Message.verbose("\tresolve done (" + report.getResolveTime() + "ms resolve - " |
| + report.getDownloadTime() + "ms download)"); |
| Message.sumupProblems(); |
| |
| eventManager.fireIvyEvent(new EndResolveEvent(md, confs, report)); |
| return report; |
| } catch (RuntimeException ex) { |
| Message.debug(ex); |
| Message.error(ex.getMessage()); |
| Message.sumupProblems(); |
| throw ex; |
| } finally { |
| context.setResolveData(null); |
| setDictatorResolver(oldDictator); |
| } |
| } |
| |
| public void outputReport(ResolveReport report, ResolutionCacheManager cacheMgr, |
| ResolveOptions options) throws IOException { |
| if (ResolveOptions.LOG_DEFAULT.equals(options.getLog())) { |
| Message.info(":: resolution report :: resolve " + report.getResolveTime() + "ms" |
| + " :: artifacts dl " + report.getDownloadTime() + "ms"); |
| } else { |
| Message.verbose(":: resolution report :: resolve " + report.getResolveTime() + "ms" |
| + " :: artifacts dl " + report.getDownloadTime() + "ms"); |
| } |
| report.setProblemMessages(Message.getProblems()); |
| // output report |
| report.output(settings.getReportOutputters(), cacheMgr, options); |
| } |
| |
| public void downloadArtifacts(ResolveReport report, Filter<Artifact> artifactFilter, |
| DownloadOptions options) { |
| long start = System.currentTimeMillis(); |
| |
| eventManager.fireIvyEvent(new PrepareDownloadEvent(report.getArtifacts().toArray( |
| new Artifact[report.getArtifacts().size()]))); |
| |
| long totalSize = 0; |
| for (IvyNode dependency : report.getDependencies()) { |
| checkInterrupted(); |
| // download artifacts required in all asked configurations |
| if (!dependency.isCompletelyEvicted() && !dependency.hasProblem() |
| && dependency.getModuleRevision() != null) { |
| DependencyResolver resolver = dependency.getModuleRevision() |
| .getArtifactResolver(); |
| Artifact[] selectedArtifacts = dependency.getSelectedArtifacts(artifactFilter); |
| DownloadReport dReport = resolver.download(selectedArtifacts, options); |
| for (ArtifactDownloadReport adr : dReport.getArtifactsReports()) { |
| if (adr.getDownloadStatus() == DownloadStatus.FAILED) { |
| if (adr.getArtifact().getExtraAttribute("ivy:merged") != null) { |
| Message.warn("\tmerged artifact not found: " + adr.getArtifact() |
| + ". It was required in " |
| + adr.getArtifact().getExtraAttribute("ivy:merged")); |
| } else { |
| Message.warn("\t" + adr); |
| resolver.reportFailure(adr.getArtifact()); |
| } |
| } else if (adr.getDownloadStatus() == DownloadStatus.SUCCESSFUL) { |
| totalSize += adr.getSize(); |
| } |
| } |
| // update concerned reports |
| for (String dconf : dependency.getRootModuleConfigurations()) { |
| // the report itself is responsible to take into account only |
| // artifacts required in its corresponding configuration |
| // (as described by the Dependency object) |
| if (dependency.isEvicted(dconf) |
| || dependency.isBlacklisted(dconf)) { |
| report.getConfigurationReport(dconf).addDependency(dependency); |
| } else { |
| report.getConfigurationReport(dconf).addDependency(dependency, |
| dReport); |
| } |
| } |
| } |
| } |
| report.setDownloadTime(System.currentTimeMillis() - start); |
| report.setDownloadSize(totalSize); |
| } |
| |
| /** |
| * Download an artifact to the cache. Not used internally, useful especially for IDE plugins |
| * needing to download artifact one by one (for source or javadoc artifact, for instance). |
| * <p> |
| * Downloaded artifact file can be accessed using {@link ArtifactDownloadReport#getLocalFile()}. |
| * </p> |
| * <p> |
| * It is possible to track the progression of the download using classical ivy progress |
| * monitoring feature (see addTransferListener). |
| * </p> |
| * |
| * @param artifact |
| * the artifact to download |
| * @param options DownloadOptions |
| * @return a report concerning the download |
| * @see #download(ArtifactOrigin, DownloadOptions) |
| */ |
| public ArtifactDownloadReport download(Artifact artifact, DownloadOptions options) { |
| DependencyResolver resolver = settings.getResolver(artifact.getModuleRevisionId()); |
| DownloadReport r = resolver.download(new Artifact[] {artifact}, options); |
| return r.getArtifactReport(artifact); |
| } |
| |
| /** |
| * Locates an artifact in dependency resolvers, and return its location if it can be located and |
| * actually exists, or an unknown {@link ArtifactOrigin} in other cases. |
| * |
| * @param artifact |
| * the artifact to locate. |
| * @return the artifact location, should be tested with |
| * {@link ArtifactOrigin#isUnknown(ArtifactOrigin)} to check if the artifact has |
| * actually been located. |
| */ |
| public ArtifactOrigin locate(Artifact artifact) { |
| DependencyResolver resolver = settings.getResolver(artifact.getModuleRevisionId()); |
| return resolver.locate(artifact); |
| } |
| |
| /** |
| * Materialize an artifact already located. |
| * <p> |
| * Not used internally, useful especially for IDE plugins needing to download artifact one by |
| * one (for source or javadoc artifact, for instance). |
| * </p> |
| * <p> |
| * Materialized artifact file can be accessed using |
| * {@link ArtifactDownloadReport#getLocalFile()}. |
| * </p> |
| * <p> |
| * It is possible to track the progression of the download using classical ivy progress |
| * monitoring feature (see addTransferListener). |
| * </p> |
| * |
| * @param origin |
| * the artifact origin to materialize |
| * @param options DownloadOptions |
| * @return a report concerning the download |
| * @see #download(Artifact, DownloadOptions) |
| * @see #locate(Artifact) |
| */ |
| public ArtifactDownloadReport download(ArtifactOrigin origin, DownloadOptions options) { |
| DependencyResolver resolver = settings.getResolver(origin.getArtifact() |
| .getModuleRevisionId()); |
| return resolver.download(origin, options); |
| } |
| |
| /** |
| * Resolve the dependencies of a module without downloading corresponding artifacts. The module |
| * to resolve is given by its ivy file URL. This method requires appropriate configuration of |
| * the ivy instance, especially resolvers. |
| * |
| * @param ivySource |
| * url of the ivy file to use for dependency resolving |
| * @param options |
| * ditto |
| * @return an array of the resolved dependencies |
| * @throws ParseException |
| * if a parsing problem occurred in the ivy file |
| * @throws IOException |
| * if an IO problem was raised during ivy file parsing |
| */ |
| public IvyNode[] getDependencies(URL ivySource, ResolveOptions options) throws ParseException, |
| IOException { |
| return getDependencies( |
| ModuleDescriptorParserRegistry.getInstance().parseDescriptor(settings, ivySource, |
| options.isValidate()), options, null); |
| } |
| |
| /** |
| * Resolve the dependencies of a module without downloading corresponding artifacts. The module |
| * to resolve is given by its module descriptor. This method requires appropriate configuration |
| * of the ivy instance, especially resolvers. |
| * <p> |
| * The <code>IvyNode</code>s are ordered from the most dependent to the less dependent, so that |
| * an IvyNode is always found in the list after all IvyNode depending directly on it. |
| * |
| * @param md |
| * the descriptor of the module for which we want to get dependencies - must not be |
| * null |
| * @param options |
| * the resolve options to use to resolve the dependencies |
| * @param report |
| * a resolve report to fill during resolution - may be null |
| * @return an array of the resolved Dependencies |
| */ |
| public IvyNode[] getDependencies(ModuleDescriptor md, ResolveOptions options, |
| ResolveReport report) { |
| // check parameters |
| if (md == null) { |
| throw new NullPointerException("module descriptor must not be null"); |
| } |
| String[] confs = options.getConfs(md); |
| Collection<String> missingConfs = new ArrayList<>(); |
| for (String conf : confs) { |
| if (conf == null) { |
| throw new NullPointerException("null conf not allowed: confs where: " |
| + Arrays.asList(confs)); |
| } |
| |
| if (md.getConfiguration(conf) == null) { |
| missingConfs.add(" '" + conf + "' "); |
| } |
| } |
| if (!missingConfs.isEmpty()) { |
| throw new IllegalArgumentException("requested configuration" |
| + (missingConfs.size() > 1 ? "s" : "") + " not found in " |
| + md.getModuleRevisionId() + ": " + missingConfs); |
| } |
| |
| IvyContext context = IvyContext.pushNewCopyContext(); |
| try { |
| options.setConfs(confs); |
| |
| Date reportDate = new Date(); |
| ResolveData data = context.getResolveData(); |
| if (data == null) { |
| data = new ResolveData(this, options); |
| context.setResolveData(data); |
| } |
| IvyNode rootNode = new IvyNode(data, md); |
| |
| for (String conf : confs) { |
| Message.verbose("resolving dependencies for configuration '" + conf + "'"); |
| |
| ConfigurationResolveReport confReport = null; |
| if (report != null) { |
| confReport = report.getConfigurationReport(conf); |
| if (confReport == null) { |
| confReport = new ConfigurationResolveReport(this, md, conf, reportDate, |
| options); |
| report.addReport(conf, confReport); |
| } |
| } |
| // we reuse the same resolve data with a new report for each conf |
| data.setReport(confReport); |
| |
| // update the root module conf we are about to fetch |
| VisitNode root = new VisitNode(data, rootNode, null, conf, null); |
| root.setRequestedConf(conf); |
| rootNode.updateConfsToFetch(Collections.singleton(conf)); |
| |
| // go fetch ! |
| boolean fetched = false; |
| while (!fetched) { |
| try { |
| fetchDependencies(root, conf, new HashSet<String>(), false); |
| fetched = true; |
| } catch (RestartResolveProcess restart) { |
| Message.verbose("===================================================="); |
| Message.verbose("= RESTARTING RESOLVE PROCESS"); |
| Message.verbose("= " + restart.getMessage()); |
| Message.verbose("===================================================="); |
| } |
| } |
| |
| // clean data |
| for (IvyNode dep : data.getNodes()) { |
| dep.clean(); |
| } |
| } |
| |
| // prune and reverse sort fetched dependencies |
| Collection<IvyNode> nodes = data.getNodes(); |
| // use a Set to avoid duplicates, linked to preserve order |
| Collection<IvyNode> dependencies = new LinkedHashSet<>(nodes.size()); |
| for (IvyNode node : nodes) { |
| if (node != null && !node.isRoot() && !node.isCompletelyBlacklisted()) { |
| dependencies.add(node); |
| } |
| } |
| List<IvyNode> sortedDependencies = sortEngine.sortNodes(dependencies, |
| SortOptions.SILENT); |
| Collections.reverse(sortedDependencies); |
| |
| handleTransitiveEviction(md, confs, data, sortedDependencies); |
| |
| return dependencies.toArray(new IvyNode[dependencies.size()]); |
| } finally { |
| IvyContext.popContext(); |
| } |
| } |
| |
| private void handleTransitiveEviction(ModuleDescriptor md, String[] confs, ResolveData data, |
| List<IvyNode> sortedDependencies) { |
| // handle transitive eviction now: |
| // if a module has been evicted then all its dependencies required only by it should be |
| // evicted too. Since nodes are now sorted from the more dependent to the less one, we |
| // can traverse the list and check only the direct parent and not all the ancestors |
| for (IvyNode node : sortedDependencies) { |
| if (!node.isCompletelyEvicted()) { |
| for (String conf : confs) { |
| IvyNodeCallers.Caller[] callers = node.getCallers(conf); |
| if (settings.debugConflictResolution()) { |
| Message.debug("checking if " + node.getId() |
| + " is transitively evicted in " + conf); |
| } |
| boolean allEvicted = callers.length > 0; |
| for (IvyNodeCallers.Caller caller : callers) { |
| if (caller.getModuleRevisionId().equals(md.getModuleRevisionId())) { |
| // the caller is the root module itself, it can't be evicted |
| allEvicted = false; |
| break; |
| } else { |
| IvyNode callerNode = data.getNode(caller.getModuleRevisionId()); |
| if (callerNode == null) { |
| Message.warn("ivy internal error: no node found for " |
| + caller.getModuleRevisionId() + ": looked in " |
| + data.getNodeIds() + " and root module id was " |
| + md.getModuleRevisionId()); |
| } else if (!callerNode.isEvicted(conf)) { |
| allEvicted = false; |
| break; |
| } else { |
| if (settings.debugConflictResolution()) { |
| Message.debug("caller " + callerNode.getId() + " of " |
| + node.getId() + " is evicted"); |
| } |
| } |
| } |
| } |
| if (allEvicted) { |
| Message.verbose("all callers are evicted for " + node + ": evicting too"); |
| node.markEvicted(conf, null, null, null); |
| } else { |
| if (settings.debugConflictResolution()) { |
| Message.debug(node.getId() |
| + " isn't transitively evicted, at least one caller was" |
| + " not evicted"); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void fetchDependencies(VisitNode node, String conf, Set<String> fetchedSet, boolean shouldBePublic) { |
| checkInterrupted(); |
| long start = System.currentTimeMillis(); |
| if (node.getParent() != null) { |
| Message.verbose("== resolving dependencies " + node.getParent().getId() + "->" |
| + node.getId() + " [" + node.getParentConf() + "->" + conf + "]"); |
| } else { |
| Message.verbose("== resolving dependencies for " + node.getId() + " [" + conf + "]"); |
| } |
| ResolveData data = node.getNode().getData(); |
| VisitNode parentVisitNode = data.getCurrentVisitNode(); |
| |
| data.setCurrentVisitNode(node); |
| DependencyDescriptor dd = node.getDependencyDescriptor(); |
| VersionMatcher versionMatcher = node.getNode().getData().getSettings().getVersionMatcher(); |
| if (dd != null && !(node.getRoot() == node.getParent() |
| && versionMatcher.isDynamic(dd.getDependencyRevisionId()))) { |
| /* |
| * we don't resolve conflicts before loading data for direct dependencies on dynamic |
| * revisions, so that direct dynamic revisions are always resolved, which is mandatory |
| * for proper replacement of dynamic revisions during 'deliver' |
| */ |
| resolveConflict(node, conf); |
| } |
| |
| if (node.loadData(conf, shouldBePublic)) { |
| // we resolve conflict again now that we have all information loaded |
| // indeed in some cases conflict manager need more information than just asked |
| // dependency to take the decision |
| resolveConflict(node, conf); |
| if (!node.isEvicted() && !node.isCircular()) { |
| for (String rconf : node.getRealConfs(conf)) { |
| doFetchDependencies(node, rconf, fetchedSet); |
| } |
| } |
| } else if (!node.hasProblem()) { |
| // the node has not been loaded but hasn't problem: it was already loaded |
| // => we just have to update its dependencies data |
| if (!node.isEvicted() && !node.isCircular()) { |
| for (String rconf : node.getRealConfs(conf)) { |
| doFetchDependencies(node, rconf, fetchedSet); |
| } |
| } |
| } |
| if (node.isEvicted()) { |
| // update selected nodes with confs asked in evicted one |
| EvictionData ed = node.getEvictedData(); |
| if (ed.getSelected() != null) { |
| for (IvyNode selected : ed.getSelected()) { |
| if (!selected.isLoaded()) { |
| // the node is not yet loaded, we can simply update its set of |
| // configurations to fetch |
| selected.updateConfsToFetch(Collections.singleton(conf)); |
| } else { |
| // the node has already been loaded, we must fetch its dependencies in the |
| // required conf |
| fetchDependencies(node.gotoNode(selected), conf, fetchedSet, true); |
| } |
| } |
| } |
| } |
| if (settings.debugConflictResolution()) { |
| Message.debug(node.getId() + " => dependencies resolved in " + conf + " (" |
| + (System.currentTimeMillis() - start) + "ms)"); |
| } |
| data.setCurrentVisitNode(parentVisitNode); |
| } |
| |
| private void doFetchDependencies(VisitNode node, String conf, Set<String> fetchedSet) { |
| Configuration c = node.getConfiguration(conf); |
| if (c == null) { |
| if (!node.isConfRequiredByMergedUsageOnly(conf)) { |
| Message.warn("configuration not found '" + conf + "' in " + node.getResolvedId() |
| + ": ignoring"); |
| if (node.getParent() != null) { |
| Message.warn("it was required from " + node.getParent().getResolvedId()); |
| } |
| } |
| return; |
| } |
| // we handle the case where the asked configuration extends others: |
| // we have to first fetch the extended configurations |
| |
| // first we check if this is the actual requested conf (not an extended one) |
| boolean requestedConfSet = false; |
| if (node.getRequestedConf() == null) { |
| node.setRequestedConf(conf); |
| requestedConfSet = true; |
| } |
| // now let's recurse in extended confs |
| String[] extendedConfs = c.getExtends(); |
| if (extendedConfs.length > 0) { |
| node.updateConfsToFetch(Arrays.asList(extendedConfs)); |
| } |
| for (String extendedConf : extendedConfs) { |
| fetchDependencies(node, extendedConf, fetchedSet, false); |
| } |
| |
| // now we can actually resolve this configuration dependencies |
| if (!isDependenciesFetched(node.getNode(), conf, fetchedSet) && node.isTransitive()) { |
| for (VisitNode dep : node.getDependencies(conf)) { |
| dep.useRealNode(); // the node may have been resolved to another real one while |
| // resolving other deps |
| for (String rconf : dep.getRequiredConfigurations(node, conf)) { |
| fetchDependencies(dep, rconf, fetchedSet, true); |
| } |
| if (!dep.isEvicted() && !dep.hasProblem()) { |
| // if there are still confs to fetch (usually because they have |
| // been updated when evicting another module), we fetch them now |
| for (String fconf : dep.getConfsToFetch()) { |
| // shouldBeFixed=false to because some of those dependencies might |
| // be private when they were actually extending public conf. |
| // Should we keep two list of confs to fetch (private&public)? |
| // I don't think, visibility is already checked, and a change in the |
| // configuration between version might anyway have worse problems. |
| fetchDependencies(dep, fconf, fetchedSet, false); |
| } |
| } |
| } |
| markDependenciesFetched(node.getNode(), conf, fetchedSet); |
| } |
| // we have finished with this configuration, if it was the original requested conf |
| // we can clean it now |
| if (requestedConfSet) { |
| node.setRequestedConf(null); |
| } |
| |
| } |
| |
| /** |
| * Returns true if we've already fetched the dependencies for this node and configuration |
| * |
| * @param node |
| * node to check |
| * @param conf |
| * configuration to check |
| * @return true if we've already fetched this dependency |
| */ |
| private boolean isDependenciesFetched(IvyNode node, String conf, Set<String> fetchedSet) { |
| String key = getDependenciesFetchedKey(node, conf); |
| return fetchedSet.contains(key); |
| } |
| |
| private void markDependenciesFetched(IvyNode node, String conf, Set<String> fetchedSet) { |
| String key = getDependenciesFetchedKey(node, conf); |
| fetchedSet.add(key); |
| } |
| |
| private String getDependenciesFetchedKey(IvyNode node, String conf) { |
| ModuleRevisionId moduleRevisionId = node.getResolvedId(); |
| return moduleRevisionId.getOrganisation() + "|" + moduleRevisionId.getName() + "|" |
| + moduleRevisionId.getRevision() + "|" + conf; |
| } |
| |
| private void resolveConflict(VisitNode node, String conf) { |
| resolveConflict(node, node.getParent(), conf, Collections.<IvyNode> emptySet()); |
| } |
| |
| /** |
| * Resolves conflict for the given node in the given ancestor. This method do conflict |
| * resolution in ancestor parents recursively, unless not necessary. |
| * |
| * @param node |
| * the node for which conflict resolution should be done |
| * @param ancestor |
| * the ancestor in which the conflict resolution should be done |
| * @param conf String |
| * @param toevict |
| * a collection of IvyNode to evict (as computed by conflict resolution in |
| * descendants of ancestor) |
| * @return true if conflict resolution has been done, false it can't be done yet |
| */ |
| private boolean resolveConflict(VisitNode node, VisitNode ancestor, String conf, |
| Collection<IvyNode> toevict) { |
| if (ancestor == null || node == ancestor) { |
| return true; |
| } |
| // check if job is not already done |
| if (checkConflictSolvedEvicted(node, ancestor)) { |
| // job is done and node is evicted, nothing to do |
| return true; |
| } |
| boolean debugConflictResolution = settings.debugConflictResolution(); |
| if (checkConflictSolvedSelected(node, ancestor)) { |
| // job is done and node is selected, nothing to do for this ancestor, but we still have |
| // to check higher levels, for which conflict resolution might have been impossible |
| // before |
| if (resolveConflict(node, ancestor.getParent(), conf, toevict)) { |
| // now that conflict resolution is ok in ancestors |
| // we just have to check if the node wasn't previously evicted in root ancestor |
| EvictionData evictionData = node.getEvictionDataInRoot(node.getRootModuleConf(), |
| ancestor); |
| if (evictionData != null) { |
| // node has been previously evicted in an ancestor: we mark it as evicted |
| if (debugConflictResolution) { |
| Message.debug(node + " was previously evicted in root module conf " |
| + node.getRootModuleConf()); |
| } |
| node.markEvicted(evictionData); |
| if (debugConflictResolution) { |
| Message.debug("evicting " + node + " by " + evictionData); |
| } |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| // compute conflicts |
| Set<IvyNode> resolvedNodes = ancestor.getNode().getResolvedNodes(node.getModuleId(), |
| node.getRootModuleConf()); |
| resolvedNodes.addAll(ancestor.getNode().getPendingConflicts(node.getRootModuleConf(), |
| node.getModuleId())); |
| Collection<IvyNode> conflicts = computeConflicts(node, ancestor, conf, toevict, |
| resolvedNodes); |
| |
| ConflictManager conflictManager = null; |
| for (VisitNode current : ancestor.getPath()) { |
| ModuleDescriptor descriptor = current.getNode().getDescriptor(); |
| if (descriptor == null) { |
| throw new IllegalStateException( |
| "impossible to get conflict manager when data has not been loaded. IvyNode = " |
| + current.getNode()); |
| } |
| |
| conflictManager = descriptor.getConflictManager(node.getModuleId()); |
| if (conflictManager != null) { |
| break; |
| } |
| } |
| |
| if (conflictManager == null) { |
| conflictManager = settings.getConflictManager(node.getModuleId()); |
| } |
| |
| Collection<IvyNode> resolved = resolveConflicts(node, ancestor, conflicts, conflictManager); |
| |
| if (resolved == null) { |
| if (debugConflictResolution) { |
| Message.debug("impossible to resolve conflicts for " + node + " in " + ancestor |
| + " yet"); |
| Message.debug("setting all nodes as pending conflicts for later conflict" |
| + " resolution: " + conflicts); |
| } |
| ancestor.getNode().setPendingConflicts(node.getModuleId(), node.getRootModuleConf(), |
| conflicts); |
| return false; |
| } |
| |
| if (debugConflictResolution) { |
| Message.debug("selected revisions for " + node + " in " + ancestor + ": " + resolved); |
| } |
| if (resolved.contains(node.getNode())) { |
| // node has been selected for the current parent |
| |
| // handle previously selected nodes that are now evicted by this new node |
| toevict = resolvedNodes; |
| toevict.removeAll(resolved); |
| |
| for (IvyNode te : toevict) { |
| te.markEvicted(node.getRootModuleConf(), ancestor.getNode(), conflictManager, |
| resolved); |
| |
| if (debugConflictResolution) { |
| Message.debug("evicting " + te + " by " |
| + te.getEvictedData(node.getRootModuleConf())); |
| } |
| } |
| |
| // it's very important to update resolved and evicted nodes BEFORE recompute parent call |
| // to allow it to recompute its resolved collection with correct data |
| // if necessary |
| ancestor.getNode().setResolvedNodes(node.getModuleId(), node.getRootModuleConf(), |
| resolved); |
| |
| Collection<IvyNode> evicted = new HashSet<>(ancestor.getNode().getEvictedNodes( |
| node.getModuleId(), node.getRootModuleConf())); |
| evicted.removeAll(resolved); |
| evicted.addAll(toevict); |
| ancestor.getNode().setEvictedNodes(node.getModuleId(), node.getRootModuleConf(), |
| evicted); |
| ancestor.getNode().setPendingConflicts(node.getModuleId(), node.getRootModuleConf(), |
| Collections.<IvyNode> emptySet()); |
| |
| return resolveConflict(node, ancestor.getParent(), conf, toevict); |
| } else { |
| // node has been evicted for the current parent |
| if (resolved.isEmpty()) { |
| if (debugConflictResolution) { |
| Message.verbose("conflict manager '" + conflictManager |
| + "' evicted all revisions among " + conflicts); |
| } |
| } |
| |
| // it's time to update parent resolved and evicted with what was found |
| |
| Collection<IvyNode> evicted = new HashSet<>(ancestor.getNode().getEvictedNodes( |
| node.getModuleId(), node.getRootModuleConf())); |
| toevict.removeAll(resolved); |
| evicted.removeAll(resolved); |
| evicted.addAll(toevict); |
| evicted.add(node.getNode()); |
| ancestor.getNode().setEvictedNodes(node.getModuleId(), node.getRootModuleConf(), |
| evicted); |
| ancestor.getNode().setPendingConflicts(node.getModuleId(), node.getRootModuleConf(), |
| Collections.<IvyNode> emptySet()); |
| |
| node.markEvicted(ancestor, conflictManager, resolved); |
| if (debugConflictResolution) { |
| Message.debug("evicting " + node + " by " + node.getEvictedData()); |
| } |
| |
| // if resolved changed we have to go up in the graph |
| Collection<IvyNode> prevResolved = ancestor.getNode().getResolvedNodes( |
| node.getModuleId(), node.getRootModuleConf()); |
| boolean solved = true; |
| if (!prevResolved.equals(resolved)) { |
| ancestor.getNode().setResolvedNodes(node.getModuleId(), node.getRootModuleConf(), |
| resolved); |
| for (IvyNode sel : resolved) { |
| if (!prevResolved.contains(sel)) { |
| solved &= resolveConflict(node.gotoNode(sel), ancestor.getParent(), conf, |
| toevict); |
| } |
| } |
| } |
| return solved; |
| } |
| } |
| |
| private Collection<IvyNode> resolveConflicts(VisitNode node, VisitNode ancestor, |
| Collection<IvyNode> conflicts, ConflictManager conflictManager) { |
| if (node.getParent() != ancestor |
| // we are not handling the direct parent |
| |
| && conflictManager == settings.getConflictManager(node.getModuleId()) |
| // the conflict manager is the default one |
| |
| && node.getParent().getNode() |
| .getResolvedNodes(node.getModuleId(), node.getRootModuleConf()) |
| .equals(conflicts) |
| // there is no new conflict in this ancestor |
| |
| ) { |
| // IVY-465 case |
| if (settings.debugConflictResolution()) { |
| Message.debug("no new conflicting revisions for " + node + " in " + ancestor + ": " |
| + conflicts); |
| } |
| |
| return conflicts; |
| } else { |
| if (settings.debugConflictResolution()) { |
| Message.debug("found conflicting revisions for " + node + " in " + ancestor + ": " |
| + conflicts); |
| } |
| |
| return conflictManager.resolveConflicts(ancestor.getNode(), conflicts); |
| } |
| } |
| |
| /** |
| * Compute possible conflicts for a node, in the context of an ancestor (a node which has a |
| * dependency - direct or indirect - on the node for which conflicts should be computed. |
| * |
| * @param node |
| * the node for which conflicts should be computed |
| * @param ancestor |
| * the ancestor in which conflicts should be computed |
| * @param conf |
| * the configuration of the node in which conflicts should be computed |
| * @param toevict |
| * a collection of nodes which have been evicted during conflict resolution at lower |
| * level. It may be empty if no conflict resolution has occurred for this node yet, |
| * or if no node has been evicted. |
| * @param selectedNodes |
| * a collection of nodes selected during previous conflict resolution for the given |
| * node and ancestor. This collection is updated by this call, removing nodes which |
| * should be evicted. |
| * @return a collection of IvyNode which may be in conflict with the given node in the given |
| * ancestor. This collection always contain at least the given node. |
| */ |
| private Collection<IvyNode> computeConflicts(VisitNode node, VisitNode ancestor, String conf, |
| Collection<IvyNode> toevict, Collection<IvyNode> selectedNodes) { |
| Collection<IvyNode> conflicts = new LinkedHashSet<>(); |
| conflicts.add(node.getNode()); |
| /* |
| * We first try to remove all evicted nodes from the collection of selected nodes to update |
| * this collection. If the collection changes, it means that it contained evicted nodes, and |
| * thus is not up to date. |
| */ |
| boolean evictedInSelected = selectedNodes.removeAll(toevict); |
| /* |
| * Another case where we need to deeply compute selected nodes is when selectedNodes is |
| * empty (not computed yet) and we aren't in the context of the direct parent of the node. |
| */ |
| if (evictedInSelected |
| || (selectedNodes.isEmpty() && !node.getParent().getNode() |
| .equals(ancestor.getNode()))) { |
| IvyContext context = IvyContext.getContext(); |
| ResolveData data = context.getResolveData(); |
| VisitNode oldVisitNode = data.getCurrentVisitNode(); |
| data.setCurrentVisitNode(ancestor); |
| try { |
| // In this case we need to compute selected nodes again. |
| Collection<IvyNode> deps = ancestor.getNode().getDependencies( |
| node.getRootModuleConf(), |
| ancestor.getNode().getConfigurations(node.getRootModuleConf()), |
| ancestor.getRequestedConf()); |
| for (IvyNode dep : deps) { |
| if (dep.getModuleId().equals(node.getModuleId())) { |
| conflicts.add(dep); |
| } |
| conflicts.addAll(dep.getResolvedNodes(node.getModuleId(), |
| node.getRootModuleConf())); |
| } |
| } finally { |
| data.setCurrentVisitNode(oldVisitNode); |
| } |
| } else if (selectedNodes.isEmpty()) { |
| /* |
| * No selected nodes at all yet, and we are in the context of the direct parent |
| * (otherwise previous block would have been reached). We can compute conflicts based on |
| * the parent direct dependencies in current root module conf. |
| */ |
| VisitNode parent = node.getParent(); |
| Collection<IvyNode> parentDepIvyNodes = parent.getNode().getDependencies( |
| node.getRootModuleConf(), |
| parent.getNode().getConfigurations(node.getRootModuleConf()), |
| parent.getRequestedConf()); |
| for (IvyNode parentDep : parentDepIvyNodes) { |
| if (parentDep.getModuleId().equals(node.getModuleId())) { |
| conflicts.add(parentDep); |
| } |
| } |
| } else { |
| conflicts.addAll(selectedNodes); |
| } |
| return conflicts; |
| } |
| |
| private boolean checkConflictSolvedSelected(VisitNode node, VisitNode ancestor) { |
| if (ancestor.getResolvedRevisions(node.getModuleId()).contains(node.getResolvedId())) { |
| // resolve conflict has already be done with node with the same id |
| if (settings.debugConflictResolution()) { |
| Message.debug("conflict resolution already done for " + node + " in " + ancestor); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean checkConflictSolvedEvicted(VisitNode node, VisitNode ancestor) { |
| if (ancestor.getEvictedRevisions(node.getModuleId()).contains(node.getResolvedId())) { |
| // resolve conflict has already be done with node with the same id |
| if (settings.debugConflictResolution()) { |
| Message.debug("conflict resolution already done for " + node + " in " + ancestor); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| public ResolvedModuleRevision findModule(ModuleRevisionId id, ResolveOptions options) { |
| DependencyResolver r = settings.getResolver(id); |
| if (r == null) { |
| throw new IllegalStateException("no resolver found for " + id.getModuleId()); |
| } |
| DefaultModuleDescriptor md = DefaultModuleDescriptor.newCallerInstance(id, |
| new String[] {"*"}, false, false); |
| |
| if (options.getResolveId() == null) { |
| options.setResolveId(ResolveOptions.getDefaultResolveId(md)); |
| } |
| |
| try { |
| return r.getDependency(new DefaultDependencyDescriptor(id, true), new ResolveData(this, |
| options, new ConfigurationResolveReport(this, md, "default", null, options))); |
| } catch (ParseException e) { |
| throw new RuntimeException("problem while parsing repository module descriptor for " |
| + id + ": " + e, e); |
| } |
| } |
| |
| /** |
| * Mediates the given dependency descriptor according to given options. |
| * <p> |
| * The mediated dependency descriptor must return the actually requested module revision id when |
| * the method {@link DependencyDescriptor#getDependencyRevisionId()} is called. |
| * </p> |
| * |
| * @param dd |
| * the dependency descriptor for which the requested module revision id should be |
| * returned |
| * @param options |
| * the resolve options to use |
| * @return the mediated {@link DependencyDescriptor}. |
| */ |
| public DependencyDescriptor mediate(DependencyDescriptor dd, ResolveOptions options) { |
| if (dd == null) { |
| return null; |
| } |
| String resolveMode = options.getResolveMode() == null ? settings.getResolveMode(dd |
| .getDependencyId()) : options.getResolveMode(); |
| if (ResolveOptions.RESOLVEMODE_DYNAMIC.equals(resolveMode) |
| && !dd.getDynamicConstraintDependencyRevisionId().equals( |
| dd.getDependencyRevisionId())) { |
| // the dynamicRevId can contain a null branch, so make sure this |
| // has been replaced by the default branch (if any!) |
| return dd.clone(ModuleRevisionId.newInstance(dd |
| .getDynamicConstraintDependencyRevisionId(), dd |
| .getDynamicConstraintDependencyRevisionId().getRevision())); |
| } else { |
| return dd; |
| } |
| } |
| |
| public EventManager getEventManager() { |
| return eventManager; |
| } |
| |
| public ResolveEngineSettings getSettings() { |
| return settings; |
| } |
| |
| public SortEngine getSortEngine() { |
| return sortEngine; |
| } |
| |
| private void checkInterrupted() { |
| IvyContext.getContext().getIvy().checkInterrupted(); |
| } |
| |
| } |