From b2500fc5cfad08b3a0228f51d80a3e787017fcd8 Mon Sep 17 00:00:00 2001 From: Leon Linhart Date: Fri, 12 Sep 2025 14:47:28 +0200 Subject: [PATCH 1/2] Correctly apply Gradle task input & output annotations to accessors Gradle's task input and output annotations should be applied to the accessors instead of the properties. No adhering to this can cause funky behavior in the generated stubs. Examples of this can be found in the docs: https://docs.gradle.org/current/userguide/implementing_custom_tasks.html Also adds missing annotations so that validation passes --- .../gradle/plugin/tasks/GenerateTask.kt | 276 +++++++++--------- .../gradle/plugin/tasks/GeneratorsTask.kt | 2 + .../gradle/plugin/tasks/ValidateTask.kt | 12 +- 3 files changed, 147 insertions(+), 143 deletions(-) diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt index 8da6bc79b0b7..ede44708d03c 100644 --- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt @@ -62,43 +62,43 @@ open class GenerateTask @Inject constructor(private val objectFactory: ObjectFac /** * The verbosity of generation */ - @Optional - @Input + @get:Optional + @get:Input val verbose = project.objects.property() /** * Whether an input specification should be validated upon generation. */ - @Optional - @Input + @get:Optional + @get:Input val validateSpec = project.objects.property() /** * The name of the generator which will handle codegen. (see "openApiGenerators" task) */ - @Optional - @Input + @get:Optional + @get:Input val generatorName = project.objects.property() /** * This is the configuration for reference paths where schemas for openapi generation are stored * The directory which contains the additional schema files */ - @Optional - @InputDirectory - @PathSensitive(PathSensitivity.ABSOLUTE) + @get:Optional + @get:InputDirectory + @get:PathSensitive(PathSensitivity.ABSOLUTE) val schemaLocation = project.objects.property() /** * The output target directory into which code will be generated. */ - @Optional + @get:Optional @get:OutputDirectory val outputDir = project.objects.property() @Suppress("unused") @set:Option(option = "input", description = "The input specification.") - @Internal + @get:Internal var input: String? = null set(value) { inputSpec.set(value) @@ -111,9 +111,9 @@ open class GenerateTask @Inject constructor(private val objectFactory: ObjectFac * changes to any $ref referenced files. Use the `inputSpecRootDirectory` property to have Gradle track changes to * an entire directory of spec files. */ - @Optional + @get:Optional @get:InputFile - @PathSensitive(PathSensitivity.RELATIVE) + @get:PathSensitive(PathSensitivity.RELATIVE) val inputSpec = project.objects.property() /** @@ -122,60 +122,60 @@ open class GenerateTask @Inject constructor(private val objectFactory: ObjectFac * By default, a merged spec file will be generated based on the contents of the directory. To disable this, set the * `inputSpecRootDirectorySkipMerge` property. */ - @Optional + @get:Optional @get:InputDirectory - @PathSensitive(PathSensitivity.RELATIVE) + @get:PathSensitive(PathSensitivity.RELATIVE) val inputSpecRootDirectory = project.objects.property(); /** * Skip bundling all spec files into a merged spec file, if true. */ - @Input - @Optional + @get:Input + @get:Optional val inputSpecRootDirectorySkipMerge = project.objects.property() /** * Name of the file that will contain all merged specs */ - @Input - @Optional + @get:Input + @get:Optional val mergedFileName = project.objects.property(); /** * The remote Open API 2.0/3.x specification URL location. */ - @Input - @Optional + @get:Input + @get:Optional val remoteInputSpec = project.objects.property() /** * The template directory holding a custom template. */ - @Optional - @InputDirectory - @PathSensitive(PathSensitivity.RELATIVE) + @get:Optional + @get:InputDirectory + @get:PathSensitive(PathSensitivity.RELATIVE) val templateDir = project.objects.property() /** * Resource path containing template files. */ - @Optional - @Input + @get:Optional + @get:Input val templateResourcePath = project.objects.property() /** * Adds authorization headers when fetching the OpenAPI definitions remotely. * Pass in a URL-encoded string of name:header with a comma separating multiple values */ - @Optional - @Input + @get:Optional + @get:Input val auth = project.objects.property() /** * Sets specified global properties. */ - @Optional - @Input + @get:Optional + @get:Input val globalProperties = project.objects.mapProperty() /** @@ -183,271 +183,271 @@ open class GenerateTask @Inject constructor(private val objectFactory: ObjectFac * File content should be in a json format { "optionKey":"optionValue", "optionKey1":"optionValue1"...} * Supported options can be different for each language. Run config-help -g {generator name} command for language specific config options. */ - @Optional - @InputFile - @PathSensitive(PathSensitivity.RELATIVE) + @get:Optional + @get:InputFile + @get:PathSensitive(PathSensitivity.RELATIVE) val configFile = project.objects.property() /** * Specifies if the existing files should be overwritten during the generation. */ - @Optional - @Input + @get:Optional + @get:Input val skipOverwrite = project.objects.property() /** * Package for generated classes (where supported) */ - @Optional - @Input + @get:Optional + @get:Input val packageName = project.objects.property() /** * Package for generated api classes */ - @Optional - @Input + @get:Optional + @get:Input val apiPackage = project.objects.property() /** * Package for generated models */ - @Optional - @Input + @get:Optional + @get:Input val modelPackage = project.objects.property() /** * Prefix that will be prepended to all model names. Default is the empty string. */ - @Optional - @Input + @get:Optional + @get:Input val modelNamePrefix = project.objects.property() /** * Suffix that will be appended to all model names. Default is the empty string. */ - @Optional - @Input + @get:Optional + @get:Input val modelNameSuffix = project.objects.property() /** * Suffix that will be appended to all api names. Default is the empty string. */ - @Optional - @Input + @get:Optional + @get:Input val apiNameSuffix = project.objects.property() /** * Sets instantiation type mappings. */ - @Optional - @Input + @get:Optional + @get:Input val instantiationTypes = project.objects.mapProperty() /** * Sets mappings between OpenAPI spec types and generated code types. */ - @Optional - @Input + @get:Optional + @get:Input val typeMappings = project.objects.mapProperty() /** * Sets additional properties that can be referenced by the mustache templates in the format of name=value,name=value. * You can also have multiple occurrences of this option. */ - @Optional - @Input + @get:Optional + @get:Input val additionalProperties = project.objects.mapProperty() /** * Sets server variable for server URL template substitution, in the format of name=value,name=value. * You can also have multiple occurrences of this option. */ - @Optional - @Input + @get:Optional + @get:Input val serverVariables = project.objects.mapProperty() /** * Specifies additional language specific primitive types in the format of type1,type2,type3,type3. For example: String,boolean,Boolean,Double. */ - @Optional - @Input + @get:Optional + @get:Input val languageSpecificPrimitives = project.objects.listProperty() /** * Specifies .openapi-generator-ignore list in the form of relative/path/to/file1,relative/path/to/file2. For example: README.md,pom.xml. */ - @Optional - @Input + @get:Optional + @get:Input val openapiGeneratorIgnoreList = project.objects.listProperty() /** * Specifies mappings between a given class and the import that should be used for that class. */ - @Optional - @Input + @get:Optional + @get:Input val importMappings = project.objects.mapProperty() /** * Specifies mappings between a given schema and the new one. */ - @Optional - @Input + @get:Optional + @get:Input val schemaMappings = project.objects.mapProperty() /** * Specifies mappings between the inline scheme name and the new name */ - @Optional - @Input + @get:Optional + @get:Input val inlineSchemaNameMappings = project.objects.mapProperty() /** * Specifies options for inline schemas */ - @Optional - @Input + @get:Optional + @get:Input val inlineSchemaOptions = project.objects.mapProperty() /** * Specifies mappings between the property name and the new name */ - @Optional - @Input + @get:Optional + @get:Input val nameMappings = project.objects.mapProperty() /** * Specifies mappings between the parameter name and the new name */ - @Optional - @Input + @get:Optional + @get:Input val parameterNameMappings = project.objects.mapProperty() /** * Specifies mappings between the model name and the new name */ - @Optional - @Input + @get:Optional + @get:Input val modelNameMappings = project.objects.mapProperty() /** * Specifies mappings between the enum name and the new name */ - @Optional - @Input + @get:Optional + @get:Input val enumNameMappings = project.objects.mapProperty() /** * Specifies mappings between the operation id name and the new name */ - @Optional - @Input + @get:Optional + @get:Input val operationIdNameMappings = project.objects.mapProperty() /** * Specifies mappings (rules) in OpenAPI normalizer */ - @Optional - @Input + @get:Optional + @get:Input val openapiNormalizer = project.objects.mapProperty() /** * Root package for generated code. */ - @Optional - @Input + @get:Optional + @get:Input val invokerPackage = project.objects.property() /** * GroupId in generated pom.xml/build.gradle.kts or other build script. Language-specific conversions occur in non-jvm generators. */ - @Optional - @Input + @get:Optional + @get:Input val groupId = project.objects.property() /** * ArtifactId in generated pom.xml/build.gradle.kts or other build script. Language-specific conversions occur in non-jvm generators. */ - @Optional - @Input + @get:Optional + @get:Input val id = project.objects.property() /** * Artifact version in generated pom.xml/build.gradle.kts or other build script. Language-specific conversions occur in non-jvm generators. */ - @Optional - @Input + @get:Optional + @get:Input val version = project.objects.property() /** * Reference the library template (sub-template) of a generator. */ - @Optional - @Input + @get:Optional + @get:Input val library = project.objects.property() /** * Git host, e.g. gitlab.com. */ - @Optional - @Input + @get:Optional + @get:Input val gitHost = project.objects.property() /** * Git user ID, e.g. openapitools. */ - @Optional - @Input + @get:Optional + @get:Input val gitUserId = project.objects.property() /** * Git repo ID, e.g. openapi-generator. */ - @Optional - @Input + @get:Optional + @get:Input val gitRepoId = project.objects.property() /** * Release note, default to 'Minor update'. */ - @Optional - @Input + @get:Optional + @get:Input val releaseNote = project.objects.property() /** * HTTP user agent, e.g. codegen_csharp_api_client, default to 'OpenAPI-Generator/{packageVersion}/{language}' */ - @Optional - @Input + @get:Optional + @get:Input val httpUserAgent = project.objects.property() /** * Specifies how a reserved name should be escaped to. */ - @Optional - @Input + @get:Optional + @get:Input val reservedWordsMappings = project.objects.mapProperty() /** * Specifies an override location for the .openapi-generator-ignore file. Most useful on initial generation. */ - @Optional - @InputFile - @PathSensitive(PathSensitivity.RELATIVE) + @get:Optional + @get:InputFile + @get:PathSensitive(PathSensitivity.RELATIVE) val ignoreFileOverride = project.objects.property() /** * Remove prefix of operationId, e.g. config_getId => getId */ - @Optional - @Input + @get:Optional + @get:Input val removeOperationIdPrefix = project.objects.property() /** * Remove examples defined in the operation */ - @Optional - @Input + @get:Optional + @get:Input val skipOperationExample = project.objects.property() /** @@ -459,8 +459,8 @@ open class GenerateTask @Inject constructor(private val objectFactory: ObjectFac * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. */ - @Optional - @Input + @get:Optional + @get:Input val apiFilesConstrainedTo = project.objects.listProperty() /** @@ -470,8 +470,8 @@ open class GenerateTask @Inject constructor(private val objectFactory: ObjectFac * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. */ - @Optional - @Input + @get:Optional + @get:Input val modelFilesConstrainedTo = project.objects.listProperty() /** @@ -484,8 +484,8 @@ open class GenerateTask @Inject constructor(private val objectFactory: ObjectFac * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. */ - @Optional - @Input + @get:Optional + @get:Input val supportingFilesConstrainedTo = project.objects.listProperty() /** @@ -496,8 +496,8 @@ open class GenerateTask @Inject constructor(private val objectFactory: ObjectFac * For more control over generation of individual files, configure an ignore file and * refer to it via [ignoreFileOverride]. */ - @Optional - @Input + @get:Optional + @get:Input val generateModelTests = project.objects.property() /** @@ -508,8 +508,8 @@ open class GenerateTask @Inject constructor(private val objectFactory: ObjectFac * For more control over generation of individual files, configure an ignore file and * refer to it via [ignoreFileOverride]. */ - @Optional - @Input + @get:Optional + @get:Input val generateModelDocumentation = project.objects.property() /** @@ -520,8 +520,8 @@ open class GenerateTask @Inject constructor(private val objectFactory: ObjectFac * For more control over generation of individual files, configure an ignore file and * refer to it via [ignoreFileOverride]. */ - @Optional - @Input + @get:Optional + @get:Input val generateApiTests = project.objects.property() /** @@ -532,15 +532,15 @@ open class GenerateTask @Inject constructor(private val objectFactory: ObjectFac * For more control over generation of individual files, configure an ignore file and * refer to it via [ignoreFileOverride]. */ - @Optional - @Input + @get:Optional + @get:Input val generateApiDocumentation = project.objects.property() /** * To write all log messages (not just errors) to STDOUT */ - @Optional - @Input + @get:Optional + @get:Input val logToStderr = project.objects.property() /** @@ -549,15 +549,15 @@ open class GenerateTask @Inject constructor(private val objectFactory: ObjectFac * LANG_POST_PROCESS_FILE (e.g. GO_POST_PROCESS_FILE, SCALA_POST_PROCESS_FILE). Please open an issue if your target * generator does not support this functionality. */ - @Optional - @Input + @get:Optional + @get:Input val enablePostProcessFile = project.objects.property() /** * To skip spec validation. When true, we will skip the default behavior of validating a spec before generation. */ - @Optional - @Input + @get:Optional + @get:Input val skipValidateSpec = project.objects.property() /** @@ -565,37 +565,37 @@ open class GenerateTask @Inject constructor(private val objectFactory: ObjectFac * definitions generated as top-level Array-of-items, List-of-items, Map-of-items definitions. * When true, A model representation either containing or extending the array,list,map (depending on specific generator implementation) will be generated. */ - @Optional - @Input + @get:Optional + @get:Input val generateAliasAsModel = project.objects.property() /** * A dynamic map of options specific to a generator. */ - @Optional - @Input + @get:Optional + @get:Input val configOptions = project.objects.mapProperty() /** * Templating engine: "mustache" (default) or "handlebars" (beta) */ - @Optional - @Input + @get:Optional + @get:Input val engine = project.objects.property() /** * Defines whether the output dir should be cleaned up before generating the output. * */ - @Optional - @Input + @get:Optional + @get:Input val cleanupOutput = project.objects.property() /** * Defines whether the generator should run in dry-run mode. */ - @Optional - @Input + @get:Optional + @get:Input val dryRun = project.objects.property() private fun Property.ifNotEmpty(block: Property.(T) -> Unit) { diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GeneratorsTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GeneratorsTask.kt index 12f12ab81c8a..d83d00b5b637 100644 --- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GeneratorsTask.kt +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GeneratorsTask.kt @@ -22,6 +22,7 @@ import org.gradle.api.tasks.TaskAction import org.gradle.internal.logging.text.StyledTextOutput import org.gradle.internal.logging.text.StyledTextOutputFactory import org.gradle.kotlin.dsl.listProperty +import org.gradle.work.DisableCachingByDefault import org.openapitools.codegen.CodegenConfigLoader import org.openapitools.codegen.CodegenType import org.openapitools.codegen.meta.GeneratorMetadata @@ -36,6 +37,7 @@ import org.openapitools.codegen.meta.Stability * * @author Jim Schubert */ +@DisableCachingByDefault(because = "not worth caching") open class GeneratorsTask : DefaultTask() { /** * A list of stability indexes to include (value: all,beta,stable,experimental,deprecated). Excludes deprecated by default. diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt index 7852b68a2fc3..4d2d13836541 100644 --- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt @@ -21,6 +21,7 @@ import io.swagger.v3.parser.core.models.ParseOptions import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.logging.Logging +import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Internal @@ -51,17 +52,18 @@ import org.openapitools.codegen.validations.oas.RuleConfiguration * * @author Jim Schubert */ +@CacheableTask open class ValidateTask : DefaultTask() { @get:InputFile - @PathSensitive(PathSensitivity.RELATIVE) + @get:PathSensitive(PathSensitivity.RELATIVE) val inputSpec = project.objects.property() - @Optional - @Input + @get:Optional + @get:Input val recommend = project.objects.property().convention(true) - @Optional - @Input + @get:Optional + @get:Input val treatWarningsAsErrors = project.objects.property().convention(false) @get:Internal From 5ae89d4ee0c8204a803c7f8e2d4c5572ca8adbc7 Mon Sep 17 00:00:00 2001 From: Leon Linhart Date: Fri, 12 Sep 2025 14:47:40 +0200 Subject: [PATCH 2/2] Enable strict Gradle plugin validation --- modules/openapi-generator-gradle-plugin/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/openapi-generator-gradle-plugin/build.gradle b/modules/openapi-generator-gradle-plugin/build.gradle index 90bf9dcc8d1e..5bfe8171199d 100644 --- a/modules/openapi-generator-gradle-plugin/build.gradle +++ b/modules/openapi-generator-gradle-plugin/build.gradle @@ -81,6 +81,10 @@ tasks.withType(ReleaseNexusStagingRepository).configureEach { onlyIf { nexusPublishing.useStaging.get() } } +tasks.named("validatePlugins").configure { + enableStricterValidation = true +} + gradlePlugin { website = "https://openapi-generator.tech/" vcsUrl = "https://github.com/OpenAPITools/openapi-generator"