How to depend on WAR file built by project, not the jar file?


(davidmichaelkarr) #1

I have a multiproject build, where two subprojects build WAR files, and another subproject constructs a Docker image, using Ben Muschko’s “gradle-docker-plugin”. That last subproject depends on the other two subprojects (along with a JDBC jar), and attempts to copy the WAR files produced by those subprojects into the Docker context required by “gradle-docker-plugin”.

My problem is, when I copy the dependencies, it’s actually only getting the “.jar” file produced by the subproject, not the “.war” file. What do I have to do to get the actual artifact that I need?

For instance, here is an excerpt of my “dependencies” block:
dependencies { runtime project(":ordersService") runtime project(":ordersGUI") runtime ... }

And thanks to “Steve973” on IRC, this is the mechanism I’m using to copy the dependent artifacts:
configurations.runtime.setTransitive(false).resolvedConfiguration.resolvedArtifacts .each { artifact -> project.copy { from artifact.file into "${buildDir}/context" rename { "${artifact.name}.${artifact.extension}" } } }

Note that I strip off the version number from the artifact file names, so my Dockerfile can reference the raw artifact name.

The problem is, this ends up copying the “.jar” artifact, not the “.war” artifact. How can I specify that I depend on the war file produced by the subproject, not the jar file?


(davidmichaelkarr) #2

I guess what I’m trying to do is use a classifier (“war”), but on a project, not a GAV. Is it possible to do this? I tried simply specifying the obvious ‘project(":projname@war")’, but that didn’t work.


(Mark Vieira) #3

You can still depend on a particular configuration when using project dependencies.

configurations {
    war {}
}

artifacts {
    war tasks.war
}

Then your dependency notation would look like:

project(path: ':projname', configuration: 'war')

(davidmichaelkarr) #4

I don’t see how I would use that. The WAR file in the other project is produced by using the “war” plugin. Are you saying I should define a “war” configuration in that project, along with this “artifacts” block? How would I form the name of the war artifact in the “artifacts” block?


(Mark Vieira) #5

Simply add the snipped I posted above. I’m not sure what you mean by “form the name of the war artifact”. The artifacts { } block here simply adds the file produced by the war task to the war configuration.


(davidmichaelkarr) #6

Ok, that appears to work. I never paid much attention to the “publishing artifacts” chapter. I now understand it a bit better.

However, I did run into another problem with this, concerning project build order.

My dependencies block now looks like this:
dependencies { runtime project(path: ":ordersService", configuration: "war") runtime project(path: ":ordersGUI", configuration: "war") runtime "oracle:ojdbc6:11.2.0.3" }

I also have a block controlling task dependencies, like this:
build.dependsOn copyDependencies build.dependsOn buildImage buildImage.dependsOn copyDependencies

The “copyDependencies” task copies the dependent artifacts, along with some other config files, to a local dir. The “buildImage” task, from the “gradle-docker-plugin”, builds a Docker image from the files in that local dir. I describe this just for background.

When I run “gradle build” from the root dir, I see this (elided):
:assemble UP-TO-DATE :check UP-TO-DATE :build UP-TO-DATE :ordersGUI:compileJava UP-TO-DATE :ordersGUI:processResources UP-TO-DATE :ordersGUI:classes UP-TO-DATE :ordersGUI:war :ordersGUI:assemble :ordersGUI:compileTestJava UP-TO-DATE :ordersGUI:processTestResources UP-TO-DATE :ordersGUI:testClasses UP-TO-DATE :ordersGUI:test UP-TO-DATE :ordersGUI:check UP-TO-DATE :ordersGUI:build :ordersImage:compileJava UP-TO-DATE :ordersImage:processResources UP-TO-DATE :ordersImage:classes UP-TO-DATE :ordersImage:jar :ordersImage:assemble :ordersImage:copyDependencies :ordersImage:buildImage Building image using context '.../ordersImage/build/context'. ... Step 12 : ADD ordersService.war ${TOMEE_HOME}/webapps/ :ordersImage:buildImage FAILED

Note that it first built the “ordersGUI” subproject, and then the “ordersImage” subproject. The latter build failed because the artifact produced by the “ordersService” subproject wasn’t there, apparently because the build order Gradle determined would have built ordersService AFTER ordersImage, which is too late.

I would have thought that if the ordersImage declared a dependency to a configuration in the ordersService project, it would have resulted in the ordersService project being built before that project.

What might I be missing?


(Mark Vieira) #7

Does buildImage have a dependency on the ‘runtime’ configuration? Your project doesn’t depend on anything, configurations do. If ‘buildImage’ needs that artifact then the configuration containing that artifact should be an input to that task.


(davidmichaelkarr) #8

I’m familiar with tasks depending on other tasks. You seem to be implying that “configurations” can depend on things (“configurations do”). I’m not sure what to do with that information. You then say that I need to specify that my task depends on a configuration by “being an input to that task”. How do I do that exactly?

My present dependencies and task definition look like this:
`dependencies {
runtime project(path: “:ordersService”, configuration: “war”)
runtime project(path: “:ordersGUI”, configuration: “war”)
runtime “oracle:ojdbc6:11.2.0.3”
}

task buildImage(type: DockerBuildImage) {
inputDir = file("${buildDir}/context")
imageId = “ssorderprocessingdashboard”
}`

I actually have another task called “copyDependencies” that copies the built WARs, so technically it’s that task that should be depending on those other projects being built, so I need to determine how I can provide the two configurations as “input to the task”.

If it matters, this is what that task looks like:
`task copyDependencies << {
project.copy {
from "src/docker"
into "${buildDir}/context"
include "Dockerfile"
include ".properties"
include "
.txt"
include “.excerpt"
include "
.sh”
}

configurations.runtime.setTransitive(false).resolvedConfiguration.resolvedArtifacts
.each { artifact ->
project.copy {
from artifact.file
into "${buildDir}/context"
rename { “${artifact.name}.${artifact.extension}” }
}
}
}`


Subproject depends on configuration from other subproject, but subproject build order doesn't respect that
(davidmichaelkarr) #9

Ok. I’ve figured this out, or at least I found a solution that’s somewhat along the lines of what you’re saying. Your reply confused me, however. Before this, I knew that tasks could depend on other tasks, and that’s it. You’re making it sound like there’s other dependencies related to tasks that I can configure. After digging into this more, I think that’s not the case, but please correct me if I’ve gone in the wrong direction here (even though I built something that works).

In any case, adding ‘dependsOn: [":ordersService:build", “:ordersGUI:build”]’ to the “copyDependencies” task makes this work. Note that I’m depending on the “build” task, not the “war” task. It seems better to me to depend on a higher-order task, to reduce the coupling with the other project.


(Mark Vieira) #10

Yes, a task can depend on anything that implements Buildable. In this case if I depend on a configuration (i.e. configurations.compile) and that configuration contains project dependencies, this gets translated to the task depending on the other tasks in that project responsible for building those artifacts. In most cases, this means and implied dependency on the ‘jar’ task.

Why would this be better? The ‘build’ task builds everything including running all tests. Your task needs only the ‘war’ so you are potentially doing unnecessary work. It now means it is not possible to build this project w/o running tests for the dependent projects without doing something like explicitly excluding those tasks.

You are correct that depending on those tasks is not ideal. You should be depending on the configuration which contains those project dependencies instead as mentioned above.