We are looking to migrate from Maven to Gradle, and have worked through most of the challenges you would expect for replacing the parent POM concept. There is one sticky point that we haven’t figured out yet. We need to specify the version of Spring Boot we are using globally, but I run into invalid build file problems with both of the solutions I’ve tried:
- I tried putting the
plugins { id 'org.springframework.boot' version '2.1.17.RELEASE' }
declaration in the common build script. Build error, “Only Project and Settings build scripts can contain plugins {} blocks.” - I tried calling the common build file to specify the
springBootVersion
parameter and using that in the plugins declaration. Build Error, “only buildscript {} and other plugins {} script blocks are allowed before plugins {} blocks, no other statements are allowed”
All of this would be easier if I could simply apply plugin: 'org.springframework.boot'
but then Gradle can’t find the plugin. All but one microservice are on a single version of Spring Boot, and we want to be able to upgrade globally if possible.
Additional Information
- I have ~40 microservices plus some libraries used by those services
- Separate repository for each of them, so the normal parent/child approach does not work
- Maven parent POMs allowed you to publish that POM as it’s own resource, and there is no 1:1 equivalent feature in Gradle
- Gradle
pluginManagement
concept also doesn’t work for us because it resolves the Spring Boot plugin but the dependency management plugin now can’t be found.
My common build script is included here:
repositories { mavenLocal() /* Removed our internal repositories */ jcenter() mavenCentral() } apply plugin: 'java' apply plugin: 'jacoco' apply plugin: 'maven-publish' apply plugin: 'io.spring.dependency-management' group = 'nedl-unified-platform' /* Required to publish Spring Boot microservices to publish to repository */ configurations { [apiElements, runtimeElements].each { it.outgoing.artifacts.removeIf { it.buildDependencies.getDependencies(null).contains(jar) } it.outgoing.artifact(bootJar) } } java { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 withJavadocJar() withSourcesJar() } ext { set('springBootVersion', '2.1.17.RELEASE') set('springCloudVersion', "Greenwich.SR6") } dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" } } jacoco { toolVersion = "0.8.5" reportsDir = file("$buildDir/reports/jacoco") } test { finalizedBy jacocoTestReport // report is always generated after tests run } jacocoTestCoverageVerification { violationRules { rule { limit { minimum = 0.2 } } } } jacocoTestReport { dependsOn test // tests are required to run before generating the report reports { xml.enabled true html.destination file("${reportsDir}/jacocoHtml") xml.destination file("${reportsDir}/jacocoReport.xml") } } tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } publishing { publications { maven(MavenPublication) { from components.java } } repositories { /* excluded for privacy and brevity's sake, our internal Maven repo */ } }
And that is called by our project build script that I want to parameterize:
plugins { id 'org.springframework.boot' version springBootVersion } apply from: "https://mycentral.repo/project-common/develop/build.gradle" dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server' implementation 'ch.qos.logback:logback-classic' implementation 'javax.annotation:javax.annotation-api:1.3.2' implementation 'javax.xml.bind:jaxb-api:2.4.0-b180830.0359' implementation 'org.glassfish.jaxb:jaxb-runtime:2.4.0-b180830.0438' testImplementation 'org.springframework.boot:spring-boot-starter-test' } version = '0.0.2-SNAPSHOT'
Advertisement
Answer
I think the gap here is that in maven you have the concept of a parent pom
, whereas in Gradle you don’t. There is no 1:1 mapping to this like you say, but you can have plugins in Gradle, and apply a plugin.
The closest thing you would have is if you developed your own Gradle plugin, which each of your projects could apply. Your custom plugin would then configure Spring Boot among whatever else is common to all your projects. This plugin would define the version of Spring Boot you want all your other projects to use.
You wouldn’t get much benefit to a custom plugin if it’s only concern is configuring Spring Boot, it would need to do other things as well. It can be difficult to create a Gradle plugin when you don’t have allot of experience in it. You lose all the familiar syntax to the build.gradle
and you literally have to write code, (there are some similarities but I have found it difficult), I would avoid it if possible.
I would suggest you start off by applying the spring boot plugin directly to one of your microservices projects, get that working, then do another. After you have done a number of them you will then be able to see what is common between them, and if it is indeed worth investing into developing a global plugin. You really need to be careful though because your global plugin has the potential to be both a blessing and curse. It may take away allot of manual work for maintainers, but if you get it wrong it will cause them grief, and then they will want to go back to maven.
I’m not sure if I understand your globally defined Spring version requirement. Unless you are using SNAPSHOT dependencies/plugins (bad don’t do that), (or a black magic settings.gralde outside of your repo), you will have to put some version somewhere. As an alternative you could create your own custom task which runs on the check
lifecycle which will check the version of spring (or your plugin) and print a warning if it’s not the latest version, and encourage the developer to upgrade.
Extra Info
Parameterizing plugins with properties can be done putting your property in gradle.properties
as springBootVersion=2.1.17.RELEASE
.