Spring-boot: Explore the possibility of making `./gradlew build` run `bootJar` rather than `jar`

Created on 5 Apr 2017  Â·  12Comments  Â·  Source: spring-projects/spring-boot

As things stand running build runs the jar task rather than bootJar. This happens because the jar task's artifact is added to the archives configuration, assemble depends upon every task that builds an artifact in the archives configuration, and build depends upon assemble. bootJar would be run as part of build if bootJar's artifact was added to the archives configuration, however this would have the unwanted downside of causing both the jar and the bootJar tasks to be run.

Instead of bootJar adding its output to archives it adds it to a new configuration named bootArchives. Using a separate configuration makes it easy for a project dependency to reference either the jar artifact or the bootJar artifact. It also allows the plugin to provide the uploadBootArchives task to publish a fat jar to a Maven repository. We don't want to lose any of these capabilities.

I think the ideal would be for bootJar to somehow take the place of jar so that build, by default, would run bootJar instead of jar. We would still want the jar task to be available should anyone wish to build the project's normal jar. We'd also, ideally, still want the separate arrangement of configurations so that the fat jar or the normal jar can be depended upon or published independently.

enhancement

Most helpful comment

@jeffbswope The couple of lines that you've shown above for a library will, I think, do what you want. However, if you're building a library and just want "certain guarantees about compatibility with the Spring Boot applications inside and outside this build" then I wouldn't apply the Spring Boot plugin at all. Instead, I'd apply the dependency management plugin and import the spring-boot-dependencies bom for the Spring Boot version with which you want to be compatible.

Something like this:

```groovy
plugins {
id "io.spring.dependency-management" version "1.0.2.RELEASE"
}

dependencyManagement {
imports {
mavenBom 'org.springframework.boot:spring-boot-dependencies:2.0.0.BUILD-SNAPSHOT'
}
}

All 12 comments

What about keeping jar task as it is and add the bootJar talk by default to build to generate another jar with another filename like shadow plugin does?

Boot's plugin's current arrangement is, by design, very similar to the Shadow plugin's. Like Boot's plugin, the Shadow plugin doesn't generate its jar when build is run. You need to specifically run shadowJar or set up some additional task dependencies.

One difference that the Shadow plugin does have is that shadowJar is configured with a classifier by default, whereas bootJar is not. This means that if both jar and shadowJar do run you'll get two separate jars, rather than one overwriting the other.

After some consultation with the Gradle team (thanks again @bmuschko and @eriwen) the recommendation is to configure assemble to depend upon bootJar (or bootWar) and disable jar (or war). This will allow build to create the executable jar or war without also creating the normal jar or war. It's also easy for a user to reverse. If they want both the standard archive and the executable archive then they can explicitly enable the jar or war task in their build script.

@daggerok Would you like to expand on your 👎 ? What don't you like about the proposal and what would you like to see us do instead?

@wilkinsona actually I removed my -1 but as a plugin user, I would like to have less gradle task configurations like dependsOn, task.enabled = false, but more magic using bootJar configuration:

  1. something similar to previous version: executable = true
  2. if regular jar file is not needed, it would be enough to have config, like executableOnly = true, which is by default can be executableOnly = executable

so usage will looks like (in this case I want build both - executable and regular jars):

bootJar {
  executable = true
  executableOnly = false
}

same for bootWar... what do you think?

out of scope:

output will be:

build/libs/my-app-0.1.5.jar
build/libs/my-app-0.1.5-bin.jar // executable

bash build/libs/*bin.jar

in this case it would be nice to have possibility to manage suffix, for example:

bootJar {
  suffix = 'bin' // default
}

or executable jar can have it's own folder:

build/libs/my-app-0.1.5.jar
build/bin/my-app-0.1.5.jar // executable

bash build/bin/*.jar

in this case we can manage prefix instead of suffix, for example using config:

bootJar {
  prefix = 'bin' // default
}

ps: but thinks about suffix, prefix (binDir) are out of scope and can be ignored, I just wanted to finish my idea... my point is doing same as it was in previous version or do all needed configuration in single closure and avoid other configurations like:

bootJar {
  launchScript {
    included = true
  }
}
assemble.dependsOn bootJar
jar.enabled = false

because the only feature we wanna achieve is create executable jar, so why we can't say just executable = true?

br
/max

@daggerok Thanks for the detailed feedback.

something similar to previous version: executable = true

We already have this. It's now:

bootJar {
    launchScript {
        included = true
    }
}

It's nested one level more deeply to provide some grouping for including the launch script and all configuring its properties.

so usage will looks like (in this case I want build both - executable and regular jars)

This would require the BootJar task to be able to produce multiple outputs. That's something that we wanted to avoid as it prevents it from (easily) being a subclass of Gradle's Jar task.

in this case it would be nice to have possibility to manage suffix

A benefit of being a Jar subclass, is that this can be done via the standard classifier property.

or executable jar can have its own folder

You can configure that using the standard destinationDir property.

because the only feature we wanna achieve is create executable jar, so why we can't say just executable = true

When the change proposed in this issue has been implemented setting up the task dependency and disabling the jar task will be taken care of automatically. As a result, all you'll need for your usecase is this:

bootJar {
  launchScript {
    included = true
  }
}

I suspect what we are doing is ill-advised more generally but...

We have a multi-module build which includes various libraries and also sample applications for those libraries.

The spring-boot-focused libraries use the Spring Boot Plugin (this may be our core mistake but historically makes dependency management easier and certain guarantees about compatibility with the Spring Boot applications inside and outside this build that use them) and have always used bootRepackage { enabled = false } on the libraries such that everything works as we imagine.

In our case then we could just gradle build (or other downstream tasks) and get normal jars for the libraries and fat jars for the applications, as desired.

I'm not sure I see how to accomplish that easily now if we must run different tasks for libraries than applications or make intricate build script changes turning tasks on/off and changing task dependencies.

Seems ideal, as you indicate, that bootJar could simply replace jar by default -- but have that behavior easily configurable in the build somehow so we can declare our desired outcome and be (somewhat) insulated from future plugin or Gradle changes.

Edit:

Reading above more closely it seems like the default will be:

assemble.dependsOn bootJar jar.enabled = false

So a library (no fat jar) just needs additionally:

bootJar.enabled = false jar.enabled = true

Which seems to work with snapshot and is not bad.

@jeffbswope The couple of lines that you've shown above for a library will, I think, do what you want. However, if you're building a library and just want "certain guarantees about compatibility with the Spring Boot applications inside and outside this build" then I wouldn't apply the Spring Boot plugin at all. Instead, I'd apply the dependency management plugin and import the spring-boot-dependencies bom for the Spring Boot version with which you want to be compatible.

Something like this:

```groovy
plugins {
id "io.spring.dependency-management" version "1.0.2.RELEASE"
}

