Dependency Management with JBoss Tattletale

By | December 27, 2014

JBoss Tattletale is not a new tool, in fact the latest version was released about three years ago and nothing has happened in the Git repository since. It still, to my knowledge, the best free tool to discover dependencies, relations and even collisions between dependencies.
In this article I will show how to use JBoss Tattletale from Maven and look at some of the reports produced by Tattletale.

Background

What first caused me to turn to Tattletale was when I wanted to use a newer version of the Apache HTTP Client in a program that used a library that has a dependency to an older version of said HTTP client. I experienced some strange problems and became quite puzzled before I realized that there were different classes that had the same name and resided in the same package on my class-path.

After having run Tattletale from the command-line a couple of times, my laziness struck me once again and decided to use the Tattletale Maven plug-in to be able to generate reports routinely when building my project.

Tattletale from Maven

I have chosen to create a Maven build profile for Tattletale report generation. There are a couple of reasons for this:

  • I want to have control over when to generate Tattletale reports.
    Report generation is time-consuming and I do not want to generate reports at every build.
  • Tattletale report generation require some preparations, as we will see, and I wanted to gather all the steps in one place.
    This enable me to either quickly copy the Maven build profile for Tattletale report generation to a new project or to include it in a parent pom-file, if one is available.

The Maven build profile for Tattletale report generation looks like this:

<profiles>
    <!-- 
        JBoss Tattletale report generation profile.
        Generate reports with: mvn clean verify -DtattleTale -DskipTests
    -->
    <profile>
        <activation>
            <property>
                <name>tattleTale</name>
            </property>
        </activation>
        <properties>
            <!-- Temporary directory that will hold files that are to be analyzed by Tattletale. -->
            <tattletale.tempdir>${project.build.directory}/temp-jar-directory</tattletale.tempdir>
            <!--  Directory to which Tattletale reports will be written.-->
            <tattletale.reportdir>${project.build.directory}/tattletale-report</tattletale.reportdir>
            <!-- External Tattletale configuration file (not external to project). -->
            <tattletale.configfile>${project.basedir}/jboss-tattletale.properties</tattletale.configfile>
        </properties>
        <build>
            <plugins>
                <!-- 
                    Copy JARs of all dependencies to directory that Tattletale is to analyze
                    when creating reports.
                -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <version>2.9</version>
                    <executions>
                        <execution>
                            <id>copy-dependencies</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>copy-dependencies</goal>
                                </goals>
                            <configuration>
                                <outputDirectory>${tattletale.tempdir}</outputDirectory>
                                <overWriteReleases>true</overWriteReleases>
                                <overWriteSnapshots>true</overWriteSnapshots>
                                <overWriteIfNewer>true</overWriteIfNewer>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <!--
                    Copy JAR of this project to directory that Tattletale is to analyze when
                    creating reports.
                -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>2.7</version>
                    <executions>
                        <execution>
                            <id>copy-resources</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>copy-resources</goal>
                            </goals>
                            <configuration>
                                <outputDirectory>${tattletale.tempdir}</outputDirectory>
                                <resources>
                                    <resource>
                                        <directory>${project.build.directory}</directory>
                                        <includes>
                                            <include>${project.artifactId}-${project.version}.jar</include>
                                        </includes>
                                    </resource>
                                </resources>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <!-- Generate Tattletale reports. -->
                <plugin>
                    <groupId>org.jboss.tattletale</groupId>
                    <artifactId>tattletale-maven</artifactId>
                    <version>1.2.0.Beta2</version>
                    <executions>
                        <execution>
                            <phase>verify</phase>
                            <goals>
                                <goal>report</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <source>${tattletale.tempdir}</source>
                        <destination>${tattletale.reportdir}</destination>
                        <configuration>${tattletale.configfile}</configuration>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Report generation consists of these steps:

  • Copy JAR-files of all dependencies to temporary directory.
    While it is possible to specify multiple directories which Tattletale is to scan for JAR-files to analyze, I found it simpler to copy all the dependencies to one and the same directory.
  • Copy the JAR-file of the project to the temporary directory.
    This assumes that the project creates one single JAR-file. If your project creates multiple JAR-files or other types of files (WAR-file(s), for instance) or use a non-standard naming scheme for the files produced, you have to modify the contents of the <includes> element.
  • Generate Tattletale report(s).
    I have used a separate configuration file for Tattletale that enables more fine-grained configuration. We’ll examine this configuration file below.

Note the three properties (tattletale.tempdir, tattletale.reportdir and tattletale.configfile) in the profile – these allow you to customize the location of the temporary directory to which JAR-files are copied, the location of the directory to which generated reports are written and finally the location of the Tattletale configuration file.

