This plugin is an opinionated approach to configure a Gradle project automatically by convention. It supports to automatically configure various plugins to reduce boilerplate code in Gradle projects.
As an example, instead of applying the Kotlin Plugin manually, the AutoConfigure-Plugin does that automatically for you
when we detect a folder src/main/kotlin
inside your module, and we also automatically configure the Kotlin
plugin with sensible defaults (encoding, JVM runtime).
The idea is similar to Spring Boot, where beans are being auto-generated and auto-configured based on the current classpath.
This plugin requires at least Gradle 7.4 and Java 17. You can still configure this plugin to use Java 8 (or any other Java version) to compile your codebase.
There are two different ways how to apply the AutoConfigure plugin:
- As normal project plugin inside
build.gradle
orbuild.gradle.kts
- As settings-plugin inside
settings.gradle
orsettings.gradle.kts
Where the latter approach has the advantage that we also include the awesome Reckon-Plugin for semantic versioning
You can apply that plugin via the Gradle Plugin Portal
by adding the following block to your build.gradle.kts
:
plugins {
id("io.cloudflight.autoconfigure-gradle") version "0.8.3"
}
This plugin is meant to be applied only at the root module of your project - never on sub-modules if you have one. All the plugin does by itself is scan the module and all sub-modules and apply any of the internal plugins mentioned below depending on the layout. Instead of requiring you to apply the same default over and over again, we do that automatically for you.
Alternatively, you can also apply the AutoConfigure-Plugin as Settings plugin in your settings.gradle.kts
:
plugins {
id("io.cloudflight.autoconfigure-settings") version "0.8.3"
}
In case your code is running on a CI-Server, we will then automatically apply the Reckon-Plugin for you in and pre-configure it with the following defaults:
reckon {
stages('rc', 'final')
scopeCalc = calcScopeFromProp()
stageCalc = calcStageFromProp()
defaultInferredScope = 'patch'
}
If this is fine for you, you don't need to add anything to your settings.gradle
, but feel free to override
this with any of your desired values.
In any way, you can omit the version
property from your build.gradle
file then, Reckon will take care of it.
If you are not running on a CI-Server (i.e. you are doing local development), the Reckon-Plugin is NOT applied, instead
we set the version to the -SNAPSHOT
or the last reckoned version as described in this ticket (but you're free to override that in the build.gradle
temporarily).
That means if the Reckon plugin would create 0.8.5-rc.0.0+20221111T075331Z
, we convert that to 0.8.5-SNAPSHOT
locally. The reason for this behaviour is this ticket.
In any case, when applying the Settings-Plugin, we will also automatically apply the io.cloudflight.autoconfigure-gradle
plugin for you, so you don't need
to do that on your own.
A good starting point to understand how this plugin works might be to go through our samples of real-life applications.
This plugin consists of multiple sub-plugins, all of which are being applied automatically when
certain functionality is being detected (i.e. we automatically apply the Java Plugin when we detect
a source folder src/main/java
).
If the plugin detects either the folder src/main/java
or src/test/java
in a module, it automatically
applies the java-library
plugin and applies the following configuration:
You can provide some configuration to this plugin by adding the following block to
your build.gradle.kts
:
javaConfigure {
languageVersion.set(JavaLanguageVersion.of(17))
vendorName.set("Cloudflight")
applicationBuild.set(false)
encoding.set("UTF-8")
createSourceArtifacts.set(false)
}
The usage of those properties will be explained in the sections below:
Based on the langageVersion
in your JavaConfigurePluginExtension
we will set the Java Toolchain
for you and also use it in the Manifest, the default being Java 17.
The encoding will be set automatically to all source sets, the default being UTF-8.
The test task is automatically configured with useJUnitPlatform()
to use the JUnit5 Platform per default. You do need to add the necessary dependencies yourself as described here.
To override the used testing platform use:
tasks.test {
useJUnit()
}
This plugin also automatically applies the Jacoco-Plugin in order to collect code-coverage information during our unit- and integration tests.
Call gradle testCodeCoverageReport
to get a multi-module coverage report over all your modules in the
build/reports/jacoco/testCodeCoverageReport/html
directory of your root module.
We automatically configure the task jar.manifest
in order to create manifests like that:
Manifest-Version: 1.0
Class-Path: jsr305-3.0.2.jar
Created-By: 17.0.2 (Eclipse Adoptium)
Implementation-Vendor: your-vendor-name
Implementation-Title: my-module
Implementation-Version: 0.0.1
Gradle-Version: 7.3.3
- The
Class-Path
is created automatically from yourruntime
configuration Gradle-Version
andCreated-By
are being taken from your current environmentImplementation-Version
is being set to the currentproject.version
Implementation-Title
is yourproject.name
- The
Implementation-Vendor
can be set by configuring thevendorName
in theJavaConfigurePluginExtension
(see above)
Per default the source and javadoc jars will be generated for all builds that have the property applicationBuild
set to false
.
If you want to change that behavior you can manually set the createSourceArtifacts
depending on the desired outcome.
If the folder src/testFixtures
exists, we automatically apply the plugin java-test-fixtures
,
you don't need to apply it on your own.
If the plugin detects either the folder src/main/kotlin
or src/test/kotlin
in a module, it automatically
applies the Kotlin Gradle Plugin and applies the following configuration:
You can provide some configuration to this plugin by adding the following block to
your build.gradle.kts
:
kotlinConfigure {
kotlinVersion.set("1.6.10")
}
The Kotlin plugin also automatically applies the Java Plugin with all its configuration possibilities. Use the configuration there to adjust the JDK or the Manifest entries.
Per default, we are using the latest version of the Kotlin Plugin (1.8.0) to compile your Kotlin-Code. Still, you can specify an older version (i.e. 1.5.20), which results in the following behaviour:
- The Kotlin StdLib of the version
1.5.20
is being added to the classpath - The
apiVersion
andlanguageVersion
is set to1.5
, see this link for more details - The setting
-Xjsr305=strict
is being added to the Kotlin Compiler options, see this link for more details
Apply the plugin io.cloudflight.autoconfigure.node-configure
in order to configure the Gradle Node Plugin
plugins {
id("io.cloudflight.autoconfigure.node-configure")
}
You can configure this plugin with the NodeConfigurePluginExtension
as follows:
nodeConfigure {
nodeVersion = "16.15.1"
downloadNode = true
npm {
npmVersion = "6.14.10"
}
}
These values shown here are also the default values, so you can omit them, i.e. if you're fine with this values simply don't configure anything.
You then automatically get the following tasks:
Task | Description |
---|---|
clfNpmBuild |
Runs npm run build . Automatically being called during compilation. |
clfNpmBuildDev |
Runs npm run build:dev . Use this target to have live reloading supported. |
clfNpmLint |
Runs npm run lint . Automatically being called in the check-phase (right after compilation). |
clfNpmTest |
Runs npm run test . This task is automatically being attached to the test target, if your package.json contains a test script. |
clfNpmUpdateVersion |
Updates the version in your package.json to the current value in build.gradle . This task is only being called in CI-Servers automatically |
The Node-Configure-Plugin takes care about dependencies between this tasks, as well as inputs and outputs and up-to-date-handling.
When it comes to the definition of APIs over HTTP, Swagger/OpenAPI is the defacto-standard nowadays. Code generation is an important part there, and there exist a couple of libraries out there, to generate code from or to OpenAPI specifications.
This plugin preconfigures both the for API generation Swagger Gradle Plugin and the Swagger Generator Plugin for code generation.
While both of them are really powerful and flexible, they all have some downsides:
- They resolve dependencies too early and do not play well with the Java Platform Plugin for central dependency constraints.
- It’s not easy to configure the plugins in a way to have the task order applied in the correct way (i.e. which task is being dependent on what when it comes to code generation)
- Their dynamic configuration makes it hard to provide sensible defaults.
- They are coming with a lot of transitive dependencies, some of them incompatible with important other plugins like the Spring Boot plugin.
With some extra-code within this plugin we managed to find a solution that fullfils all these requirements while keeping the flexibility of the underlying plugins.
In order to support all possibible use cases, there is one important thing to understand: Swagger/OpenAPI specifictions (yaml or json) always need to be in another module than the code that is being generated out of it.
So we always see the whole setup as two steps:
- Manage your API specifications (either by generating API spcifications from code, or by importing external specifications)
- Generate code from Swagger/OpenAPI
Our recommended way to work with OpenAPI clients is to write APIs interfaces and DTOs in Java or Kotlin code, annotate them with Spring and OpenAPI annotations and then generate the OpenAPI specification from there, and use that in turn to generate other clients like for Typescript.
The module skeleton-api
only contains Java/Kotlin interfaces along with their DTOs and all
Spring MVC annotations to define the API contract to that server.
The according build.gradle
file looks like that:
plugins {
id "io.cloudflight.autoconfigure.swagger-api-configure"
}
dependencies {
implementation 'io.swagger:swagger-annotations'
implementation 'org.springframework:spring-web'
}
By doing this, the build will generate API definitions in JSON and YAML format to the directory skeleton-api/build/generated-resources/openapi
.
The task which is doing this, is called clfGenerateSwaggerDocumentation
and it is triggered
automatically before calling the jar
task.
This task wraps the GenerateSwaggerDocsTask
of the plugin mentioned above and configures it automatically with those values:
swagger {
apiSource {
locations = [
project.group + '.api',
project.group + '.api.dto'
]
}
}
That means, if the groupId of your project (to be set in the root build.gradle
)
is io.cloudflight.skeleton
, then your API interfaces need to be in the package io.cloudflight.skeleton.api
(or a subpackage of it)
and your DTOs in the package io.cloudflight.skeleton.api.dto
.
Additionally, the Autoconfigure Gradle Plugin will create an additional maven publication, that means the YAML and JSON files are also being published to your artifact repository like Nexus and you can fetch it from there like JAR files.
In case you have to embed an external OpenAPI specification, or for any reason you want to maintain your own OpenAPI specification instead of writing code, you can do that as well.
In order to take part in the advanced dependency resolution that have been implemented within this plugin on top of the official Swagger plugin, you have to put those OpenAPI files into a separate gradle/maven module:
Your module structure might look like that:
externalsystem-api
build.gradle
myopenapifile.yml
myproject-server
build.gradle
myproject-ui
package.json
build.gradle
We are focussing on the module externalsystem-api
now, the build.gradle
looks as follows:
apply plugin: 'java'
artifacts.add(JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME, file('myopenapifile.yml')) {
name project.name
classifier 'swagger'
type 'yaml'
}
- We need to apply the Java plugin manually here, as we don’t have a folder
src/main/java
here. - Refer to your OpenAPI or Swagger configuration file and create a publication.
- If you OpenAPI or Swagger configuration file is provided in JSON format, you can alternatively specify json as type here.
That’s it. Your OpenAPI specification (Whereever it comes from) is now registered and can be used later as reference for code generation.
Now that we have defined our API modules, we can use them to generate code from it. To achieve that, simply apply the
plugin io.cloudflight.autoconfigure.swagger-codegen-configure
and create a dependency within the configuration
swaggerApi
to your API module.
This plugin applies the necessary swaggerSources configuration for the Swagger Generator Plugin so that you don't have to do anything extra. The minimal configuration is nothing more than that:
plugins {
id 'io.cloudflight.autoconfigure.swagger-codegen-configure'
}
dependencies {
swaggerApi project(':api')
}
Dependending of the layout of the module which applies io.cloudflight.autoconfigure.swagger-codegen-configure
, code is
being generated with the following defaults (the rules are evaluated top down, first match is being applied):
Rule | swaggerGenerator |
swaggerLibrary |
---|---|---|
The Node plugin is applied | typescript-angular |
null |
Module name ends with -client |
spring |
spring-cloud |
Default | spring |
spring-boot |
Target packages are derived from the project group
.
In rare occasions the applied defaults do not work, in that case you can simply add or overwrite the missing configuration as you would if you were using the Swagger Generator Plugin without the AutoConfigure Plugin:
swaggerSources {
'externalsystem-api' {
code {
additionalProperties = [
'delegatePattern' : 'true'
]
}
}
}
Please note that the configuration name (externalsystem-api
in that case) has to match the name of the referenced module or dependency.
A more complex configuration would be:
swaggerSources {
'hub-api-definition' {
code {
language = "java"
library = "resttemplate"
components = [apis: true, apiTests: false, supportingFiles: true]
additionalProperties = [
'invokerPackage': "io.cloudflight.commons.hub",
'apiPackage' : "io.cloudflight.commons.hub.client",
'modelPackage' : "io.cloudflight.commons.hub.dto"
]
}
}
}
Per default, we are using the module io.swagger.codegen.v3:swagger-codegen-cli:3.0.34
as code generator library.
There are two ways to override that:
- override the property
swaggerCodegenCliVersion
in yourbuild.gradle
to use another version ofio.swagger.codegen.v3:swagger-codegen-cli
.
swaggerCodgenConfigure {
swaggerCodegenCliVersion = "3.0.30"
}
- define your own generator within the
swaggerCodegen
as described in the official plugin documentation.
typescript-angular
is the default generator for node projects. This is not what you want in case you are using other frameworks.
To change the generator, override the property nodeSwaggerGenerator
in your build.gradle
.
swaggerCodgenConfigure {
nodeSwaggerGenerator = "typescript-fetch"
}
You can also define your own artifacts containg mustache templates for code generation, simply add a dependency within the swaggerTemplate
configuration
and refer to a JAR that contains the updates mustache files:
dependencies {
swaggerApi project(':api')
swaggerTemplate "<your-jar-with-mustache-templates>"
}
This autoconfigures the Gradle plugin provided by the SpringDoc OpenApi project.
SpringDoc OpenApi has a different approach than the above explained swagger plugin. It needs a running Spring Boot application that serves the OpenAPI document.
The autoconfiguration for this plugin is applied as follows:
plugins {
id 'io.cloudflight.autoconfigure.springdoc-openapi-configure'
}
The plugin has to be applied to a module that provides a Spring Boot application which the plugin will try to start using a custom Spring Boot run configuration.
This custom run configuration will be started in a dummy working directory to work around a issue currently present in the springdoc plugin. You can find more information here.
The dummy working directory is created with a task called createDummyForkedSpringBootWorkingDir
. Various other tasks are automatically configured to depend on this, since they access the dummy directory for some reason.
If you run into some problems with the task, try adding it as a dependency to your task by adding the following.
tasks.named("your-task-name") {
dependsOn("createDummyForkedSpringBootWorkingDir")
}
If you have multiple tasks that need to depend on it you can do:
def taskList = ["your-task-1", "your-task-2"]
tasks.matching { taskList.contains(it.name) }.all {
dependsOn("createDummyForkedSpringBootWorkingDir")
}
The springdoc plugin is automatically configured to generate the open-api spec in YAML
format. If you prefer the JSON
format you can easily change that by using our extension:
import io.cloudflight.gradle.autoconfigure.springdoc.openapi.OpenApiFormat
openApiConfigure {
fileFormat = OpenApiFormat.JSON
}
For generating the OpenAPI document the task clfGenerateOpenApiDocumentation
has to be run.
To provide custom configuration just add the openApi extension configuration block. See https://github.com/springdoc/springdoc-openapi-gradle-plugin#customization.
openApi {
outputDir=buildDir
}
In multi-module projects, each of the above mentioned plugins can be configured on a per-module basis,
but you can also use the autoConfigure
extension of this plugin to set defaults for all submodules without
iterating through all modules by yourself.
To achieve that, add the following block to your root module build.gradle.kts
in a multi-module project:
autoConfigure {
java {
languageVersion.set(JavaLanguageVersion.of(17))
encoding.set("UTF-16")
vendorName.set("My cool company")
}
kotlin {
kotlinVersion.set("1.5.31")
}
}
Each property here comes with an equivalent to the above mentioned sub-plugins. If you set a default value on the root module here, and override it with a more specific value in the sub-module, then the latter will always win.
To reduce unnecessary code for setting the version and group of all sub-modules we automatically set the version and group
of all sub-modules to the values defined for the root-module.
If you want to have a different version or group, just define it in the sub-module. Any values defined in the build.gradle
of a sub-module will not be overridden.
version = "1.1.1"
group = "com.project.module.other.group"