War plugin - handling of duplicated artifact names

plugins

(Johannes Porzelt) #1

Hi all,

currently we are migrating a build from Maven to Gradle. So far everything worked out pretty smooth and we are already see a lot of benefits of the migration (faster build times, less build description code, less plugins needed).

Now we are facing the following problem:
We have a multi module maven build where various modules were building artifacts with the same name.
E.g:
Module A
groupId: com.company.moduleA
artifactId: app-adapter

Module B
groupId: com.company.moduleB
artifactId: app-adapter

Both producing an artifact named: app-adapter-1.0.0.jar

The maven-war-plugin handled this by prefixing the conflicting jar files with their groupId.
So we have:

  • WEB-INF/lib/com.company.moduleA-app-adapter-.jar
  • WEB-INF/lib/com.company.moduleB-app-adapter-.jar

With Gradle this is a different story:
In the war file we see all those jar files with the same name and when it is extracted only one of them wins on the file system.
Resulting in missing libraries.
Tested with Gradle 4.1 and 3.3

Sure we can handle this in our application by renaming the artifactsIds to be unique. But this wouldn’t solve library conflicts that might come through transitive dependencies. Thinking of something like util-1.0.0.jar or common-1.0.0.jar

Is there any similar approach in Gradle like in Maven? Google did not help so far…

Any suggestion, opinion, solution is welcome :wink:

Cheers,
Johannes


(Johannes Porzelt) #2

we already discussed the approach of modifying the archivesBaseName like:

archivesBaseName = "${project.group}-${project.name}”

But since we are also publishing those artifacts to our nexus repo this is not a satisfying solution.


(uklance) #3

You could probably do this via Configuration.getResolvedArtifacts()

Eg:

task copyAndDedup() {
    doLast {
        Map<String, List<ResolvedArtifact>> groupedArtifacts = [:]
        configurations.runtime.resolvedConfiguration.resolvedArtifacts.each { ResolvedArtifact artifact ->
            String aName = artifact.moduleVersion.id.name
            List<ResolvedArtifact> group = groupedArtifacts[name] ?: []
            group << artifact
            groupedArtifacts[name] = group
        }
        File outputDir = file("$buildDir/copyAndDedup")

        // copy all non-jars from the 2runtime" configuration
        copy {
            from configurations.runtime.asFileTree.matching {
                exclude '*.jar'
            }
        }
        groupedArtifacts.values().each { List<ResolvedArtifact> group ->
             if (group.size() == 1) {
                 // don't need to rename this jar
                 copy {
                     from group[0].file
                     into outputDir
                 }
             } else {
                 // two or more artifacts with the same name, rename
                 group.each { ResolvedArtifact artifact ->
                     def moduleVersionId = artifact.moduleVersion.id
                     copy {
                         from artifact.file
                         into outputDir
                         rename ".*", "${moduleVersionId.group}-${moduleVersionId.name}-${moduleVersionId.version}.jar"
                     }
                 }
             }
        }
    }
}

war {
   dependsOn copyAndDedup
   classpath = fileTree("$buildDir/copyAndDedup")
}

Warning! Not compiled or tested
Inspiration taken from here


(Johannes Porzelt) #4

Hi Lance,

thanks a lot for your reply. Looks like a reasonable workaround for the problem.
We also had a long discussion within the company and in the end we agreed on changing our artifactIds.

Anyways I have the opinion if the unique identifier for a maven artifact is the combination of groupId+artifactId+version (I already learned that this was not the case some time ago), the war plugin should be able to properly handle this.


(uklance) #5

I agree that the default behavior of ignoring one of the jars is not great. It would be nice to have a configurable hook for a custom merge strategy. You could catch this in future by failing, not ideal but arguably better

Eg:

war {
   duplicatesStrategy = DuplicatesStrategy.FAIL
}

(Johannes Porzelt) #6

Thank you, this I will definitely add.