A word of warning concerning the choice of directory which to write the report to:
Tattletale will delete the contents of this directory before generating reports, so do not chose a directory that contains files and/or folders that you do not want deleted.

If you like to clean up after Tattletale report generation and delete the temporary directory, add the following plug-in to the Maven profile listed above after the Tattletale plug-in:

<!--
    Cleans up after Tattletale report generation,
    deleting the temporary directory and its contents.
-->
<plugin>
    <artifactId>maven-clean-plugin</artifactId>
    <version>2.6.1</version>
    <executions>
        <execution>
            <id>clean-up</id>
            <phase>verify</phase>
            <goals>
                <goal>clean</goal>
            </goals>
            <configuration>
                <excludeDefaultDirectories>true</excludeDefaultDirectories>
                <failOnError>false</failOnError>
                <filesets>
                    <fileset>
                        <directory>${tattletale.tempdir}</directory>
                    </fileset>
                </filesets>
            </configuration>
        </execution>
    </executions>
</plugin>

Tattletale Configuration

As previously mentioned, I rely on a separate configuration file to configure Tattletale.
My configuration file looks like this:

# JBoss Tattletale configuration file.
# For complete documentation, please see: http://docs.jboss.org/tattletale/userguide/1.2/en-US/html_single/
# Classloader structure to use when scanning JAR files. Only relevant to users of JBoss AS.
classloader=org.jboss.tattletale.reporting.classloader.NoopClassLoaderStructure
# Comma-separated list of profiles used to determine if classes are provided by some framework or library.
# Available profiles:
# java5     - JavaSE 5 API
# java6     - JavaSE 6 API
# ee5       - JavaEE 5 API
# ee6       - JavaEE 6 API
# seam22    - JBoss Seam 2.2 API
# cdi10     - CDI 1.0 API
# spring25  - Spring 2.5 API
# spring30  - Spring 3.0 API
# jce5      - JavaCE 5 API
# jce6      - JavaCE 6 API
# jsse5     - JSSE 5 API
# jsse6     - JSSE 6 API
profiles=java6, spring30
# Comma-separated list of reports to generate. Wildcard "*" is default and generates all available reports.
# classdependants       - For all classes, lists the classes that depends on a class. No transitive dependants.
# classdependson        - For all classes, lists the classes a class depends on. No transitive dependencies.
# dependants            - For all archives, list the archives that depend on an archive. No transitive dependants.
# dependson             - For all archives, list the archives that an archive depends on. No transitive dependencies.
# graphviz              - Report showing dependencies graphically. Requires Graphviz.
# transitivedependants  - For all classes, lists the classes that depends on a class. No transitive dependants.
# transitivedependson   - For all archives, list the archives that depend on an archive, including transitive dependants.
# circulardependency    - List archives that have circular one or more circular dependencies.
# classlocation         - Lists the archive(s) in which a specific class is located.
# osgi                  - OSGI state of dependencies.
# sealed                - Reports sealed status of archives. See http://docs.oracle.com/javase/tutorial/deployment/jar/sealman.html for more info.
# sign                  - Reports signed status of archives. See http://docs.oracle.com/javase/tutorial/deployment/jar/signing.html for more info.
# eliminatejars         - Lists archives which have identical names but different versions.
# invalidversion        - Lists archives that has invalid OSG version identifier.
# multiplejars          - Lists classes that appear in more than one archive and the archives respective class appear in.
# multiplejarspackage   - Lists packages that appear in more than one archive and the archives respective package appear in. 
# multiplelocations     - Lists archives that appear in more than one location in the analyzed files.
# unusedjar             - Lists archives that are not referenced from any other archive among the analyzed files.
# blacklisted           - Lists archives containing blacklisted classes or packages.
# noversion             - Lists archives that does not have a version identifier.
# jar                   - Reports an overview for each archive, including class version, required and provided classes etc.
reports=*
# Comma-separated list of directory names, partial file paths or file names (for example JAR-files)
# that are to be excluded from the Tattletale reports.
# Filenames will require an exact match, while directory names and partial paths will match anywhere in a path.
excludes=somelib.jar
# Comma-separated list of blacklisted packages or classes.
# For blacklisted packages, anything in the package or any sub-package will be blacklisted.
blacklisted=com.deprecated
# Comma-separated list of extensions of files that are to be scanned.
scan=.jar
# Generate images in the 'graphviz' report if Graphviz is available.
enableDot=false
# Full path to the Graphviz executable. Not required if the Graphviz executable is on the path.
# For more information on Graphiviz, please see: http://www.graphviz.org/
# Example configuration for Windows:
#graphvizDot=C:\\Program\ Files\ (x86)\\Graphviz2.38\\bin\\dot.exe

