Spring-boot: build in Gradle 2.3 runs more tasks than in Gradle 2.2

Created on 18 Mar 2015  路  21Comments  路  Source: spring-projects/spring-boot

I'm not sure if this is entirely due to Gradle, and there's nothing we can do about it, or if our plugin's involved.

With Gradle 2.2:

$ ./gradlew build
:compileJava
:processResources UP-TO-DATE
:classes
:jar
:bootRepackage
:assemble
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build

With Gradle 2.3:

$ ./gradlew build
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar
:findMainClass
:startScripts
:distTar
:distZip
:bootRepackage
:assemble
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build

Note the additional findMainClass, startScripts, distTar, and distZip tasks that have been run with Gradle 2.3

Question on StackOverflow that brought this to my attention.

Most helpful comment

@jeffbswope You can make Gradle 2.3 happy by configuring mainClassName with any value. For example, this build script works fine:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.2.2.RELEASE'
    }
}

apply plugin: 'spring-boot'

bootRepackage {
    enabled = false
}

mainClassName = 'com.foo.Bar'

You'll end up with start scripts that don't work (as they point to a non-existent main class) but, as I said above, I'm not sure what we can do about that given the change in Gradle's behaviour.

All 21 comments

Removing Boot's plugin, and making no other changes, makes Gradle 2.3 behave the same as Gradle 2.2, i.e. it's our plugin that's causing the different behaviour. I wonder if this could be related to bootRepackage depending on the distribution tasks (#2622), although that doesn't appear to explain the behaviour change between Gradle 2.2 and 2.3.

This appears to break builds which rely on bootRepackage.enabled = false. The :startScripts task is still queued and fails for lack of a main class.

@philwebb I think we should tackle this in 1.2.x. Do you agree?

Sounds good.

The change is due to a change in the application plugin's behaviour between Gradle 2.2 and 2.3.

In my comment above I stated that the behaviour in Gradle 2.3 was the same as Gradle 2.2 when you removed the Spring Boot plugin. This wasn't really a fair comparison as removing the Spring Boot plugin also removed Gradle's application plugin.

Consider this build script:

apply plugin: 'application'

It fails with Gradle 2.3:

gradle build
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:startScripts FAILED

FAILURE: Build failed with an exception.

* What went wrong:
A problem was found with the configuration of task ':startScripts'.
> No value has been specified for property 'mainClassName'.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 3.124 secs

But works fine with Gradle 2.2.1:

gradle build
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar
:assemble
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build

BUILD SUCCESSFUL

Total time: 3.332 secs

I'm not sure that there's anything that we can do, particularly in 1.2.x, to address this pretty fundamental change in Gradle's behaviour

@jeffbswope You can make Gradle 2.3 happy by configuring mainClassName with any value. For example, this build script works fine:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.2.2.RELEASE'
    }
}

apply plugin: 'spring-boot'

bootRepackage {
    enabled = false
}

mainClassName = 'com.foo.Bar'

You'll end up with start scripts that don't work (as they point to a non-existent main class) but, as I said above, I'm not sure what we can do about that given the change in Gradle's behaviour.

Thanks @wilkinsona

This is entirely too bound to the specifics of the problem but also seems to work as a workaround without creating invalid start scripts:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.2.2.RELEASE'
    }
}

apply plugin: 'spring-boot'

bootRepackage {
    enabled = false
}

gradle.taskGraph.whenReady { graph ->
    def badTasks = ['findMainClass', 'startScripts', 'distTar', 'distZip']
    graph.allTasks.findAll {it.name in badTasks}.each { task ->
        task.enabled = false
    }
}

The reality is that the dependency management plugin being spun off likely eliminates our need/desire to use the boot plugin with bootRepackage disabled for our libs, but have not dug into making that change yet.

I've posted on the Gradle forum about the regression caused by the new behaviour in Gradle 2.3. Given that we have a workaround, I'd like to wait and see what the response is before deciding what, if anything, we should do about this.

So I don't forget, I prototyped this as a solution:

project.afterEvaluate {
    if (!project.plugins.hasPlugin(ApplicationPlugin)) {
        project.plugins.apply(ApplicationPlugin)
        project.tasks.withType(CreateStartScripts) { CreateStartScripts task ->
            task.enabled = false
        }
    }
}

The idea was to switch off the problematic pieces of the application plugin unless the user has explicitly applied the application plugin. I'm not sure it's a _good_ solution, but it did get the build working.

