Tải bản đầy đủ
3 Resolving, reporting, and retrieving

3 Resolving, reporting, and retrieving

Tải bản đầy đủ

This operation will trigger a search for the file ivy.xml in the local directory, a parse
of it, and a recursive resolve and parse of all dependencies.
ivy-resolve:
[ivy:resolve] :: resolving dependencies ::
[ org.antbook | diary-core |working@Zermatt ]
[ivy:resolve]
confs: [compile, test, master, runtime, default]
[ivy:resolve]
found [ log4j | log4j | 1.2.13 ] in maven2
[ivy:resolve]
found [ junit | junit | 3.8.2 ] in maven2
[ivy:resolve] :: resolution report ::
------------------------------------------------------------------|
|
modules
||
artifacts
|
|
conf
| number| search|dwnlded|evicted|| number|dwnlded|
------------------------------------------------------------------|
compile
|
1
|
0
|
0
|
0
||
1
|
0
|
|
test
|
2
|
0
|
0
|
0
||
2
|
0
|
|
master
|
0
|
0
|
0
|
0
||
0
|
0
|
|
runtime
|
1
|
0
|
0
|
0
||
1
|
0
|
|
default
|
1
|
0
|
0
|
0
||
1
|
0
|
------------------------------------------------------------------BUILD SUCCESSFUL

The output is a brief summary of actions: what configurations there are, how many artifacts are depended upon, and how many files were downloaded. In this run everything
was in the cache; if an artifact was not there, here’s where it would be downloaded.
Once an ivy.xml file has been resolved, tasks that act on the resolved files and
metadata can be used. One of these tasks is invaluable when setting up a build:
, which creates a report on all the dependencies.
11.3.1

Creating a dependency report
When setting up a project’s dependency and configuration information in the
ivy.xml file, you need to know what’s happening. You need to know which files are
in which configuration and whether there were any conflicts. Rather than analyze the
build file logs or look at retrieved artifacts, the tool to use is Ivy’s task.
This task creates an HTML report of the resolved system. There’s also the
task, which outputs pure XML for further processing. For
most developers, the HTML report is the most useful.




This target generates an HTML page for every configuration in the chosen output
directory. Figure 11.1 shows the output for the default configuration, whose sole
dependency is Log4J.
Only after the report matches your expectations should you move on to retrieving artifacts.

RESOLVING, REPORTING, AND RETRIEVING

305

Figure 11.1

11.3.2

Ivy reports the dependencies for one of the configurations

Retrieving artifacts
After resolution, a module’s dependencies are all in the local Ivy cache, which is in
${user.home}/.ivy/cache by default. That’s good, but it isn’t directly where the
project can fetch them. Three tasks make these files accessible. The
and tasks set up an Ant fileset or path to the list of dependencies for
a configuration:


The generated Ant datatype can be passed straight to tasks that take paths or filesets,
including and .
For this chapter, we will use the task. It copies all of the
libraries of the selected configurations into a directory tree:

pattern="${ivy.lib.dir}/[conf]/[artifact]-[revision].[ext]"

306

CHAPTER 11

MANAGING DEPENDENCIES

sync="true"/>


This copies the files from the cache into separate subdirectories, one for each
configuration:
ivy-retrieve:
[ivy:retrieve] :: retrieving :: [ org.antbook | diary-core ] [sync]
[ivy:retrieve] confs: [compile, test, master, runtime, default]
[ivy:retrieve] 5 artifacts copied, 0 already retrieved

If different configurations use the same artifacts, they get their own private copy from
the local cache. When the task is run again, it won’t copy the files if they already exist:
ivy-retrieve:
[ivy:retrieve] :: retrieving :: [ org.antbook | diary-core ] [sync]
[ivy:retrieve] confs: [compile, test, master, runtime, default]
[ivy:retrieve] 0 artifacts copied, 5 already retrieved

We also can look into a directory to see what’s there, such as the test configuration:
>ls build/ivy/lib/test
junit-3.8.2.jar log4j-1.2.13.jar

