Team Dependency Management Using Snapshots
Chapter 3 of this book discussed how to manage your dependencies in a multi-module build, and while dependency management is fundamental to any Maven build, the team dynamic makes it critical.
In this section, you will learn about using snapshots more effectively in a team environment, and how to enable this within your continuous integration environment.
So far in this book, snapshots have been used to refer to the development version of an individual module. The generated artifacts of the snapshot are stored in the local repository, and in contrast to regular dependencies, which are not changed, these artifacts will be updated frequently. Projects in Maven stay in the snapshot state until they are released, which is discussed in section 7.8 of this chapter.
Snapshots were designed to be used in a team environment as a means for sharing development versions of artifacts that have already been built. Usually, in an environment where a number of modules are undergoing concurrent development, the build involves checking out all of the dependent projects and building them yourself. Additionally, in some cases, where projects are closely related, you must build all of the modules simultaneously from a master build.
While building all of the modules from source can work well and is handled by Maven inherently, it can lead to a number of problems:
- It relies on manual updates from developers, which can be error-prone. This will result in local inconsistencies that can produce non-working builds
- There is no common baseline against which to measure progress
- Building can be slower as multiple dependencies must be rebuilt also
- Changes developed against outdated code can make integration more difficult
As you can see from these issues, building from source doesn’t fit well with an environment that promotes continuous integration. Instead, use binary snapshots that have been already built and tested.
In Maven, this is achieved by regularly deploying snapshots to a shared repository, such as the internal repository set up in section 7.3. Considering that example, you’ll see that the repository was defined in
[...] <distributionManagement> <repository> <id>internal</id> <url>file://localhost/C:/mvnbook/repository/internal</url> </repository> [...] </distributionManagement>
proficio-api to the repository with the following command:
C:mvnbookproficiotrunkproficio-api> mvn deploy
You’ll see that it is treated differently than when it was installed in the local repository. The filename that is used is similar to
proficio-api-1.0-20070726.120139-1.jar. In this case, the version used is the time that it was deployed (in the UTC timezone) and the build number. If you were to deploy again, the time stamp would change and the build number would increment to 2.
This technique allows you to continue using the latest version by declaring a dependency on
1.0-SNAPSHOT, or to lock down a stable version by declaring the dependency version to be the specific equivalent such as
1.0-20070726.12013-1. While this is not usually the case, locking the version in this way may be important if there are recent changes to the repository that need to be ignored temporarily.
Currently, the Proficio project itself is not looking in the internal repository for dependencies, but rather relying on the other modules to be built first, though it may have been configured as part of your settings files. To add the internal repository to the list of repositories used by Proficio regardless of settings, add the following to
[...] <repositories> <repository> <id>internal</id> <url>http://localhost:8081/internal</url> </repository> </repositories> [...]
pluginRepositoryelement as well.
Now, to see the updated version downloaded, build
proficio-core with the following command:
C:mvnbookproficiotrunkproficio-core> mvn -U install
During the build, you will see that some of the dependencies are checked for updates, similar to the example below (note that this output has been abbreviated):
[...] proficio-api:1.0-SNAPSHOT: checking for updates from internal [...]
-U argument in the prior command is required to force Maven to update all of the snapshots in the build. If it were omitted, by default, no update would be performed. This is because the default policy is to update snapshots daily – that is, to check for an update the first time that particular dependency is used after midnight local time.
You can always force the update using the
-U command, but you can also change the interval by changing the repository configuration. To see this, add the following configuration to the repository configuration you defined above in
[...] <repository> [...] <snapshots> <updatePolicy>interval:60</updatePolicy> </snapshots> </repository> [...]
In this example, any snapshot dependencies will be checked once an hour to determine if there are updates in the remote repository. The settings that can be used for the update policy are never, always, daily (the default), and
Whenever you use the
-U argument, it updates both releases and snapshots. This causes many plugins to be checked for updates, as well as updating any version ranges.
This technique can ensure that developers get regular updates, without having to manually intervene, and without slowing down the build by checking on every access (as would be the case if the policy were set to always). However, the updates will still occur only as frequently as new versions are deployed to the repository.
It is possible to establish a policy where developers do an update from the source control management (SCM) system before committing, and then deploy the snapshot to share with the other team members. However, this introduces a risk that the snapshot will not be deployed at all, deployed with uncommitted code, or deployed without all the updates from the SCM, making it out-of-date. Several of the problems mentioned earlier still exist – so at this point, all that is being saved is some time, assuming that the other developers have remembered to follow the process.
A much better way to use snapshots is to automate their creation. Since the continuous integration server regularly rebuilds the code from a known state, it makes sense to have it build snapshots, as well.
How you implement this will depend on the continuous integration server that you use. To deploy from your server, you must ensure that the
distributionManagement section of the POM is correctly configured. However, as you saw earlier, Continuum can be configured to deploy its builds to a Maven snapshot repository automatically. If there is a repository configured to which to deploy them, this feature is enabled by default in a build definition. So far in this section, you have not been asked to apply this setting, so let’s go ahead and do it now. Log in as an administrator and go to the following Configuration screen, shown below.
To complete the Continuum configuration page, you can cut and paste field values from the following list:
|Build Output Directory||
|Release Output Directory||
|Deployment Repository Directory|
The Deployment Repository Directory field entry relies on your internal repository and Continuum server being in the same location. If this is not the case, you can enter a full repository URL such as
To try this feature, follow the Show Projects link, and click Build Now on the Proficio API project. Once the build completes, return to your console and build
proficio-core again using the following command:
C:mvnbookproficiotrunkproficio-core> mvn -U install
You’ll notice that a new version of
proficio-api is downloaded, with an updated time stamp and build number.
With this setup, you can avoid all of the problems discussed previously. Better yet, while you get regular updates from published binary dependencies, when necessary, you can either lock a dependency to a particular build, or build from source. Another point to note about snapshots is that it is possible to store them in a separate repository from the rest of your released artifacts. This can be useful if you need to clean up snapshots on a regular interval, but still keep a full archive of releases.
If you are using the regular deployment mechanism (instead of using Maestro Build Management or Continuum), this separation is achieved by adding an additional repository to the
distributionManagement section of your POM. For example, if you had a snapshot-only repository in
/www/repository/snapshots, you would add the following:
[...] <distributionManagement> [...] <snapshotRepository> <id>internal.snapshots</id> <url>file://localhost/www/repository/snapshots</url> </snapshotRepository> </distributionManagement> [...]
This will deploy to that repository whenever the version contains SNAPSHOT, and deploy to the regular repository you listed earlier, when it doesn’t.
Given this configuration, you can make the snapshot update process more efficient by not checking the repository that has only releases for updates. The replacement repository declarations in your POM would look like this:
[...] <repositories> <repository> <id>internal</id> <url>http://localhost:8081/internal</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>internal.snapshots</id> <url>http://localhost:8081/snapshots</url> <snapshots> <updatePolicy>interval:60</updatePolicy> </snapshots> </repository> </repositories> [...]