Each configuration property is quite well commented in the file itself. However, there are a few things that I would like to emphasize:

  • I haven’t used the ‘profiles’ property except for setting the above values.
    You may want to consider creating additional profiles for, for instance, Java 7 and more recent versions of the Spring framework or any other frameworks that commonly are used in your organization.
  • I usually create all reports (using the ‘*’ wildcard, as above) but the reports that I have found most useful are:
    multiplejars – List classes that appear in more than one archive.
    blacklisted – List archives containing blacklisted classes and/or packages.
    dependson – For all archives, list the archives one archive directly depends on (no transitive dependencies).
    unusedjar – Tries to determine whether an archive is used or not. Note that an archive that is said to be unused may still be used through Java reflection or XML configuration.
  • If you want Tattletale to generate figures showing dependencies (the ‘graphviz’ report), you should be aware that this will make report generation very slow.
    Thus I almost exclusively set the ‘enableDot’ property to false.

Examples

In this section I will show some examples of the reports that I find most interesting. I have added the above Maven configuration to one of my projects and produced Tattletale reports.

multiplejars

As before, this report lists the classes that are found in more than one archive.

Excerpt of a multiplejars Tattletale report.

Excerpt of a multiplejars Tattletale report.

In the above example, we can see that, for instance, the javax.transaction.SystemException class appears in the following JAR-files:

  • activemq-all-5.9.0.jar
  • geronimo-jta_1.0.1B_spec-1.0.1.jar
  • geronimo-jta_1.1_spec-1.1.1.jar

In this example, I would take a look at which dependency, or which dependencies, that has a dependency to the geronimo-jta_1.0.1B_spec-1.0.1.jar and exclude it, if possible.
In this particular example, the geronimo-jta specification JAR-files include the version number in their Maven artifact ids, which cause Tattletale to consider these two archives as two different archives, not two different versions of the same archive. This is the reason for looking at the multiplejars report; while the Maven group id and artifact id of libraries may not always be consistent, the class names and packages in which the classes reside will despite this cause entries to appear in this report.

blacklisted

The blacklisted report can be used to discover use of deprecated code. In this example, I have the following configuration for blacklisted packages in the Tattletale configuration file:

blacklisted=se.ivankrizsan.messagecowboy.domain.valueobjects

The blacklisted report then looks like this:

A blacklisted Tattletale report.

A blacklisted Tattletale report.

On the left we first see in which archive the code that use blacklisted code is located, in this example the archive is message-cowboy-1.0.0-SNAPSHOT.jar. Below the name of the archive, we see which packages that contain code that use blacklisted code and on the right, in the Usage column, we see which entry in the blacklist that caused the entry to appear in the report.

dependson

The dependson report lists the direct dependencies (not including transitive dependencies) of archives.

Excerpt of a dependson Tattletale report.

Excerpt of a dependson Tattletale report.

In the above example we can see that the camel-core-2.12.1.jar depends on the activation-1.1-osgi.jar, the activemq-all-5.9.0.jar, the geronimo-stax-api_1.0_spec-1.0.1.jar, the jaxb-api-2.1.jar and the xpp3-1.1.3.4.O.jar.
In addition, there are a number of classes or interfaces that cannot be found in the available dependencies, for instance the javax.annotation.concurrent.GuardedBy annotation.

graphicaldependencies

The graphical dependencies report draws diagrams showing:

  • Dependencies of one archive.
  • Dependencies between packages in one archive.

The archive dependencies diagram is usually quite straightforward and shows the direct dependencies of an archive:

Archive dependencies diagram from the graphicaldependencies Tattletale report.

Archive dependencies diagram from the graphicaldependencies Tattletale report.

The package dependencies diagram shows dependencies between packages in one archive and direct dependencies to packages external to the archive:

Package dependencies diagram from the graphicaldependencies Tattletale report.

Package dependencies diagram from the graphicaldependencies Tattletale report.

Final Words

JBoss Tattletale definitely has a place in my toolbox. The major drawback, apart from a few minor bugs, is that the project seem more or less abandoned by JBoss and the author. The project is licensed under the GPL 2.1 license, so it is possible to fork it.
There is a JIRA at https://issues.jboss.org/projects/TTALE/summary and on Github, there are a few pull requests that fixes some bugs and implements new features, so there seem to be at least some interest in the project from the community.

One thought on “Dependency Management with JBoss Tattletale

  1. Benjamin

    For those who find this post, to let you know there is also a Tattletale version for JBoss EAP 7 : tattletale-eap7 that you can find on Maven Central and in github.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *