How to include an artifact from one Gradle project as a resource in another project?

Given a project file structure like this:

git-root
 ├── ServerApp
 │    ├── build.gradle
 │    └── src
 │        └── ...
 └── ClientJAR
     ├── build.gradle
     └── src
         └── ...

ServerApp needs to include an artifact of ClientJAR‘s build (the default artifact) as one of its resources. There is no dependency between ServerApp and ClientJAR in the traditional sense – neither project references code from the other.

As the diagram shows, the two Gradle projects are siblings in a single git repo. Changing that structure (eg, to make this a container/composite build) is only a last resort; I’m looking for a solution that keeps this structure intact.

Some Background/Context

The intention is that the application built by ServerApp has an endpoint that can be used to download the artifact that was built in ClientJAR project. The most obvious way (to me) to implement that is to have ClientJAR‘s artifact be embedded into ServerApp (which is a Spring Boot application) as a resource at build time.

I’ve tried using includeFlat and declaring ClientJAR as a dependency in ServerApp, but that caused the “plain” ClientJAR JAR artifact to be included in ServerApp‘s libs, which isn’t what I need. I need the bootJar artifact (which is currently the result of calling gradlew build in ClientJAR).

Is there a clean way to “embed” a sibling like this? Even a concrete example of one build invoking another (sibling) and then copying its output into the /build/resources folder would be a reasonable option (I think).

I’m still testing and checking, but here is what I got working using a combination of custom GradleBuild and Copy tasks.

In ServerApp I added these custom tasks:

task buildClient(type: GradleBuild) {
    buildFile="../ClientJAR/build.gradle"
    tasks = ['clean', 'build']
}

task copyClient(type: Copy, dependsOn: buildClient) {
    from ('../ClientJAR/build/libs/') {
        include 'client-*.jar'
        rename { 'client.jar' }
    }
    into "$buildDir/resources/main"
}

I also declared that processResources should depend on copyClient:

processResources {
    dependsOn copyClient
}

With that, client.jar ends up in ServerApp/build/resources/main/ (and in the final Boot WAR artifact) as expected.

You can do this in three steps:

  1. Link the build of the projects together so they can reference one another;
  2. Obtain the desired JAR in the ServerApp build by depending on a ClientJAR configuration; and
  3. Copy the JAR to the location it needs to be in resources.

1. Link the build of the projects together

As you have already done, you can achieve this with the use of includeFlat:

// ServerApp/settings.gradle

includeFlat("ClientJAR")

2. Obtain the JAR in the consuming project via new configurations

Gradle have instructions for sharing artifacts in this way. To summarise, you can do it in four steps:

(a) Add a new consumable configuration to the producer project (ClientJAR):

// ClientJAR/build.gradle

configurations {
   bootJarConsumable {
      canBeConsumed = true
      canBeResolved = false
   }
}

(b) Add the desired artifact to it, using the Jar task which generates bootJar (assumed called bootJarTaskName):

// ClientJAR/build.gradle

artifacts {
   bootJarConsumable(tasks.named('bootJarTaskName'))
}

(c) Add a new configuration to ServerJAR for loading the JAR

// ServerApp/build.gradle

configurations {
   bootJarResolvable {
      canBeConsumed = false
      canBeResolved = true
   }
}

(d) Depend on the artifact in consuming project by depending on the new configuration:

// ServerApp/build.gradle

dependencies {
   bootJarResolvable project(path: ':ClientJAR', configuration: 'bootJarConsumable')
}

3. Copy the JAR to the location it needs to be in resources

ServerApp then has access to the JAR, but it is not in the desired location. You can write a task to do the final copy step:

// ServerApp/build.gradle

tasks.register('copyToResources', Copy) {
    from configurations.named('bootJarResolvable')
    into sourceSets.main.resources.sourceDirectories.singleFile
}

Finally, you need to ensure the processResources task depends on this so all everything will run when the ServerApp build is run:

// ServerApp/build.gradle

processResources {
    dependsOn copyToResources
}

Leave a Comment