Clearly, the dependencies are all set up. Incidentally, the sync="true" attribute of
the task hands over complete control of the directory to Ivy. Ivy will
delete from the destination directories any files that aren’t currently part of the configuration’s dependencies. This action lets us avoid having to clean up the directories
when changing the dependencies of configurations; Ivy does it for us.
Once the artifacts are retrieved into directories, Ant can use the files.
11.3.3

Setting up the classpaths with Ivy
To compile using the retrieved libraries, we can use Ant’s datatype declaration to set up classpaths from the configuration directories:














RESOLVING, REPORTING, AND RETRIEVING

307

To test that everything is set up, we run ant clean test to run all unit tests against
a fresh rebuild of the classes. As they pass, we know that the build is working, which
means that the classpaths are set up right for the compile and test targets.
That’s it! We’ve used Ivy to set up the classpaths for the different activities in our
application: compiling and testing. One has Log4J; the other has Log4J and JUnit. Ivy
resolves the dependencies for each configuration, including inheriting the dependencies of other configurations, downloads the files from repositories, adds the dependencies of those files to the configurations, and then creates status reports or copies
the artifacts over for Ant to use. That’s a pretty impressive set of operations.
We could stop there; it’s powerful enough. But Ivy does more, much more. We
can use it to glue together projects, feeding the artifacts of one project into another.

11.4

WORKING ACROSS PROJECTS WITH IVY
Ivy can pull down artifacts from the local cache, a team repository, or a remote server;
it can be used to pass metadata and artifacts from one Ant project to another, each of
which is, in Ivy terminology, a separate module with its own ivy.xml file. Each
project’s build needs to publish the JAR files and other artifacts it creates into a shared
repository. The ivy.xml files of the other projects declare a dependency on the created artifacts, and Ivy will locate the files during resolution.

11.4.1

Sharing artifacts between projects
The task will copy the artifacts of a project to the repository identified
by a named resolver, here the “local” resolver:

overwrite="true"
artifactspattern="${dist.dir}/[artifact]-[revision].[ext]"/>


This resolver is bound to a repository in ${user.home}/.ivy/local. All builds
by the same user have access to the repository, unless their ivyconf.xml files say
otherwise. Because it’s shared by all of the user’s projects, all the user’s builds can
retrieve the artifacts published to it.
The task requires a version number for the files, which is set with the
pubrevision attribute. In our builds, we’ve been numbering all artifacts, but Ivy
doesn’t require this. You can create artifacts with simple names and have their revision number tacked on when the file is published. This is useful when retrofitting Ivy
to an existing project. Ivy’s task can even determine a build
number from a repository, by determining the version number of the latest artifact in
the repository and setting an Ant property to that value plus one.
With our build files already using version numbers, we avoid that step. We do
have to tell Ivy how to locate the files, which is where the artifactspattern
attribute comes in. It contains a directory path and a pattern for finding artifacts. The
308

CHAPTER 11

MANAGING DEPENDENCIES

pattern [artifact]-[revision].[ext] says that artifacts are created with the
pattern of name-revision and the expected extension. Unnumbered artifacts would
need the simpler pattern [artifact].[ext]. What we don’t do is list the actual
files that are created. This seems surprising, but it’s because the ivy.xml file
declared the artifact already in its element:




This element declares that an artifact is published in the “master” configuration only.
The artifact’s name defaults to that of the module, and it also has the default extension/type of JAR. Projects can create artifacts with different names or extensions. In
chapter 14, for example, we’ll create the EAR file diary.ear:


Again, this is invaluable when adding Ivy to existing code. There’s no need to change
the name of existing artifacts; you only need to add extra metadata and extend the
build process with the task, a task which must be run as part of
the local installation activities:
ivy-publish:
[ivy:publish] :: delivering :: [ org.antbook | diary-core |
working@Zermatt ] :: 0.1alpha-SNAPSHOT :: integration
[ivy:publish]
delivering ivy file to C:\diary\core\dist
/ivy-0.1alpha-SNAPSHOT.xml
[ivy:publish] :: publishing :: [ org.antbook | diary-core ]
[ivy:publish]
published diary-core to
C:\Documents and Settings\ant\.ivy/local/org.antbook
/diary-core/0.1alpha-SNAPSHOT/jars/diary-core.jar
[ivy:publish]
published ivy to C:\Documents and Settings\ant\
.ivy/local/org.antbook/diary-core/0.1alpha-SNAPSHOT/ivys/ivy.xml

b

c

d

This trace of the publish process shows the actions Ivy took:

b
c
d

Ivy placed a copy of the ivy.xml file into the same directory in which the artifacts
were created. All Ivy variables in this file are expanded, so all version numbers and
other late-binding values are expanded.
Ivy copied the JAR file to the specified repository.
Ivy copied the XML file to the specified repository.
Sometimes Ivy doesn’t seem to update the metadata before publishing the file. Deleting the entire dist directory fixed this. A clean build is always safest.
The operation placed the file in a user-specific repository.
There’s nothing to stop the task from publishing the file to a team repository, such as
one on a shared file store. This would work well if it takes a long time to build some
of the project’s artifacts, and there’s no need for every developer to rebuild every part

WORKING ACROSS PROJECTS WITH IVY

309

of the application. The team repository would share the files for everyone to use. This
brings up the next part of the problem: using the published files.
11.4.2

Using published artifacts in other projects
Published artifacts can be pulled into other projects simply by listing them in the
section of the other project’s ivy.xml files. The web application coming in chapter 12 uses the diary-core library, so the application’s
ivy.xml file declares a dependency upon the diary-core artifact:
name="diary-core"
rev="latest.integration"
changing="true"
conf="compile->master;runtime->master;war->master"/>

b

c

d

This declaration has three unusual features. Instead of a hard-coded revision number,
or even a property-driven value, the requested version is latest.integration
b. This is a way of asking for the latest version published on the repository. Ivy will
look at the repositories and find the latest version. As the ivyconf.xml file declares
that org.antbook artifacts come from the local and team repositories only, Ivy
doesn’t poll any remote server. As well as asking for the latest version, we declare that
the artifact and its metadata are changing c. Normally Ivy doesn’t update metadata
files once they’re in the cache; instead it saves the complete dependency graph of every
configuration to its own XML file nearby. For static dependencies, caching all the
dependency information makes resolution much faster. For changing files and metadata, that cached data can stop changes being picked up. We need the most current copy
from the repository, along with its metadata. Unless we used
or the current timestamp to create a new build number on every build—which is an
option—we need to tell Ivy to poll for metadata and artifact changes on every build.
This is what changing ="true" does.
Declaring the configuration to depend on
The final detail is that in every configuration, we ask for the “master” configuration
of the diary-core module only d. That gives us nothing but diary-core.jar—
not its dependencies. When Ivy publishes artifacts to the repository, it adds the
module’s dependency data. If we had compiled with the default configuration via
a compile->default dependency, we would have had Log4J on the classpath.
NOTE

When you depend upon other modules, you get all the dependencies of the
selected configuration, not just the published artifacts of the modules.

This is an important feature of dependency management tools. Their goal is to simplify your life by giving you all the files you need. There’s a price: sometimes you get
more than you want. As we don’t need Log4J to compile the web application and we
don’t want it at runtime, we must ask for a configuration that contains the diarycore artifact but not its dependencies.
310

CHAPTER 11

MANAGING DEPENDENCIES

The choice of public configurations is based on those of Maven2, because whenever Ivy pulls in metadata from the Maven repositories, it parses the metadata and
maps it to Ivy configurations. Maven only supports the configurations of table 11.2.
Table 11.2

Maven2's dependency configurations, as interpreted by Ivy

Configuration name

Meaning

default

The artifact and its runtime dependencies

master

The artifact itself

runtime

All the runtime dependencies of the artifact

compile

Dependencies used to compile the artifact

provided

Compile time dependencies to be pre-installed on the classpath

system

Runtime dependencies to be pre-installed on the classpath

Ivy doesn’t mandate any specific configurations. We normally start off with the
Maven team’s configurations to be consistent with artifacts coming from the Maven
repository. The master configuration has the artifacts with no dependencies,
runtime has the dependencies with no artifact, and default has both. We use
these standard configurations to make it possible to publish artifacts to the central
repository, something that’s beyond the scope of this book. We can still add private
configurations for internal use (such as setting up a classpath) or to create new public configurations with different dependencies, such as embedded,
redist, or webapp.
Having declared the dependencies on the master configuration only, we can run
the new build.
Resolving the new project
The complete ivy.xml file for the diary web application is much more complex
than the diary-core module. Running its ivy-resolve target creates a long list
of dependencies:
ivy-resolve:
[ivy:resolve] :: resolving dependencies ::
[ org.antbook | diary | working ]
[ivy:resolve] confs: [default, compile, test, master, runtime,
war, jing]
[ivy:resolve]
found [ org.antbook | diary-core |
0.1alpha-SNAPSHOT ] in local
[ivy:resolve]
[0.1alpha-SNAPSHOT]
[ org.antbook | diary-core | latest.integration ]
[ivy:resolve]
found [ rome | rome | 0.8 ] in maven2
[ivy:resolve]
found [ jdom | jdom | 1.0 ] in maven2
[ivy:resolve]
found [ javax.servlet | servlet-api | 2.4 ]
in maven2
[ivy:resolve]
found [ httpunit | httpunit | 1.6 ] in maven2

WORKING ACROSS PROJECTS WITH IVY

311

[ivy:resolve]
[ivy:resolve]
[ivy:resolve]
[ivy:resolve]
[ivy:resolve]
[ivy:resolve]
[ivy:resolve]
[ivy:resolve]

found [ xerces | xmlParserAPIs | 2.2.1 ] in maven2
found [ xerces | xercesImpl | 2.6.2 ] in maven2
found [ nekohtml | nekohtml | 0.9.1 ] in maven2
found [ xerces | xerces | 2.4.0 ] in maven2
found [ junit | junit | 3.8.2 ] in maven2
found [ rhino | js | 1.5R4.1 ] in maven2
found [ jtidy | jtidy | 4aug2000r7-dev ] in maven2
found [ thaiopensource | jing | 20030619 ]
in maven2
[ivy:resolve] downloading C:\Documents and Settings\ant\.ivy\local\
org.antbook\diary-core\0.1alpha-SNAPSHOT\jars\diary-core.jar
[ivy:resolve] ... (12kB)
[ivy:resolve] .. (0kB)
[ivy:resolve] [SUCCESSFUL ] [ org.antbook | diary-core |
0.1alpha-SNAPSHOT ] /diary-core.jar[jar] (100ms)
[ivy:resolve] :: resolution report ::
[ivy:resolve] :: evicted modules:
[ivy:resolve] [ junit | junit | 3.8.1 ]
by [[ junit | junit | 3.8.2 ]] in [test]
[ivy:resolve] [ javax.servlet | servlet-api | 2.3 ]
by [[ javax.servlet | servlet-api | 2.4 ]] in [test]

This build log shows that the diary-core module was downloaded from the local
repository. This is the artifact published earlier with the task; it
can now go on the classpath of the web application.
The other interesting log item is that there were two “evicted modules.” The artifact junit-3.8.1 was replaced by junit-3.8.2, and version 2.4 of the servlet
API was used instead of version 2.3. The older dependencies came from HttpUnit,
which is something we’ll be using for testing the web application. It was built against
older versions of the two libraries, versions picked up by the Ivy resolver. As newer
versions were in the web application’s ivy.xml, the older versions were evicted and
the newer versions retained on the dependency graph. Eviction information is
included in the HTML pages generated by , so team members can
see what versions are being used. Figure 11.2 shows the report. It highlights that the
diary-core artifact was found on the local repository, and checked for changes
(“searched”), while two artifacts were evicted.
Now that we are feeding the output of one project into the next, we can chain
builds together just by running them in the right order. We can use the
task to do this:









312

CHAPTER 11

MANAGING DEPENDENCIES

Figure 11.2

In this configuration, two obsolete modules are evicted.

