Skip to content

Commit b3845bc

Browse files
authored
minor: (#173) remove incorrect Json2YamlTask and make the output format configurable (#175)
* minor: (#173) remove incorrect Json2YamlTask and make the output format configurable * update docs * add workaround for lazy task configuration of the `generateOpenApiDocs` task --------- Signed-off-by: Clemens Grabmann <[email protected]>
1 parent 3d632f9 commit b3845bc

File tree

14 files changed

+249
-121
lines changed

14 files changed

+249
-121
lines changed

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,37 @@ plugins {
466466
}
467467
```
468468

469-
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.
469+
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.
470+
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](https://github.com/cloudflightio/autoconfigure-gradle-plugin/issues/171).
471+
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.
472+
473+
<details>
474+
<summary>If you run into some problems with the task, try adding it as a dependency to your task by adding the following.</summary>
475+
476+
```groovy
477+
tasks.named("your-task-name") {
478+
dependsOn("createDummyForkedSpringBootWorkingDir")
479+
}
480+
```
481+
482+
If you have multiple tasks that need to depend on it you can do:
483+
484+
```groovy
485+
def taskList = ["your-task-1", "your-task-2"]
486+
tasks.matching { taskList.contains(it.name) }.all {
487+
dependsOn("createDummyForkedSpringBootWorkingDir")
488+
}
489+
```
490+
</details>
491+
492+
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:
493+
```groovy
494+
import io.cloudflight.gradle.autoconfigure.springdoc.openapi.OpenApiFormat
495+
496+
openApiConfigure {
497+
fileFormat = OpenApiFormat.JSON
498+
}
499+
```
470500

471501
For generating the OpenAPI document the task `clfGenerateOpenApiDocumentation` has to be run.
472502

src/main/kotlin/io/cloudflight/gradle/autoconfigure/extentions/gradle/api/NamedDomainObjectSetExt.kt

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,32 @@
11
package io.cloudflight.gradle.autoconfigure.extentions.gradle.api.tasks
22

3+
import org.gradle.api.DomainObjectCollection
4+
import org.gradle.api.NamedDomainObjectProvider
5+
import org.gradle.api.NamedDomainObjectSet
36
import org.gradle.api.Task
47
import org.gradle.api.tasks.TaskCollection
58
import org.gradle.api.tasks.TaskProvider
69
import kotlin.reflect.KClass
710

11+
12+
/**
13+
* @see TaskCollection.withType
14+
*/
15+
internal fun <T : Task, S : T> TaskCollection<T>.withType(klass: KClass<S>): NamedDomainObjectSet<S> = this.withType(klass.java)
16+
17+
18+
/**
19+
* @see TaskCollection.withType
20+
*/
21+
internal fun <T : Task, S : T> TaskCollection<T>.withType(klass: KClass<S>, configuration: (it: S) -> Unit): DomainObjectCollection<S> = this.withType(klass.java, configuration)
22+
23+
/**
24+
* @see TaskCollection.named
25+
*/
26+
internal fun <T : Task, S : T> TaskCollection<T>.named(name: String, klass: KClass<S>): TaskProvider<S> = this.named(name, klass.java)
27+
28+
829
/**
930
* @see TaskCollection.named
1031
*/
11-
internal fun <T : Task, S : T> TaskCollection<T>.named(name: String, klass: KClass<S>): TaskProvider<S> = this.named(name, klass.java)
32+
internal fun <T: Task, S : T> TaskCollection<T>.named(name: String, klass: KClass<S>, configuration: (it: S) -> Unit): NamedDomainObjectProvider<S> = this.named(name, klass.java, configuration)

src/main/kotlin/io/cloudflight/gradle/autoconfigure/springdoc/openapi/Json2YamlTask.kt

Lines changed: 0 additions & 35 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.cloudflight.gradle.autoconfigure.springdoc.openapi
2+
3+
import org.gradle.api.provider.Property
4+
5+
enum class OpenApiFormat(val extension: String) {
6+
YAML("yaml"),
7+
JSON("json")
8+
}
9+
10+
abstract class SpringDocOpenApiConfigureExtension {
11+
abstract val fileFormat: Property<OpenApiFormat>
12+
}

src/main/kotlin/io/cloudflight/gradle/autoconfigure/springdoc/openapi/SpringDocOpenApiConfigurePlugin.kt

Lines changed: 70 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package io.cloudflight.gradle.autoconfigure.springdoc.openapi
22

33
import com.github.psxpaul.task.JavaExecFork
44
import io.cloudflight.gradle.autoconfigure.AutoConfigureGradlePlugin.Companion.TASK_GROUP
5-
import io.cloudflight.gradle.autoconfigure.extentions.gradle.api.named
6-
import io.cloudflight.gradle.autoconfigure.extentions.gradle.api.withType
5+
import io.cloudflight.gradle.autoconfigure.extentions.gradle.api.tasks.named
6+
import io.cloudflight.gradle.autoconfigure.extentions.gradle.api.tasks.withType
77
import io.cloudflight.gradle.autoconfigure.java.JavaConfigurePlugin
88
import io.cloudflight.gradle.autoconfigure.util.addApiDocumentationPublication
99
import org.gradle.api.Plugin
@@ -14,6 +14,7 @@ import org.gradle.api.tasks.TaskProvider
1414
import org.slf4j.Logger
1515
import org.slf4j.LoggerFactory
1616
import org.springdoc.openapi.gradle.plugin.OpenApiExtension
17+
import org.springdoc.openapi.gradle.plugin.OpenApiGeneratorTask
1718
import org.springdoc.openapi.gradle.plugin.OpenApiGradlePlugin
1819
import org.springframework.boot.gradle.plugin.SpringBootPlugin
1920
import java.net.ServerSocket
@@ -25,21 +26,16 @@ class SpringDocOpenApiConfigurePlugin : Plugin<Project> {
2526
target.plugins.apply(SpringBootPlugin::class.java)
2627
target.plugins.apply(OpenApiGradlePlugin::class.java)
2728

29+
val extension = target.extensions.create(EXTENSION_NAME, SpringDocOpenApiConfigureExtension::class.java)
30+
extension.fileFormat.convention(OpenApiFormat.YAML);
2831
val openapi = target.extensions.getByType(OpenApiExtension::class.java)
29-
configureOpenApiExtension(openapi, target, target.name)
30-
val openApiTask = target.tasks.named("generateOpenApiDocs")
31-
val json2Yaml: TaskProvider<out Task> =
32-
target.tasks.register("clfJsonToYaml", Json2YamlTask::class.java) { task ->
33-
with(openapi) {
34-
task.inputFile.set(outputDir.file(outputFileName))
35-
task.outputFile.set(outputDir.file(outputFileName.map { it.replace(".json", ".yaml") }))
36-
task.dependsOn(openApiTask)
37-
}
38-
}
32+
configureOpenApiExtension(openapi, extension, target, target.name)
33+
val openApiTask = target.tasks.named("generateOpenApiDocs", OpenApiGeneratorTask::class)
34+
makeOpenApiTaskReactive(openApiTask, openapi)
3935

4036
val documentationTask = target.tasks.register("clfGenerateOpenApiDocumentation") {
4137
it.group = TASK_GROUP
42-
it.dependsOn(json2Yaml)
38+
it.dependsOn(openApiTask)
4339
}
4440

4541
target.tasks.withType(GenerateMavenPom::class) {
@@ -49,9 +45,25 @@ class SpringDocOpenApiConfigurePlugin : Plugin<Project> {
4945
`setupWorkaroundFor#171`(target, openapi)
5046

5147
target.afterEvaluate {
52-
configureJsonDocumentPublishing(openapi, target, openApiTask)
53-
configureYamlDocumentPublishing(target, openapi, json2Yaml)
48+
configureDocumentPublishing(openapi, target, openApiTask)
49+
}
50+
}
51+
52+
private fun makeOpenApiTaskReactive(openApiTask: TaskProvider<OpenApiGeneratorTask>, openapi: OpenApiExtension) {
53+
// for some reason the springdoc plugin reads the values from the extension during task initialization and uses that as convention for the task properties,
54+
// which results in some values being incorrect. Because of that we reconfigure the properties conventions to directly use the extension properties. And
55+
// add conventions to the extension properties to the expected default values see: https://github.com/springdoc/springdoc-openapi-gradle-plugin/blob/master/src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGeneratorTask.kt#L46
56+
openApiTask.configure {
57+
it.apiDocsUrl.convention(openapi.apiDocsUrl)
58+
it.outputFileName.convention(openapi.outputFileName)
59+
it.groupedApiMappings.convention(openapi.groupedApiMappings)
60+
it.outputDir.convention(openapi.outputDir)
5461
}
62+
63+
openapi.apiDocsUrl.convention("http://localhost:8080/v3/api-docs")
64+
openapi.outputFileName.convention("openapi.json")
65+
openapi.groupedApiMappings.convention(emptyMap())
66+
openapi.outputDir.convention(openApiTask.flatMap { it.project.layout.buildDirectory })
5567
}
5668

5769
private fun `setupWorkaroundFor#171`(target: Project, openapi: OpenApiExtension) {
@@ -72,7 +84,7 @@ class SpringDocOpenApiConfigurePlugin : Plugin<Project> {
7284
// these tasks also need to depend on the createDirTask since they somehow access the dummy folder as well
7385
val dependingTaskNames = setOf("resolveMainClassName", "processResources", "compileKotlin", "compileJava")
7486

75-
target.tasks.matching { dependingTaskNames.contains(it.name) }.configureEach {
87+
target.tasks.matching { dependingTaskNames.contains(it.name) }.all {
7688
it.dependsOn(createDirTask)
7789
}
7890

@@ -83,26 +95,34 @@ class SpringDocOpenApiConfigurePlugin : Plugin<Project> {
8395

8496
private fun configureOpenApiExtension(
8597
openapi: OpenApiExtension,
98+
configureExtension: SpringDocOpenApiConfigureExtension,
8699
target: Project,
87100
basename: String
88101
) {
89-
with(openapi) {
90-
val serverPort = freeServerSocketPort()
91-
val managementPort = freeServerSocketPort()
92-
93-
outputDir.set(target.layout.buildDirectory.dir("generated/resources/openapi"))
94-
outputFileName.set("${basename}.json")
95-
apiDocsUrl.set("http://localhost:${serverPort}/v3/api-docs")
96-
customBootRun {
97-
it.workingDir.set(target.layout.buildDirectory.dir("dummyForkedSpringBootWorkingDir"))
102+
val serverPort = freeServerSocketPort()
103+
val managementPort = freeServerSocketPort()
104+
val outputFileName = configureExtension.fileFormat.map { "${basename}.${it.extension}" }
105+
val docsUrl = openapi.outputFileName.map {
106+
val basePath = "http://localhost:${serverPort}/v3/api-docs"
107+
when {
108+
it.endsWith(".${OpenApiFormat.JSON.extension}") -> basePath
109+
it.endsWith(".${OpenApiFormat.YAML.extension}") -> "${basePath}.${OpenApiFormat.YAML.extension}"
110+
else -> throw UnsupportedFormatException("The provided openapi filename '${it}' ends in an unsupported extension. Make sure you use 'yaml' or 'json'")
98111
}
112+
}
99113

100-
mapOf(
101-
"--server.port" to serverPort,
102-
"--management.server.port" to managementPort
103-
).forEach { arg ->
104-
customBootRun.args.add("${arg.key}=${arg.value}")
105-
}
114+
openapi.outputDir.set(target.layout.buildDirectory.dir("generated/resources/openapi"))
115+
openapi.outputFileName.set(outputFileName)
116+
openapi.apiDocsUrl.set(docsUrl)
117+
openapi.customBootRun {
118+
it.workingDir.set(target.layout.buildDirectory.dir("dummyForkedSpringBootWorkingDir"))
119+
}
120+
121+
mapOf(
122+
"--server.port" to serverPort,
123+
"--management.server.port" to managementPort
124+
).forEach { arg ->
125+
openapi.customBootRun.args.add("${arg.key}=${arg.value}")
106126
}
107127
}
108128

@@ -113,37 +133,36 @@ class SpringDocOpenApiConfigurePlugin : Plugin<Project> {
113133
}
114134
}
115135

116-
private fun configureJsonDocumentPublishing(
136+
private fun configureDocumentPublishing(
117137
openapi: OpenApiExtension,
118138
target: Project,
119-
task: TaskProvider<Task>,
139+
task: TaskProvider<out Task>,
120140
) {
121-
addApiDocumentationPublication(
122-
target,
123-
task,
124-
target.artifacts,
125-
openapi.outputDir.get().toString(),
126-
openapi.outputFileName.get().replace(".json", ""),
127-
"json"
128-
)
129-
}
141+
val format = openapi.outputFileName.map {
142+
if (it.endsWith(".${OpenApiFormat.YAML.extension}")) {
143+
OpenApiFormat.YAML
144+
} else if (it.endsWith(".${OpenApiFormat.JSON.extension}")) {
145+
OpenApiFormat.JSON
146+
} else {
147+
throw UnsupportedFormatException("The provided openapi filename '${it}' ends in an unsupported extension. Make sure you use 'yaml' or 'json'")
148+
}
149+
}
150+
151+
val basename = openapi.outputFileName.zip(format) { fileName, format ->
152+
fileName.replace(".${format.extension}", "")
153+
}
130154

131-
private fun configureYamlDocumentPublishing(
132-
target: Project,
133-
openapi: OpenApiExtension,
134-
task: TaskProvider<out Task>
135-
) {
136155
addApiDocumentationPublication(
137-
target,
138156
task,
139157
target.artifacts,
140-
openapi.outputDir.get().toString(),
141-
openapi.outputFileName.get().replace(".json", ""),
142-
"yaml"
158+
openapi.outputDir,
159+
basename,
160+
format
143161
)
144162
}
145163

146164
companion object {
165+
const val EXTENSION_NAME = "openApiConfigure"
147166
val logger: Logger = LoggerFactory.getLogger(SpringDocOpenApiConfigurePlugin::class.java)
148167
}
149168
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package io.cloudflight.gradle.autoconfigure.springdoc.openapi
2+
3+
class UnsupportedFormatException(message: String) : RuntimeException(message)

src/main/kotlin/io/cloudflight/gradle/autoconfigure/util/PublicationSupport.kt

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package io.cloudflight.gradle.autoconfigure.util
22

3+
import io.cloudflight.gradle.autoconfigure.springdoc.openapi.OpenApiFormat
34
import io.cloudflight.gradle.autoconfigure.swagger.SWAGGER_CLASSIFIER
4-
import org.gradle.api.Project
55
import org.gradle.api.Task
66
import org.gradle.api.artifacts.PublishArtifact
77
import org.gradle.api.artifacts.dsl.ArtifactHandler
8+
import org.gradle.api.file.DirectoryProperty
89
import org.gradle.api.plugins.JavaPlugin
10+
import org.gradle.api.provider.Provider
911
import org.gradle.api.tasks.TaskProvider
1012

1113
internal fun addApiDocumentationPublication(
@@ -27,20 +29,23 @@ internal fun addApiDocumentationPublication(
2729
}
2830

2931
internal fun addApiDocumentationPublication(
30-
project: Project,
3132
task: TaskProvider<out Task>,
3233
artifacts: ArtifactHandler,
33-
targetDir: String,
34-
basename: String,
35-
format: String
34+
targetDir: DirectoryProperty,
35+
basename: Provider<String>,
36+
format: Provider<OpenApiFormat>
3637
): PublishArtifact {
38+
val fileName = basename.zip(format) { name, format ->
39+
"${name}.${format.extension}"
40+
}
41+
3742
return artifacts.add(
3843
JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME,
39-
project.file("$targetDir/${basename}.${format}")
44+
targetDir.file(fileName)
4045
) {
41-
it.name = basename
46+
it.name = basename.get()
4247
it.classifier = SWAGGER_CLASSIFIER
43-
it.type = format
48+
it.type = format.get().extension
4449
it.builtBy(task.get())
4550
}
46-
}
51+
}

0 commit comments

Comments
 (0)