如何对Spring Boot Gradle插件进行参数化?

huangapple go评论69阅读模式
英文:

How do you parameterize the Spring Boot Gradle plugin?

问题

我们正在考虑从Maven迁移到Gradle,并且已经解决了大部分替换父POM概念的挑战。但有一个难题我们还没有解决。我们需要在全局范围内指定正在使用的Spring Boot版本,但是我尝试过的两种解决方案都遇到了构建文件无效的问题:

  • 我尝试将 plugins { id 'org.springframework.boot' version '2.1.17.RELEASE' } 声明放入通用构建脚本中。构建错误"只有项目和设置构建脚本才能包含 plugins {} 块。"
  • 我尝试调用通用构建文件以指定 springBootVersion 参数,并在插件声明中使用它。构建错误"只有在 plugins {} 块之前允许使用 buildscript {} 和其他 plugins {} 脚本块,不允许使用其他语句。"

如果我可以简单地使用 apply plugin: 'org.springframework.boot' 就会更容易,但是Gradle无法找到该插件。除一个微服务外,所有微服务都使用相同版本的Spring Boot,我们希望如果可能的话能够进行全局升级。

附加信息

  • 我有大约40个微服务以及一些被这些服务使用的库。
  • 每个微服务都有一个单独的存储库,因此常规的父/子方法不适用。
  • Maven父POM允许您将该POM发布为自己的资源,在Gradle中没有相应的功能。
  • Gradle的 pluginManagement 概念对我们也不起作用,因为它可以解析Spring Boot插件,但现在无法找到依赖管理插件。

这里是我的通用构建脚本:

repositories {
    mavenLocal()
	
    /* 移除了我们内部的存储库 */

	jcenter()
    mavenCentral()
}

apply plugin: 'java'
apply plugin: 'jacoco'
apply plugin: 'maven-publish'
apply plugin: 'io.spring.dependency-management'

group = 'nedl-unified-platform'

/* 将Spring Boot微服务发布到存储库所需 */
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 // 测试运行后始终生成报告
}

jacocoTestCoverageVerification {
    violationRules {
        rule {
            limit {
                minimum = 0.2
            }
        }
    }
}

jacocoTestReport {
    dependsOn test // 必须在生成报告之前运行测试
	
    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 {
		/* 为保护隐私和简洁性,省略了内部Maven存储库 */
	}
}

这由我们的项目构建脚本调用,我想要参数化:

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'
英文:

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'

答案1

得分: 0

我不确定我完全理解你的问题,但你应该使用Gradle的方式来共享配置:使用根项目配置。

不要在每个项目中包含通用的构建脚本,而是创建一个全局项目,并在这里设置配置。

root
|
| --- projectA
| --- projectB
| --- projectC

使用相应的 settings.gradle

include 'projectA'
include 'projectB'
include 'projectC'

在根 build.gradle 中设置版本

ext.springBootVersion = '2.1.17.RELEASE'

在使用 springBoot 的子项目中,比如 projectB,在子 build.gradle 中应用插件

buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
}
}
apply plugin: 'org.springframework.boot'
英文:

I'm not sure I understood your issue perfectly but you should use the Gradle way for sharing configuration : the root project config.

Instead of including the common build script in every project, create a global project and set the configuration here.

root
|
| --- projectA
| --- projectB
| --- projectC

With the according settings.gradle

include 'projectA'
include 'projectB'
include 'projectC'

In the root build.gradle, set up the version

ext.springBootVersion = '2.1.17.RELEASE'

In subprojects using springBoot, let's say projectB, apply the plugin in the sub build.gradle

buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
}
}
apply plugin: 'org.springframework.boot'

答案2

得分: 0

我认为这里的差异在于,Maven中有父pom的概念,而在Gradle中没有。像你所说的那样,这并没有一一对应的关系,但在Gradle中你可以使用插件,并应用一个插件。

最接近的情况是,你可以开发自己的Gradle插件,然后你的每个项目都可以应用它。你的自定义插件可以配置Spring Boot以及其他所有项目共有的内容。这个插件会定义你希望所有其他项目使用的Spring Boot版本。

