null

NullPointerException, probably…

Posts Tagged ‘ivy

Banning Transitive Dependencies With Maven2/3, Gradle and Ivy

with 12 comments

Oh, you are using build tool with dependency management? Good! Be it Maven2/3, Gradle or Ivy, your life as devops or developer is much easier. Until you hit it. The evil transitive dependency. How can it be evil you ask? When the classes in it clash with the classes you really need.  Here’s some use-cases:

  1. Same dependency, different jar names, two examples here:
    1. The Jakarta Commons renaming effort: commons-io:commons-io:1.3.2 and org.apache.commons:common-io:1.3.2
    2. The Spring Framework artifacts naming convention alternatives: spring-beans, spring-context, etc in repo1 versus org.springframework.beans, org.springframework.context, etcin
      SpringSource EBR.
  2. Different packaging of the sample classes, many examples here:
    1. OSGi repackagings: asm:asm:3.2 and org.objectweb.asm:com.springsource.org.objectweb.asm:3.2.0
    2. Modularization of Spring 2.5.6: as single jar and as spring-whatever multiple modules
    3. Xerces and Xalan are included in JDK since 1.5. They are still present as transitive dependencies in all the tools which support JDK 1.4.
    4. Alternative packagings with and without dependencies: cglib:cglib and cglib:cglib-nodep
    5. Project merges like Google collections, which are now included in Google Guava
  3. Deliberately reimplemented interfaces, for example for bridging legacy APIs to new implementation, such as in SLF4J.
  4. Your patches for 3rd-party tools.

All those may end up with 2 or more classes with the same name in the classpath. Why it is bad? Java class identifier consists of fully-qualified class name and the classloader that loaded it, so if two classes with the same name reside in same classpath JVM considers them to be the same class, and only one of them will be loaded. Which one? The first classloader encounters. Which one will it be? You have no idea.
When the duplicated classes are exactly the same, you will never notice. But if the classes are different, you’ll start getting runtime exceptions, such as NoSuchMethodError, NoClassDefFoundError and friends. That’s because other classes expect for find one API, but encounter another one – wrong class was loaded first. Not fun.

Now, when you know how evil they are, let’s take those bastards down!

Maven 2/3

There is no simple way (Maven’s tagline) to exclude some dependency from all the scopes. I’ll show two cases – manual exclusion and working with IntelliJ IDEA:

    1. Stage 1: exclude all the banned dependencies one by one:
      1. Manually edit Maven’s poms
        1. For each evil dependency:
        2. Find which top-level dependency brings the evil transitive hitcher with it. This is done by using Maven Dependency Plugin:
          mvn dependency:tree -Dincludes=commons-logging:commons-logging
        3. You’ll get something like this:
          [INFO] com.mycompany.myproduct:rest-client:1.0
          [INFO] \- org.springframework:spring-webmvc:jar:3.0.5.RELEASE:compile
          [INFO]    \- org.springframework:spring-core:jar:3.0.5.RELEASE:compile
          [INFO]       \- commons-logging:commons-logging:jar:1.1.1:compile
        4. Go to the pom.xml with your dependency management (you use dependency management, don’t you? If you don’t, don’t tell anyone, go and start using it) find spring-webmvc dependency and add an exclusion to it:
          1     <dependency>
          2     	<groupId>org.springframework</groupId>
          3     	<artifactId>spring-webmvc</artifactId>
          4     	<version>3.0.5.RELEASE</version>
          5         <exclusions>
          6             <exclusion>
          7                 <artifactId>commons-logging</artifactId>
          8                 <groupId>commons-logging</groupId>
          9             </exclusion>
          10         </exclusions>
          11     </dependency>
      2. Working with IntelliJ IDEA:
        IntelliJ IDEA Maven Dependencies
          1. Open Maven Dependencies Graph.
          2. Filter it by the dependency you are looking for.
          3. Select it and press Shift-Delete.
    2. Good job! Your nailed them down in the current version of your build. But what happens when someone adds a new 3rd party dependency and brings some bad stuff with it as transitives? You need to protect your build from this scenario. So, stage 2: Fail the build if one of the banned dependencies ever added to the build with Maven Enforcer Plugin. Add the plugin to your root project pom:
      1 <project>
      2   <build>
      3     <plugins>
      4       <plugin>
      5         <groupId>org.apache.maven.plugins</groupId>
      6         <artifactId>maven-enforcer-plugin</artifactId>
      7         <version>1.0</version>
      8         <executions>
      9           <execution>
      10             <id>enforce-banned-dependencies</id>
      11             <goals>
      12               <goal>enforce</goal>
      13             </goals>
      14             <configuration>
      15               <rules>
      16                 <bannedDependencies>
      17                   <excludes>
      18                     <exclude>commons-logging</exclude>
      19                     <exclude>cglib:cglib</exclude>
      20                   </excludes>
      21                 </bannedDependencies>
      23               </rules>
      24               <fail>true</fail>
      25             </configuration>
      26           </execution>
      27         </executions>
      28       </plugin>
      29     </plugins>
      30 </build>
      31 </project>
    3. As I mentioned, using the Enforcer plugin won’t exclude the unwanted dependencies, it only will fail the build. Once that happened (and trust me, it will), you need to go and exclude them manually, as described in Stage 1 above.

And we are done with Maven. Not fun? Switch your build tool!

Ivy

Well, comparing to Maven it’s emabrassing how easy is to add global exclusion in Ivy. All you need to do is add exclude tag, and it will do the job for all the transitive dependencies, both in current and future use:

1 <dependencies>
2     <dependency org="org.springframework" name="spring-webmvc"
3 rev="3.0.5.RELEASE" conf="compile->default"/>
4     <exclude org="commons-logging"/>
5 </dependencies>

Done.

Gradle

Since Gradle uses Ivy under the hood, here comes the same ease, but even groovier:

1     configurations {
2         all*.exclude module: 'commons-logging'
3         all*.exclude group: 'cglib', module: 'cglib-nodep'
4     }

That’s all! Now your code is bullet-proof from classloading conflicts and you can do nasty class-replacing stuff, for logging or pleasure.

Written by JBaruch

22/06/2011 at 08:39

Posted in Build

Tagged with , , ,

Follow

Get every new post delivered to your Inbox.

Join 1,067 other followers