1

I have a Gradle project with two subprojects. The parent does not contain any code; all the Kotlin code is in the two subprojects. All Gradle build files are defined in the Kotlin DSL.

Upon building, Gradle generates two JAR files, one in the build subfolder of each subproject. I believe this is the intended default behavior of Gradle. But this is not what I want.

I want to publish the JAR file of the parent project as a Maven artifact. Therefore, I need both subprojects to be included in one JAR file. How can I achieve this?

Note: On this web page, the author seems to achieve pretty much what I would need in this code snippet:

apply plugin: "java"

subprojects.each { subproject -> evaluationDependsOn(subproject.path)}

task allJar(type: Jar, dependsOn: subprojects.jar) {
    baseName = 'multiproject-test'
    subprojects.each { subproject ->
        from subproject.configurations.archives.allArtifacts.files.collect {
            zipTree(it)
        }
    }
}

artifacts {
    archives allJar
}

However, this is defined in Gradle's native Groovy DSL. And I find myself unable to translate it into the Kotlin DSL. I tried to put a Groovy build file (*.gradle) besides the Kotlin build file (*.gradle.kts), but this led to a strange build error. I'm not sure if mixed build file languages are supported. Besides, I would consider it bad practice too. Better only define all build files in just one language.

Also, the example above pertains to the Java programming language. But I do not expect this to be a big problem, as both Java and Kotlin produce JVM bytecode as compile output.

More clarification:

  • I am not talking about a "fat JAR". Dependencies and the Kotlin library are not supposed to be included in the JAR.
  • I do not care if the JAR files for the subprojects are still getting built or not. I'm only interested in the integrated JAR that contains both subprojects.
  • The main point is getting the combined JAR for the binaries. Combined JARs for the sources and JavaDoc would be a nice-to-have, but are not strictly required.
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Madoc
  • 5,841
  • 4
  • 25
  • 38
  • the way the gradle `maven-publish` plugin is usually epected to work is to have a artifact on maven per (sub-)project ... you can make the parent project also publish a artifact that just declares dependencies to make using it easy to use – Nikky Sep 03 '22 at 18:01
  • @Nikky Yes right, that's how it was conceived. But in my project, we use sub-projects in order to isolate certain architectural layers from each other. Still, we want to publish everything as one artifact in the end. I know this is possible because of the Groovy snippet I found, I just don't know how to translate it into the Kotlin DSL. – Madoc Sep 03 '22 at 23:05

1 Answers1

2

I would use the Gradle guide Creating "uber" or "fat" JARs from the Gradle documentation as a basis. What you want is essentially the same thing. It's also much better than the Groovy example you found, as it doesn't use the discouraged subprojects util, or 'simple sharing' that requires knowing how the other projects are configured.

  1. Create a configuration for resolving other projects.

    // build.gradle.kts
    
    val mergedJar by configurations.creating<Configuration> {
      // we're going to resolve this config here, in this project
      isCanBeResolved = true 
      // this configuration will not be consumed by other projects
      isCanBeConsumed = false 
      // don't make this visible to other projects
      isVisible = false 
    }
    
  2. Use the new configuration to add dependencies on the projects we want to add into our combined Jar

    dependencies {
      mergedJar(project(":my-subproject-alpha"))
      mergedJar(project(":my-subproject-beta"))
    }
    
  3. Now copy the guide from the docs, except instead of using configurations.runtimeClasspath we can use the mergedJar configuration, which will only create the subprojects we specified.

    However we need to make some modifications.

    1. I've adjusted the example to edit the existing Jar task rather than creating a new 'fatJar' task.
    2. for some reason, setting isTransitive = false causes Gradle to fail resolution. Instead I've added a filter (it.path.contains(rootDir.path)) to make sure the Jars we're consuming are inside the project.
    tasks.jar {
      dependsOn(mergedJar)
    
      from({
        mergedJar
          .filter {
            it.name.endsWith("jar") && it.path.contains(rootDir.path)
          }
          .map {
            logger.lifecycle("depending on $it")
            zipTree(it)
          }
      })
    }
    
aSemy
  • 5,485
  • 2
  • 25
  • 51
  • Thanks a lot for your answer! I just got around to trying it out. Currently, Gradle does not like the `mergedJar` keyword; it says "Unresolved reference`. What am I missing? – Madoc Sep 19 '22 at 11:55
  • I noticed that what you called `mergedJar` in one place was called `uberJar` on initialization. – Madoc Sep 19 '22 at 12:05