Warning: tutorial in progress !
In the previous tutorial you have seen how to deal with dependencies between two simple projects.
This tutorial will guide you through the use of ivy in a more complete environment. All the sources of this tutorial are available in src/example/multi-project in ivy distribution (warning: the sources attached with ivy 1.3 contain an error in the common.xml file. Please use either latest build to find proper example sources or replace the common.xml file with this one).
Here is a 10000ft overview of the projects involved in this tutorial:
helps to identify module by a version
gives a list of files in a directory (recursively)
gives the total size of all files in a directory, or of a collection of files
find files in a given dir or among a list of files which match a given name
gives the total size of files matching a name in a directory
give access to all other modules features through a simple console app
For sure this is not aimed to demonstrate how to develop a complex app or give indication of advanced algorithm :-)
But this gives a simple understanding of how ivy can be used to develop an application divided in multitple modules.
Now, here is how these modules relate to each other:
Modules in yellow are the modules described in this tutorial, and modules in blue are external dependencies (we will see how to generate this graph later in this tutorial).
As you can see, we have here a pretty interesting set of modules with dependencies between each other, each depending on the latest version of the others.
The sources for this tutorial can be found in src/example/multi-project in the ivy distribution. In this directory, you will find the following files:
This a root build file which can be used to call targets on all modules, in the order of their dependencies (ensuring that a module is always built before any module depending on it, for instance)
the common build file imported by all build.xml files for each project. This build defines the targets which can be used in all projects.
some properties common to all projects
contains a directory per module, with for each
Ivy file of the module, describing its dependencies upon other modules and / or external modules.
Example:
<ivy-module version="1.0">
<info
organisation="jayasoft"
module="find"
status="integration"/>
<configurations>
<conf name="core"/>
<conf name="standalone" extends="core"/>
</configurations>
<publications>
<artifact name="find" type="jar" conf="core" />
</publications>
<dependencies>
<dependency name="version" rev="latest.integration" conf="core->default" />
<dependency name="list" rev="latest.integration" conf="core" />
<dependency org="apache" name="commons-collections" rev="3.1" conf="core->default" />
<dependency org="apache" name="commons-cli" rev="1.0" conf="standalone->default" />
</dependencies>
</ivy-module>
The build file of the project, which consists mainly in an import of the common build file and of a module specific properties file:
<project name="find" default="compile">
<property file="build.properties"/>
<import file="${common.dir}/common.xml"/>
</project>
Module specific properties + properties to find the common build file
projects.dir = ${basedir}/..
wkspace.dir = ${projects.dir}/..
common.dir = ${wkspace.dir}/common
the source directory with all java sources
Note that this doesn't demonstrate good practice for software development in general, in particular you won't find any unit test in this samples, even if we think unit testing is very important. But this isn't the aim of this tutorial.
Now that you are a bit more familiar with the structure, let's have a look at the most important part of this example: the common build file. Indeed, as you have seen all modules build files only import the common build file, and defines their dependencies in their ivy files (with which you should begin to be familiar).
So, here are some aspects of this common build file:
<target name="configure">
<!-- setup ivy default configuration with some custom info -->
<property name="ivy.local.default.root" value="${repository.dir}/local"/>
<property name="ivy.shared.default.root" value="${repository.dir}/shared"/>
<!-- here is how we would have configured ivy if we had our own ivyconf file
<ivy:configure file="${common.dir}/ivyconf.xml" />
-->
</target>
This target configures ivy only by setting two properties: the location for the local repository and the location for the shared repository. It's the only configuration done here, since ivy 1.3 is configured by default to work in a team environment (see default configuration tutorial for details about this). For sure in a real environment the shared repository location would rather be in a team shared directory (or in a more complex repository, again see the default configuration tutorial to see how to use something really different).
This target only indicates in comments how the configuration would have been done if the default configuration wasn't ok for our purpose.
<target name="resolve" depends="configure, clean-lib" description="--> retrieve dependencies with ivy">
<mkdir dir="${lib.dir}"/> <!-- not usually necessary, ivy creates the directory IF there are dependencies -->
<!-- this target is named resolve even if we do a retrieve:
in fact a resolve will be called, and then the retrieve will simply copy files in the lib directory -->
<ivy:retrieve pattern="${lib.dir}/[artifact].[ext]" />
</target>
Here we see that we only call a retrieve task, the resolve being done automatically with default parameters (which are ok in our case). So here nothing special, we simply use ivy to retrieve dependencies in the lib directory, putting artifacts without revision in their names (it's easier to use with an ide, for instance).
<target name="publish" depends="clean-build, new-version, jar" description="--> publish this project in the ivy repository">
<property name="revision" value="${version}"/>
<ivy:publish artifactspattern="${build.dir}/[artifact].[ext]"
resolver="shared"
pubrevision="${revision}"
status="release"
/>
<echo message="project ${ant.project.name} released with version ${revision}" />
</target>
This target let publish the module in the shared repository, with the revision found in the version property, which is set by other targets. It can be used when a module reaches a specific milestone, or whenever you want the teeam to benefit from a new version of the module.
<target name="publish-local" depends="local-version, jar" description="--> publish this project in the local ivy repository">
<delete file="${build.dir}/ivy.xml"/> <!-- delete last produced ivy file to be sure a new one will be generated -->
<ivy:publish artifactspattern="${build.dir}/[artifact].[ext]"
resolver="local"
pubrevision="${revision}"
pubdate="${now}"
status="integration"
/>
<echo message="project ${ant.project.name} published locally with version ${revision}" />
</target>
This is very similar to the publish task, except that this publish the revision in the local repository, which is used only in your environment and doesn't disturb the team. When you change something in a module and want to benefit from the change in another one, you can simply call publish-local in this module, and then your next build of the other module will automatically get this local version.
<target name="clean-local" depends="configure" description="cleans the local repository for the current module">
<delete dir="${ivy.local.default.root}/${ant.project.name}"/>
</target>
This target is used when you don't want to use your local version of a module anymore, for example when you release a new version to the whole team.
<target name="report" depends="resolve" description="--> generates a report of dependencies">
<ivy:report todir="${build.dir}"/>
</target>
Generates both an html report and a graphml report.
For example, to generate a graph like the one shown at the beginning of this tutorial, you just have to follow the instructions given here with the graphml file you will find in projects/console/build/
after having called report in the console project, and that's it, you have a clear overview of all your app dependencies !