Hi
This (long) email reports a defect, plus the source code explanation for
the defect,
plus a proposed source code change (which I have tested) to correct the
defect.
This email is formatted in a fixed-width font and should be read in a wide
window
(about 100 characters).
FAILURE REPORT:
I believe the "install" Ant task of Apache Ivy 2.5.3 has a defect whereby
a request to install a module from the Maven repository fails when the
module
has a POM parent module in the Maven repository.
The following request is an example demonstrating the failure:
<ivy:install organisation="ch.qos.logback"
module="logback-classic"
revision="1.5.19"
from="maven-central"
to="third-party-local"
transitive="true"
overwrite="true"/>
This produces the following failure:
configure:
[ivy:configure] :: Apache Ivy 2.5.3 - 20241223125031 ::
https://ant.apache.org/ivy/ ::
[ivy:configure] :: loading settings :: file = /home/xxx/ivysettings.xml
install-logback-classic:
[echo] Install logback-classic 1.5.19
[ivy:install] :: installing ch.qos.logback#logback-classic;1.5.19 ::
[ivy:install] :: resolving dependencies ::
[ivy:install] :: downloading artifacts to cache ::
[ivy:install] :: installing in third-party-local ::
[ivy:install] :: install resolution report ::
[ivy:install] :: resolution report :: resolve 0ms :: artifacts dl 0ms
---------------------------------------------------------------------
| | modules ||
artifacts |
| conf | number| search|dwnlded|evicted||
number|dwnlded|
---------------------------------------------------------------------
| default | 1 | 0 | 0 | 0 || 0 |
0 |
---------------------------------------------------------------------
[ivy:install]
[ivy:install] :: problems summary ::
[ivy:install] :::: WARNINGS
[ivy:install] io problem while parsing ivy file:
https://repo1.maven.org/maven2/ch/
qos/logback/logback-classic/1.5.19/logback-classic-1.5.19.pom
(java.io.IOException: Impossible to load parent for
file:/home/xxx/
.ivy2/cache/ch.qos.logback/logback-classic/ivy-1.5.19.xml.original.
Parent=ch.qos.logback#logback-parent;1.5.19)
[ivy:install] module not found: ch.qos.logback#logback-classic;1.5.19
[ivy:install] ==== maven-central: tried
[ivy:install]
https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.5.19
/logback-classic-1.5.19.pom
[ivy:install] ::::::::::::::::::::::::::::::::::::::::::::::
[ivy:install] :: UNRESOLVED DEPENDENCIES ::
[ivy:install] ::::::::::::::::::::::::::::::::::::::::::::::
[ivy:install] :: ch.qos.logback#logback-classic;1.5.19: not found
[ivy:install] ::::::::::::::::::::::::::::::::::::::::::::::
[ivy:install]
[ivy:install] :: USE VERBOSE OR DEBUG MESSAGE LEVEL FOR MORE DETAILS
BUILD FAILED
The "maven-central" resolver is defined as:
<ibiblio name="maven-central" usepoms="true" useMavenMetadata="true"
m2compatible="true"/>
The "ch.qos.logback#logback-parent;1.5.19" module is present in the
repository.
EXPLANATION FOR FAILURE:
I believe the reason for failure is that the "default" resolver is used to
find
the parent module, instead of the specified "from" resolver being used.
Source code referenced below was obtained by git clone of latest sources.
A request to parse the parent module is made in the 'parseDescriptor()'
method of
org.apache.ivy.plugins.parser.m2.PomModuleDescriptorParser:line 188:
final ResolvedModuleRevision parentModule =
parseOtherPom(ivySettings, parentModRevID, true);
if (parentModule == null) {
throw new IOException("Impossible to load parent for " +
res.getName()
+ ". Parent=" + parentModRevID);
}
The resolver for the parent module is requested in the 'parseOtherPom()'
method of:
org.apache.ivy.plugins.parser.m2.PomModuleDescriptorParser:line 453:
DependencyResolver resolver =
ivySettings.getResolver(parentModRevID);
if (resolver == null) {
// TODO: Throw exception here?
return null;
}
dd = toSystem(dd, ivySettings.getContextNamespace());
return resolver.getDependency(dd, data);
This effectively invokes:
org.apache.ivy.core.settings.IvySettings:line 922:
public synchronized DependencyResolver getResolver(ModuleRevisionId
mrid) {
DependencyResolver r = getDictatorResolver();
if (r != null) {
return r;
}
String resolverName = getResolverName(mrid);
return getResolver(resolverName);
}
The 'getDictatorResolver()' call returns "null", causing
'getResolverName(mrid)'
to be called. This is:
org.apache.ivy.core.settings.IvySettings:line 965:
public synchronized String getResolverName(ModuleRevisionId mrid) {
ModuleSettings ms = moduleSettings.getRule(mrid, new
Filter<ModuleSettings>() {
public boolean accept(ModuleSettings o) {
return o.getResolverName() != null;
}
});
return ms == null ? defaultResolverName : ms.getResolverName();
}
The 'moduleSettings.getRule(mrid,...)' returns "null", causing
'defaultResolverName'
to be returned. This is the name of the default resolver.
The calling method (see from "line 922" above) then does
'getResolver(resolverName)'
to obtain a reference to the default resolver and returns this to
'parseOtherPom()'
(see from "line 453" above). This calls 'resolver.getDependency(dd,data)'
which
invokes the 'getDependency()' method of
org.apache.ivy.plugins.resolver.BasicResolver:line 183:
public ResolvedModuleRevision getDependency(DependencyDescriptor
dd, ResolveData data)
throws ParseException {
IvyContext context = IvyContext.pushNewCopyContext();
try {
...
This obtains a "null" at line 256:
ResolvedResource artifactRef = findFirstArtifactRef(nsMd, nsDd,
data);
checkInterrupted();
if (artifactRef == null) {
throw new UnresolvedDependencyException("\t" + getName()
+ ": no ivy file nor artifact found for " +
systemMrid, false);
}
causing it to throw the 'UnresolvedDependencyException', which is caught at
line 328:
} catch (UnresolvedDependencyException ex) {
if (!ex.getMessage().isEmpty()) {
if (ex.isError()) {
Message.error(ex.getMessage());
} else {
Message.verbose(ex.getMessage());
}
}
return data.getCurrentResolvedModuleRevision();
'data.getCurrentResolvedModuleRevision()' returns "null" and this is
returned to
the calling method 'parseOtherPom()' (see from "line 453" above) which
returns
this result (null) to its calling method 'parseDescriptor()' (see 188" above
and reproduced here):
org.apache.ivy.plugins.parser.m2.PomModuleDescriptorParser:line 188:
final ResolvedModuleRevision parentModule =
parseOtherPom(ivySettings, parentModRevID, true);
if (parentModule == null) {
throw new IOException("Impossible to load parent for " +
res.getName()
+ ". Parent=" + parentModRevID);
}
This causes the "Impossible to load parent" failure to be generated.
PROPOSED CORRECTION FOR DEFECT:
(1) Set a "settings" dictator resolver for the "install" task.
In the 'install() method of the InstallEngine class,
insert the lines commented as "+CC":
org.apache.ivy.core.install.InstallEngine:line 79
// build module file declaring the dependency
Message.info(":: installing " + mrid + " ::");
DependencyResolver oldDictator =
resolveEngine.getDictatorResolver();
DependencyResolver oldSettingsDictator =
settings.getDictatorResolver(); // +CC
boolean log = settings.logNotConvertedExclusionRule();
try {
settings.setLogNotConvertedExclusionRule(true);
resolveEngine.setDictatorResolver(fromResolver);
settings.setDictatorResolver(fromResolver ); // +CC
org.apache.ivy.core.install.InstallEngine:line 194
} finally {
// IVY-834: log the problems if there were any...
Message.sumupProblems();
settings.setDictatorResolver(oldSettingsDictator ); // +CC
resolveEngine.setDictatorResolver(oldDictator);
settings.setLogNotConvertedExclusionRule(log);
}
(2) Make 'DictatorResolver' methods available in the 'InstallEngineSettings'
interface.
In the InstallEngineSettings interface insert the lines commented as "+CC":
org.apache.ivy.core.install.InstallEngineSettings:line 28
public interface InstallEngineSettings extends ParserSettings {
void setDictatorResolver(DependencyResolver resolver); // +CC
DependencyResolver getDictatorResolver(); // +CC
DependencyResolver getResolver(String from);
(3) Make the 'getDictatorResolver()' method of the IvySettings class public.
In the IvySettings class change the access modifier of
'getDictatorResolver()'
from "private" to "public".
org.apache.ivy.core.settings.IvySettings:line 912
BEFORE MODIFICATION
private DependencyResolver getDictatorResolver() {
if (dictatorResolver == null) {
return null;
}
...
AFTER MODIFICATION
public DependencyResolver getDictatorResolver() { // CC modified
if (dictatorResolver == null) {
return null;
}
(END OF DEFECT CORRECTION)
Regards
Colin Chambers