This presetdef can delegate targets down to the child components,
building and perhaps publishing the core component before the web application.
Ordering the builds by hand works if the dependencies are simple and known by
the author of the master build file, but doing so doesn’t scale. Once you have more
than a few projects, projects with different team members maintaining the build
.xml and ivy.xml files, soon you have dependencies where you don’t expect. If the
list of build files to delegate to isn’t kept synchronized, projects start building with
outdated artifacts and developers start wondering why changes don’t propagate, or
worse: you ship old code.
11.4.3

Using Ivy to choreograph builds
If ordering builds by hand is unreliable, the solution is to have the machine work out
the correct sequence. Every module’s ivy.xml file lists that module’s dependencies.
If something were to look through all these files, it could see what modules were being
built and which ones depended on others. It could then sort the list of modules in the
order needed to ensure that all a module’s predecessors were built before the module
itself was built.
The task does this, creating a filelist of all the build.xml files
ordered so that projects are built in the right sequence:

WORKING ACROSS PROJECTS WITH IVY

313

skipbuildwithoutivy="true">



We can use this task to create a task to delegate work to the build files:







The path can also be printed out, to show the execution order:

refid="child.projects" />
The order to build the projects is
${child.projects.property}



Running this target shows the build order of the projects:
> ant -f ch11-ivy-masterbuild.xml show-order
Buildfile: ch11-ivy-masterbuild.xml
[ivy:buildlist] :: Ivy 1.4.1 - 20061109165313 ::
http://ivy.jayasoft.org/ ::
[ivy:buildlist] :: configuring :: file = C:\diary\ivyconf.xml
show-order:
[echo] The order to build the projects is
[echo]
C:\diary\core\build.xml;
C:\diary\persist\build.xml;
C:\diary\persist-webapp\build.xml;
C:\diary\webapp\build.xml

This run shows that Ivy can not only manage the problem of dependencies between
projects, it also can use that same information to order builds.
There are a couple of aspects of the task to be aware of: looping and indirection.
Loops in project dependencies
If there’s a loop—that is, if two or more modules create a cycle in the dependency—
the order of those looped projects is undefined. Modules before the loop will be built
in the right order, and modules after the loop will be ordered, but there’s no way to
predict the order Ivy will choose for those in the loop. This means that it’s acceptable
to have loops in a project, but Ivy will not order the dependencies correctly.
Loops are not unusual in Java library projects, especially if one configuration of
a project depends upon the output of another, which itself depends on the first
314

CHAPTER 11

MANAGING DEPENDENCIES

program. Often you can unroll the loop by creating a new module that depends
on everything.
Indirect dependencies
Ivy doesn’t order builds correctly if two projects have an indirect dependency via a
module that isn’t in the fileset. For example, the persistwebapp project coming in chapter 14 depends on diary-core-persist,
which itself depends upon diary-core. If the diary-core-persist project
was excluded from the fileset, the task wouldn’t care that
persist-webapp depended indirectly on diary-core, and not build the components in the right order.
To create a correct ordering of the build files, the fileset inside the task must contain all related projects. This doesn’t mean that you have to build them all, because
the root attribute can restrict the list to only those build files that matter for a chosen model:
skipbuildwithoutivy="true"
root="webapp" >



This task passes in all build files with matching ivy.xml files, but only selects those
projects that the webapp module depends on. The task can be used to build subsets
of a system, using Ivy to manage the selection and ordering of the subset.
The task is the core Ivy task for complex projects. There’s
a reporting task , which can create a report about all artifacts in
a repository. If a project publishes all generated artifacts to a private repository, this
reporting task will list everything that gets produced and show the dependencies
between artifacts. This is a useful piece of documentation.
There are some other details about Ivy worth knowing about, which we’ll take a
quick look at.

11.5

OTHER ASPECTS OF IVY
There’s a lot more about Ivy that’s covered in its documentation, and there’s no substitute for experience. Here are some techniques that are worth pulling out because
they’re so important.

11.5.1

Managing file versions through Ivy variables
Ivy files support Ivy variables. They are like Ant properties, but they’re truly variable:
they can be changed. They are expanded in strings just like Ant properties, so
${ivy.default.ivy.user.dir} is bound to what in Ant would be ${user
.home}/.ivy. We set some properties in our ivyconf.xml, such as this pattern:

OTHER ASPECTS OF IVY

315