如果自定义插件只关注配置Spring Boot,那么它可能不会带来太多好处,它还需要做其他事情。如果你没有太多在这方面的经验,创建一个Gradle插件可能会很困难。你会失去所有熟悉的build.gradle的语法,而且实际上必须编写代码(虽然有一些相似之处,但我发现这很困难),如果可能的话,我会避免这样做。

我建议你首先直接将spring boot插件应用到一个微服务项目中,让它正常工作,然后再处理下一个项目。在完成了一些项目之后,你就能够看到它们之间的共同之处,以及是否值得投资开发一个全局插件。不过你确实需要小心,因为全局插件可能既是一种福音也可能是一种诅咒。它可能会减少维护人员的大量手动工作,但如果做错了,会给他们带来麻烦,然后他们可能会想回到Maven。

我不确定我是否理解你全局定义的Spring版本要求。除非你使用SNAPSHOT依赖/插件(不推荐这样做),或者在存储库之外有一个黑魔法的settings.gradle,否则你必须在某个地方放置一些版本。作为替代方案,你可以创建自己的自定义任务,该任务在check生命周期上运行,检查Spring(或你的插件)的版本,如果不是最新版本,则会打印警告,并鼓励开发人员进行升级。

额外信息
可以通过将属性放在gradle.properties中,如 springBootVersion=2.1.17.RELEASE,来为插件设置参数。

英文:

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 .

答案3

得分: 0

这个示例对我有效,尽管我可能不理解所有约束。

如果我们将Spring Boot的版本抽象到一个固定的URI后面(例如在内部CI/CD服务器上),那么在每个项目/仓库的build.gradle中考虑以下内容:

buildscript {
    repositories {
        jcenter()
    }

    def SPRING_BOOT_VERSION_URI = 'http://localhost:5151/api-server/spring-boot.txt'
    ext.springBootVersion = new URL(SPRING_BOOT_VERSION_URI).getText().trim()

    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
    }
}

apply plugin: 'org.springframework.boot'

apply from: "../common/build.gradle"

我意识到原始问题说明apply plugin不起作用,但我不确定这是否排除了这种方法。

最后,注意将这个方法扩展为不仅仅是一个简单的文本文件,可以将其扩展为更正式的JSON规范(根据团队的需求定制)。

英文:

This example works for me, though I may not understand all of the constraints.

If we abstract the version of Spring Boot behind a fixed URI (e.g. on an internal CI/CD server), then consider this in each project/repo's build.gradle:

buildscript {
repositories {
jcenter()
}
def SPRING_BOOT_VERSION_URI = 'http://localhost:5151/api-server/spring-boot.txt'
ext.springBootVersion = new URL(SPRING_BOOT_VERSION_URI).getText().trim()
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
}
}
apply plugin: 'org.springframework.boot'
apply from: "../common/build.gradle"

I realize the original question states that the apply plugin doesn't work, but it's not clear to me if that precludes this method.

Finally, note that it is easy to expand this beyond a simple text-file to be a more formal JSON specification (tailored to the teams' needs).

答案4

得分: 0

将这部分添加到根项目中,所有子项目应该能够从相同的一组Spring Boot依赖中进行导入。其中的关键部分是 allprojects 块:

buildscript {
    repositories {
        maven { url "https://plugins.gradle.org/m2/" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

ext {
  springBootVersion = '2.3.4.RELEASE'
}

allprojects {
    apply plugin: 'java'
    apply plugin: 'io.spring.dependency-management'

    dependencyManagement {
        imports {
            mavenBom("org.springframework.boot:spring-boot-dependencies:${springBootVersion}")
        }
    }
}
apply plugin: 'org.springframework.boot'
英文:

If you add this to the root project, all child projects should be able to just import from the same set of Spring Boot dependencies. The magic ingredient is the allprojects block:

buildscript {
    repositories {
        maven { url "https://plugins.gradle.org/m2/" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

ext {
  springBootVersion = '2.3.4.RELEASE'
}

allprojects {
    apply plugin: 'java'
    apply plugin: 'io.spring.dependency-management'

    dependencyManagement {
        imports {
            mavenBom("org.springframework.boot:spring-boot-dependencies:${springBootVersion}")
        }
    }
}
apply plugin: 'org.springframework.boot'

huangapple
  • 本文由 发表于 2020年10月16日 21:14:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/64389906.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定