Ivy, everyone’s favorite “it’s not Maven” dependency manager, has a unique, idiosyncratic feature I was fighting with today: artifact tags to override which jars/artifacts you use from upstream projects.
While Maven has only project-to-project dependencies (i.e. you get all the jars in each upstream project whether you want them or not), Ivy adds the concept of configurations to select subsets of artifacts within your dependencies. E.g., in an ivy.xml file:
<dependency org="upstream" name="project" rev="1.0"
conf="default->someSubsetConf" />
By using someSubsetConf, you can select a specific set of artifacts (only those that its own ivy.xml file declares as part of it’s someSubsetConf) from the upstream project.
Ivy goes further, and that if the upstream project doesn’t have a configuration in its ivy.xml file that suits your needs, you can request specific artifacts directly, e.g.:
<dependency org="upstream" name="project" rev="1.0">
<artifact name="onlyTheOneYouWant"/>
</dependency>
While this flexibility, which is very characteristic of Ivy, is often times nice, I do sometimes wonder whether Maven’s non-flexibility and hence simplicity in not having confs and artifact overrides is the better choice in the long run.
Nonetheless, I use Ivy, and have used artifact tags on several occasions, but they never worked the way I thought they would, so I finally sat down and tried to systematically understand their semantics.
The Short Version
-
Prefer to not use
artifacttags—if possible, change the upstream project’s configurations to match your needs and just rely on configuration-to-configuration mapping. It’s more intuitive, less boilerplate.If you must use
artifacttags, then: -
Always add a
confattribute to yourartifacttag, and -
Always add a
confattribute to the parentdependencytag that is the union of all of the confs used byartifacttags within that dependency.
For example:
<dependency org="upstream" name="project"
rev="1.0" conf="conf1,conf2">
<artifact name="artifact1" conf="conf1"/>
<artifact name="artifact2" conf="conf2"/>
</dependency>
Will put artifact1 in your conf1 and artifact2 in your conf2, just as you would expect. Simple.
Be warned: if you don’t include these conf attributes exactly as I’ve described, things will very likely not work they way you expect.
If you don’t care why, that’s fine, this template should serve you well. If you do want to understand the incantation, that’s what the next section is about.
The Long Version
This section describes each of the variations of dependency and artifact tags I used to derive Ivy’s semantics and decipher the docs.
Note that I used Ivy 2.3.0-rc1, which as of this writing is current, and the springframework dependency comes from Bizo’s internal Ivy repository, so it doesn’t match the Maven central namespace or anything like that. Substitute your own dependency with a non-trivial amount of artifacts if you want to follow along.
The base ivy.xml file I was using looked like:
...
<configurations>
<conf name="default"/>
<conf name="buildtime" visibility="private"/>
<conf name="test" visibility="private"/>
<conf name="sources"/>
</configurations>
<dependencies
defaultconfmapping="sources->sources();%->default"
defaultconf="default;sources">
...
</dependencies>
And now, each of the variations:
-
Just a regular
dependencytag:<dependency org="springframework" name="spring" rev="3.0.6.RELEASE"/>Pulls in:
- default: all
defaultspring artifacts (web, servlets, test, etc.) - buildtime, test: no spring artifacts
- sources: all
sourcesspring artifacts (web, servlets, test, etc.)
As expected, given our
ivy.xmlfile’sdefaultconf="default;sources"setting.This is actually fine, and shows the intuitiveness of configuration-to-configuration mapping.
But the idea is that the Spring
defaultconf (in our internal repository) anyway pulls in a lot of various Spring jars we don’t need, so we’re going to try and useartifactto cut that down some. - default: all
-
Add just an
artifacttag:<dependency org="springframework" name="spring" rev="3.0.6.RELEASE"> <artifact name="org.springframework.web" /> </dependency>Pulls in:
- default: web jar
- buildtime, test: no spring artifacts
- sources: web jar (not sources)
Wtf? Ivy docs on artifact:
“By default, if no (artifact) configuration is specified, artifacts specification applies to all master configurations.”
What is a “master configuration? Ivy docs on dependency:
“This mapping (
confattribute ofdependencytag) indicates which configurations of the dependency are required in which configurations of the current module, also called master configurations.”So, due to
defaultconf="default;sources", those are our “master configurations” for this artifact, and the web jar artifact is put in both the default and sources confs.Take away: Leaving off
artifact’sconfattribute puts the artifact in any master configuration, which is probably not what you want. Add aconf. -
Add an
artifacttag withconfattribute:<dependency org="springframework" name="spring" rev="3.0.6.RELEASE"> <artifact name="org.springframework.web" conf="default"/> </dependency>Pulls in:
- default: web jar
- buildtime, test: no spring artifacts
- sources: all spring sources
Wtf? Ivy docs on artifact:
“do not forget that if you do not specify any specification for a particular configuration (none of the artifact’s confs include the master configuration) then no specification will apply for this configuration and it will be resolved not taking into account any specification.”
Since we used
conf=defaultbut had noconf=sourcesanywhere, sources fell back to using the default mapping, and pulled in all sources artifacts.Take away: If you add an
artifactconf, set the parentdependencyconfto include only those confs that you’ve mapped. -
Add an
artifacttag withconfattribute anddependencyconf:<dependency org="springframework" name="spring" rev="3.0.6.RELEASE" conf="default"> <artifact name="org.springframework.web" conf="default"/> </dependency>Pulls in:
- default: web jar
- buildtime, test: no spring artifacts
- sources: no spring artifacts
As expected.
-
Add two
artifacttags withconfattributes:<dependency org="springframework" name="spring" rev="3.0.6.RELEASE"> <artifact name="org.springframework.web" conf="default"/> <artifact name="org.springframework.web-sources" type="sources" ext="jar" conf="sources"/> </dependency>Pulls in:
- default: web jar
- buildtime, test: no spring artifacts
- sources: web sources
As expected. All master configurations had
artifacttag overrides, so none of them (masterorsources) fell back to their default mapping. -
Add two
artifacttags with one non-”master”confattribute:<dependency org="springframework" name="spring" rev="3.0.6.RELEASE"> <artifact name="org.springframework.web" conf="default"/> <artifact name="org.springframework.test" conf="test"/> </dependency>Pulls in:
- default: web jar
- buildtime: no spring artifacts
- test: no spring artifacts
- sources: all spring sources
Wtf? Ivy docs on artifact:
confattribute is “comma separated list of the master configurations in which this artifact should be included.”testis not a master configuration, so it is essentially ignored.Take away: if you add an
artifactconf, ensure it is a master configuration, or just add it to thedependencyconfto make sure -
Two
artifacttags, both with “master”confattributes:<dependency org="springframework" name="spring" rev="3.0.6.RELEASE" conf="default,test"> <artifact name="org.springframework.web" conf="default"/> <artifact name="org.springframework.test" conf="test"/> </dependency>Pulls in:
- default: web jar
- buildtime: no spring artifacts
- test: test jar
- sources: no spring sources
As expected, given the behavior discovered so far.
Note on Transitive Dependencies
Just a quick note, but while artifact let’s you pick apart an upstream project’s artifacts for only those you want, this doesn’t have any affect on the transitive dependencies you inherit from the project—those are still based strictly on configurations.
Usually this just means you’ll end up pulling in more transitive dependencies than you need, but I think it re-enforces the notion the artfact override is a hack and that finding a way to do configuration-to-configuration mappings is a better way to do things with Ivy.
Conclusion
Don’t use artifact tags. If you do, always specify both the dependency conf and the artifact conf.