Can I ask why there is a dependency on the application plugin? It makes assumptions about how the distributions plugin works, that means that an app using the spring-boot gradle plugin has to also inherit the immutable CopySpec from the application plugin, and cannot create it's own unique distributions.

There is an outstanding pull request to make this configurable: https://github.com/gradle/gradle/pull/264.

Would it be actually be better to find a way to remove the dependency to reduce coupling and hidden behaviours?

@stuartstevenson With hindsight, and particularly in light of the regression/behaviour change in the application plugin in Gradle 2.3, that's probably what we'd choose to do now. And it may well be what we end up doing to resolve this issue and #2622 in Spring Boot 1.3

thanks for the quick response @wilkinsona , is there anything I can do to help out?

Thanks for the offer. The first step is probably for us to reach agreement on the solution. At first glance, moving away from the application plugin seems like the right thing to do, but I'm not sure how much of its functionality we'd end up duplicating in order to continue to support the bootRun task. Some exploration in that direction would be much appreciated.

I've had a look through the spring-boot-gradle-plugin and seen that there are two usages of the application plugin.

  1. In RunPluginFeatures.mainClassNameFinder it loops over all tasks and when it finds CreateStartScripts in the application plugin, it adds a dependency on that task on the FindMainClassTask.
  2. In FindMainClassTask.findMainClass the code checks the SpringBootExtension for the main class name but also the application plugin ApplicationPluginConvention to try to get the main class name, and then it tries to find a task named 'run' from the application plugin to extract the main class name from that. At the end it sets the mainClassName on botht he SpringBootExtension and the runTask.

I've tried deleting the used code on my fork and the project builds. What I'm not sure about is to what extent the FindMainClassTask relies upon the configuration of the application plugin's task in order to work. The run task also has to be configured by the user as the default value for the main class name is null. It seems like the code is trying to work with the application plugin, rather than rely on it for any unique functionality that isn't provided by SpringBootExtension.

From Luke Daley on the Gradle forum (I'd link to it, but the thread seems to have been lost in the migration to Discourse):

If you're able to make the decision not to set the main class name, you can remove the distribution archives at this time with:

[distZip, distTar].each { task ->  configurations.archives.artifacts.removeAll { it.class.simpleName == "ArchivePublishArtifact" && it.archiveTask == task }
  task.enabled = false
}

Unfortunately, ArchivePublishArtifact is internal so this is touching internals. That said, this type has leaked in other ways so it will be a long time before it moves/changes if it ever does at all.

The only alternatives I can come up with for you (that aren't conditionally applying the application plugin in the first place) are a bit too loose and risk messing with the user's adhoc configuration.

I have the same issue and was wondering if this issue will be resolved soon, any news?

I've started new Spring Cloud + Spring Boot project until hit this issue and now I'm considering to implement it with Spring Framework only.
It is not good idea to downgrade Gradle from 2.5 to 2.2 and the proposed fix is not working, because we use subprojects with ApplicationPlugin enabled.

Any plans to make this issue a major priority?

Thank you!

@DanailMinchev Have you tried the suggestion from Luke Daley?

Also, if the solution that I proposed does not work, then your problem is with Gradle, not Spring Boot as the application plugin is now integrated with the distribution plugin. You're applying the ApplicationPlugin directly so you're getting Gradle's standard behaviour for 2.3 and later.

@wilkinsona thank you for the fix and the information!

With these new changes, it would be great to get some guidance on how to use bootRepackage in conjunction with distTar since I believe creating a distribution tarball with a repackaged jar with wrapper scripts is a common use case. If you agree, perhaps we can open another ticket? Much thanks!

That's really orthogonal to this issue. While we've changed the dependencies of the bootRepackage task, I don't think there's any difference in what distTar will generate between 1.2 and 1.3.

I'm somewhat reluctant to try and cover this in Boot's reference guide as Gradle's distribution plugin is still incubating so we'd be documenting a moving target.

That said, feel free to open a new issue. If someone figures out the configuration that's required (I think it'll involve configuring the contents CopySpec and, possibly, customising the startup scripts), and it seems stable across a number of version of Gradle, then we could consider adding it to the docs.

Perhaps a StackOverflow question is best. The reason I mention it here is that I was hit with this issue upgrading from Gradle 2.2 to 2.5 today. Between M5 of 1.3.0 and Gradle 2.5, I'm not really sure what the recommended course is for my setup. Thanks for your comment!

Was this page helpful?
0 / 5 - 0 ratings