dependencyManagement {
imports {
mavenBom 'org.springframework.boot:spring-boot-dependencies:2.0.0.BUILD-SNAPSHOT'
}
}

@wilkinsona Thanks. This project predates the nice separation of dependency management from the boot plugin, so it's far past time do what's required to stop applying it on our boot libraries and rely on the dependency-management plugin alone during our 2.x migration.

@wilkinsona we have a project which creates an executable jar (via bootJar) with a classifier.
We also have an Artifactory plugin which publishes all artifacts from the archives configuration.
Now that the spring-boot plugin automatically disables the jar task, when we run a build we end up having the regular jar specified as an artifact (with a specific file path) in the archives configuration - but because the jar task is skipped, this file is never created and the artifactoryPublish task fails.

I solved our issue easily by explicitly enabling the jar task. However I believe this is just one symptom of a more general problem.

I would expect that if the spring-boot plugin disables the jar task it would also remove the jar artifact from the archives configuration.

I suspect that having the jar artifact still present in the archives configuration even though the artifact never gets created, might create other strange behaviors for all kinds of scenarios where scripts or plugins depend by default on the contents of the archive configuration.

There's a balance to be struck here, and I think that removing the output of the jar task from the archives configuration may move things towards being too cumbersome to re-enable. My expectation thus far had been that if the output of the contents of the archives configuration is of interest, then the jar task would also have been enabled. That appears to hold true for your scenario.

Thanks for the concern and suggestion, but until we encounter a real – rather than hypothetical – scenario where that's not the case I'd prefer to leave things as they are.

I found usefull for me configuration which right now will also work, but as I expect

plugins {
    id 'org.springframework.boot' version '1.5.3.RELEASE'
    id 'io.spring.dependency-management' version '1.0.2.RELEASE'
}

repositories {
    mavenCentral()
    maven { url "https://repo.spring.io/snapshot" }
    maven { url "https://repo.spring.io/milestone" }
}

// dependencies and other configuration are skipped...

springBoot {
    executable = true
}

dependencyManagement {
    imports {
        mavenBom 'org.springframework.boot:spring-boot-dependencies:2.0.0.BUILD-SNAPSHOT'
        mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Finchley.BUILD-SNAPSHOT'
    }
}

in this case I can use latest spring version within lovely gradle spring plugin configuration:

springBoot {
    executable = true
}

I can use milestone versions of spring-web / spring-webflux and on project build jar task will not be skipped and jar itself will be executable

worked example

Was this page helpful?
0 / 5 - 0 ratings