diff --git a/.github/workflows/update-gradle-versions.yml b/.github/workflows/update-gradle-versions.yml new file mode 100644 index 00000000..3583fba4 --- /dev/null +++ b/.github/workflows/update-gradle-versions.yml @@ -0,0 +1,41 @@ +name: Update Gradle Versions + +on: + schedule: + - cron: '0 9 * * 1' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + update-gradle-versions: + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '21' + + - name: Update bundled Gradle wrappers + run: ./scripts/update_gradle_versions.sh + + - name: Run tests + run: ./gradlew :project-generator:test :cli:test + + - name: Create pull request + uses: peter-evans/create-pull-request@v7 + with: + commit-message: Update bundled Gradle versions + title: Update bundled Gradle versions + body: | + Updates the bundled Gradle wrapper resources and README to the latest three minor lines for Gradle 9 and Gradle 8 from the official Gradle versions feed. + branch: codex/update-gradle-versions + delete-branch: true diff --git a/README.md b/README.md index 8dc53748..a81ba327 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Then, you can use the versions.yaml in the `generate-project` command: - `--classes-module-type`: fixed (default), random - `--type-of-string-resources`: normal (default), large - `--generate-unit-test`: Generate unit tests (default: false) -- `--gradle`: gradle_8_14_3, gradle_9_1_0, gradle_9_2_0, gradle_9_3_0, gradle_9_3_1, gradle_9_4_0 (default: gradle_9_4_0) +- `--gradle`: gradle_9_4_1, gradle_9_3_1, gradle_9_2_1, gradle_8_14_4, gradle_8_13, gradle_8_12_1 (default: gradle_9_4_1) - `--develocity`: Enables the Develocity build scan plugin (default: false). If --develocity-url is not specified, the build scan will be published to Gradle Scans. - `--develocity-url`: Specify Develocity URL - `--versions-file`: Path to a custom YAML file with dependency versions @@ -77,7 +77,7 @@ ProjectGenerator( typeOfStringResources = TypeOfStringResources.LARGE, layers = 5, generateUnitTest = true, - gradle = GradleWrapper(Gradle.GRADLE_9_3_1), + gradle = GradleWrapper(Gradle.fromValue("9.3.1")), projectRootPath = file.path ).write() @@ -200,12 +200,15 @@ If enabled, each module will generate n unit tests, where n is the argument `cla ``` ## Gradle Gradle used, versions supported: -* Gradle 8.14.3 -* Gradle 9.1.0 -* Gradle 9.2.0 -* Gradle 9.3.0 -* Gradle 9.3.1 -* Gradle 9.4.0 **default** +* Gradle 9.x + * 9.4.1 + * 9.3.1 + * 9.2.1 +* Gradle 8.x + * 8.14.4 + * 8.13 + * 8.12.1 +* The newest bundled version is the default. ##### Example ```kotlin diff --git a/backend/server.cjs b/backend/server.cjs index 73556c9a..db1dec7b 100644 --- a/backend/server.cjs +++ b/backend/server.cjs @@ -76,10 +76,10 @@ app.post('/api/generate', upload.single('versions-file'), async (req, res) => { `--type`, type, `--classes-module`, classesModule, `--classes-module-type`, body['classes-module-type'] || 'fixed', - `--type-of-string-resources`, body['type-of-string-resources'] || 'normal', - `--gradle`, body.gradle || 'GRADLE_9_4_0' + `--type-of-string-resources`, body['type-of-string-resources'] || 'normal' ]; + if (body.gradle) args.push('--gradle', body.gradle); // Add project name if provided if (body['project-name']) { args.push('--project-name', body['project-name']);} if (toBool(body['generate-unit-test']) || toBool(body.generateUnitTest)) args.push('--generate-unit-test'); diff --git a/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/GenerateVersionsYaml.kt b/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/GenerateVersionsYaml.kt index 9758f6fc..5210f5f3 100644 --- a/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/GenerateVersionsYaml.kt +++ b/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/GenerateVersionsYaml.kt @@ -10,7 +10,7 @@ class GenerateVersionsYaml { val file = File("versions.yaml") val versions = Versions() val content = """ - |gradle: ${Gradle.GRADLE_9_4_0.version} + |gradle: ${Gradle.latest().version} |project: | develocity: ${versions.project.develocity} | develocityUrl: ${versions.project.develocityUrl} diff --git a/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/Main.kt b/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/Main.kt index a70c4cac..523dcc89 100644 --- a/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/Main.kt +++ b/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/Main.kt @@ -42,14 +42,13 @@ class GenerateProjects : CliktCommand(name = "generate-project") { private val typeOfStringResources: String by option().choice("large", "normal").default("normal") private val layers by option().int().default(5) private val generateUnitTest by option().flag(default = false) - private val gradle: String? by option().choice( - "gradle_8_14_3", - "gradle_9_1_0", - "gradle_9_2_0", - "gradle_9_3_0", - "gradle_9_3_1", - "gradle_9_4_0" - ) + private val gradle: String? by option() + .convert { + if (!Gradle.isSupportedValue(it)) { + fail("Unknown Gradle version: $it. Supported versions: ${Gradle.supportedDisplayValues()}") + } + it + } private val develocity by option().flag(default = false) private val versionsFile by option().file() private val outputDir by option("--output-dir") @@ -151,7 +150,7 @@ internal fun resolveProjectRootPath(outputDir: String?, language: Language, proj internal fun resolveGradle(cliGradle: String?, versionsFile: VersionsFile?): Gradle { return cliGradle?.let(Gradle::fromValue) ?: versionsFile?.gradle - ?: Gradle.GRADLE_9_4_0 + ?: Gradle.latest() } class GenerateYaml : CliktCommand(name = "generate-yaml-versions") { diff --git a/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/VersionsParser.kt b/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/VersionsParser.kt index 3f05c109..7de22c7c 100644 --- a/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/VersionsParser.kt +++ b/cli/src/main/kotlin/io/github/cdsap/projectgenerator/cli/VersionsParser.kt @@ -7,7 +7,13 @@ import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.fasterxml.jackson.module.kotlin.KotlinModule +import io.github.cdsap.projectgenerator.model.AdditionalPlugin +import io.github.cdsap.projectgenerator.model.Android +import io.github.cdsap.projectgenerator.model.DependencyInjection import io.github.cdsap.projectgenerator.model.Gradle +import io.github.cdsap.projectgenerator.model.Kotlin +import io.github.cdsap.projectgenerator.model.Project +import io.github.cdsap.projectgenerator.model.Testing import io.github.cdsap.projectgenerator.model.VersionsFile import java.io.File @@ -28,7 +34,17 @@ object VersionsParser { val tree = mapper.readTree(file) normalizeGradleVersion(tree) normalizeProjectDefaults(tree) - return mapper.treeToValue(tree, VersionsFile::class.java) + val payload = mapper.treeToValue(tree, VersionsFilePayload::class.java) + return VersionsFile( + gradle = payload.gradle?.let(Gradle::fromValue), + kotlin = payload.kotlin, + android = payload.android, + testing = payload.testing, + project = payload.project, + di = payload.di, + additionalBuildGradleRootPlugins = payload.additionalBuildGradleRootPlugins, + additionalSettingsPlugins = payload.additionalSettingsPlugins + ) } private fun normalizeGradleVersion(tree: com.fasterxml.jackson.databind.JsonNode) { @@ -36,7 +52,7 @@ object VersionsParser { val gradleNode = tree.get("gradle") ?: return if (!gradleNode.isTextual) return - val normalized = Gradle.fromValue(gradleNode.asText()).name + val normalized = Gradle.fromValue(gradleNode.asText()).version tree.put("gradle", normalized) } @@ -49,4 +65,15 @@ object VersionsParser { projectNode.put("develocityUrl", "") } } + + private data class VersionsFilePayload( + val gradle: String? = null, + val kotlin: Kotlin = Kotlin(), + val android: Android = Android(), + val testing: Testing = Testing(), + val project: Project = Project(), + val di: DependencyInjection = DependencyInjection.HILT, + val additionalBuildGradleRootPlugins: List? = null, + val additionalSettingsPlugins: List? = null + ) } diff --git a/cli/src/test/kotlin/io/github/cdsap/projectgenerator/cli/GenerateProjectsCliTest.kt b/cli/src/test/kotlin/io/github/cdsap/projectgenerator/cli/GenerateProjectsCliTest.kt index 384bb85c..7ee56652 100644 --- a/cli/src/test/kotlin/io/github/cdsap/projectgenerator/cli/GenerateProjectsCliTest.kt +++ b/cli/src/test/kotlin/io/github/cdsap/projectgenerator/cli/GenerateProjectsCliTest.kt @@ -41,25 +41,41 @@ class GenerateProjectsCliTest { assertTrue(error.message?.contains("classes per module must be >= 10") == true) } + @Test + fun `unsupported gradle version lists supported values`() { + val error = assertThrows { + GenerateProjects().parse( + listOf( + "--modules", "6", + "--gradle", "9.9.9" + ) + ) + } + + assertTrue(error.message?.contains("Unknown Gradle version: 9.9.9") == true) + assertTrue(error.message?.contains(Gradle.supportedDisplayValues()) == true) + } + @Test fun `gradle from versions file is used when flag is absent`() { - val resolved = resolveGradle(null, VersionsFile(gradle = Gradle.GRADLE_9_3_1)) + val configured = Gradle.supported()[1] + val resolved = resolveGradle(null, VersionsFile(gradle = configured)) - assertEquals(Gradle.GRADLE_9_3_1, resolved) + assertEquals(configured, resolved) } @Test fun `gradle flag overrides versions file`() { - val resolved = resolveGradle("gradle_9_4_0", VersionsFile(gradle = Gradle.GRADLE_9_3_1)) + val resolved = resolveGradle(Gradle.latest().cliValue, VersionsFile(gradle = Gradle.oldest())) - assertEquals(Gradle.GRADLE_9_4_0, resolved) + assertEquals(Gradle.latest(), resolved) } @Test fun `latest gradle is used when neither flag nor versions file provide one`() { val resolved = resolveGradle(null, null) - assertEquals(Gradle.GRADLE_9_4_0, resolved) + assertEquals(Gradle.latest(), resolved) } @Test diff --git a/cli/src/test/kotlin/io/github/cdsap/projectgenerator/cli/VersionsParserTest.kt b/cli/src/test/kotlin/io/github/cdsap/projectgenerator/cli/VersionsParserTest.kt index 354dec66..61e214a5 100644 --- a/cli/src/test/kotlin/io/github/cdsap/projectgenerator/cli/VersionsParserTest.kt +++ b/cli/src/test/kotlin/io/github/cdsap/projectgenerator/cli/VersionsParserTest.kt @@ -148,24 +148,24 @@ class VersionsParserTest { @Test fun `parses gradle from YAML case insensitively`() { val yaml = """ - gradle: 9.3.1 + gradle: ${Gradle.supported()[1].version} """.trimIndent() val file = File(tempDir.toFile(), "versions.yaml").apply { writeText(yaml) } val versionsFile = VersionsParser.fromFile(file) - assertEquals(Gradle.GRADLE_9_3_1, versionsFile.gradle) + assertEquals(Gradle.supported()[1], versionsFile.gradle) } @Test fun `parses legacy gradle enum name from YAML for backwards compatibility`() { val yaml = """ - gradle: GRADLE_9_3_1 + gradle: ${Gradle.supported()[1].legacyEnumName} """.trimIndent() val file = File(tempDir.toFile(), "versions.yaml").apply { writeText(yaml) } val versionsFile = VersionsParser.fromFile(file) - assertEquals(Gradle.GRADLE_9_3_1, versionsFile.gradle) + assertEquals(Gradle.supported()[1], versionsFile.gradle) } } diff --git a/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/ProjectGenerator.kt b/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/ProjectGenerator.kt index 96dccd07..664c5966 100644 --- a/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/ProjectGenerator.kt +++ b/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/ProjectGenerator.kt @@ -15,7 +15,7 @@ class ProjectGenerator( private val typeOfStringResources: TypeOfStringResources = TypeOfStringResources.NORMAL, private val layers: Int, private val generateUnitTest: Boolean = false, - private val gradle: GradleWrapper = GradleWrapper(Gradle.GRADLE_9_4_0), + private val gradle: GradleWrapper = GradleWrapper(Gradle.latest()), private val projectRootPath: String = "projects_generated/generated_project/project_kts", private val develocity: Boolean = false, private val layerNames: List = DefaultNames.layerNames, diff --git a/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/model/Gradle.kt b/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/model/Gradle.kt index a0668370..807a3b47 100644 --- a/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/model/Gradle.kt +++ b/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/model/Gradle.kt @@ -1,18 +1,59 @@ package io.github.cdsap.projectgenerator.model -enum class Gradle(val version: String) { - GRADLE_8_14_3("8.14.3"), - GRADLE_9_1_0("9.1.0"), - GRADLE_9_2_0("9.2.0"), - GRADLE_9_3_0("9.3.0"), - GRADLE_9_3_1("9.3.1"), - GRADLE_9_4_0("9.4.0"); +data class Gradle(val version: String) { + + val cliValue: String + get() = "gradle_${version.replace('.', '_')}" + + val legacyEnumName: String + get() = cliValue.uppercase() + + val resourceZipName: String + get() = "${cliValue.lowercase()}.zip" companion object { + private const val SUPPORTED_VERSIONS_RESOURCE = "supported-gradle-versions.txt" + + private val supportedVersions: List by lazy { + val stream = requireNotNull(Gradle::class.java.classLoader.getResourceAsStream(SUPPORTED_VERSIONS_RESOURCE)) { + "Missing resource: $SUPPORTED_VERSIONS_RESOURCE" + } + stream.bufferedReader().useLines { lines -> + lines + .map(String::trim) + .filter { it.isNotEmpty() && !it.startsWith("#") } + .map(::Gradle) + .toList() + } + } + + private val supportedLookup: Map by lazy { + supportedVersions.flatMap { gradle -> + listOf( + gradle.version.lowercase() to gradle, + gradle.cliValue.lowercase() to gradle, + gradle.legacyEnumName.lowercase() to gradle + ) + }.toMap() + } + + fun supported(): List = supportedVersions + + fun latest(): Gradle = supported().first() + + fun oldest(): Gradle = supported().last() + + fun supportedCliChoices(): List = supported().flatMap { listOf(it.cliValue, it.version) } + + fun supportedDisplayValues(): String = supported().joinToString(", ") { "${it.cliValue} (${it.version})" } + + fun isSupportedValue(value: String): Boolean = supportedLookup.containsKey(value.lowercase()) + fun fromValue(value: String): Gradle { - return entries.firstOrNull { it.version == value } - ?: entries.firstOrNull { it.name.equals(value, ignoreCase = true) } - ?: throw IllegalArgumentException("Unknown Gradle version: $value") + return supportedLookup[value.lowercase()] + ?: throw IllegalArgumentException( + "Unknown Gradle version: $value. Supported versions: ${supportedDisplayValues()}" + ) } } } diff --git a/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/writer/GradleWrapper.kt b/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/writer/GradleWrapper.kt index 56f0e6e6..926834fe 100644 --- a/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/writer/GradleWrapper.kt +++ b/project-generator/src/main/kotlin/io/github/cdsap/projectgenerator/writer/GradleWrapper.kt @@ -8,7 +8,7 @@ import java.util.zip.ZipInputStream class GradleWrapper(val gradle: Gradle) { fun installGradleVersion(path: String) { - val zipFileName = "${gradle.name.lowercase()}.zip" + val zipFileName = gradle.resourceZipName val outputDir = path unzipResourceFile(zipFileName, outputDir) diff --git a/project-generator/src/main/resources/gradle_8_12_1.zip b/project-generator/src/main/resources/gradle_8_12_1.zip new file mode 100644 index 00000000..d9e94709 Binary files /dev/null and b/project-generator/src/main/resources/gradle_8_12_1.zip differ diff --git a/project-generator/src/main/resources/gradle_8_13.zip b/project-generator/src/main/resources/gradle_8_13.zip new file mode 100644 index 00000000..a2a9b4f5 Binary files /dev/null and b/project-generator/src/main/resources/gradle_8_13.zip differ diff --git a/project-generator/src/main/resources/gradle_8_14_4.zip b/project-generator/src/main/resources/gradle_8_14_4.zip new file mode 100644 index 00000000..b06b0ce7 Binary files /dev/null and b/project-generator/src/main/resources/gradle_8_14_4.zip differ diff --git a/project-generator/src/main/resources/gradle_9_2_1.zip b/project-generator/src/main/resources/gradle_9_2_1.zip new file mode 100644 index 00000000..179cc298 Binary files /dev/null and b/project-generator/src/main/resources/gradle_9_2_1.zip differ diff --git a/project-generator/src/main/resources/gradle_9_4_0.zip b/project-generator/src/main/resources/gradle_9_4_1.zip similarity index 98% rename from project-generator/src/main/resources/gradle_9_4_0.zip rename to project-generator/src/main/resources/gradle_9_4_1.zip index 93be226f..4f714998 100644 Binary files a/project-generator/src/main/resources/gradle_9_4_0.zip and b/project-generator/src/main/resources/gradle_9_4_1.zip differ diff --git a/project-generator/src/main/resources/supported-gradle-versions.txt b/project-generator/src/main/resources/supported-gradle-versions.txt new file mode 100644 index 00000000..d9e4aa6c --- /dev/null +++ b/project-generator/src/main/resources/supported-gradle-versions.txt @@ -0,0 +1,6 @@ +9.4.1 +9.3.1 +9.2.1 +8.14.4 +8.13 +8.12.1 diff --git a/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/DefaultTestVersions.kt b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/DefaultTestVersions.kt index 9a509167..c3e94d3e 100644 --- a/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/DefaultTestVersions.kt +++ b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/DefaultTestVersions.kt @@ -5,6 +5,7 @@ import io.github.cdsap.projectgenerator.model.Versions class DefaultTestVersions { companion object { - val LATEST_GRADLE = Gradle.GRADLE_9_4_0 + val LATEST_GRADLE = Gradle.latest() + val OLDEST_SUPPORTED_GRADLE = Gradle.oldest() } } diff --git a/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/ProjectGeneratorE2ETest.kt b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/ProjectGeneratorE2ETest.kt index 69b86dbe..49d03dca 100644 --- a/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/ProjectGeneratorE2ETest.kt +++ b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/ProjectGeneratorE2ETest.kt @@ -1,10 +1,10 @@ package io.github.cdsap.projectgenerator import io.github.cdsap.projectgenerator.DefaultTestVersions.Companion.LATEST_GRADLE +import io.github.cdsap.projectgenerator.DefaultTestVersions.Companion.OLDEST_SUPPORTED_GRADLE import io.github.cdsap.projectgenerator.model.Android import io.github.cdsap.projectgenerator.model.ClassesPerModule import io.github.cdsap.projectgenerator.model.ClassesPerModuleType -import io.github.cdsap.projectgenerator.model.Gradle import io.github.cdsap.projectgenerator.model.Language import io.github.cdsap.projectgenerator.model.Project import io.github.cdsap.projectgenerator.model.Shape @@ -88,7 +88,7 @@ class ProjectGeneratorE2ETest { TypeOfStringResources.LARGE, 5, true, - GradleWrapper(Gradle.GRADLE_8_14_3), + GradleWrapper(OLDEST_SUPPORTED_GRADLE), projectRootPath = "${tempDir.toFile().path}/${shape.name.lowercase().capitalize()}$modules/project_kts", develocity = false, projectName = "${shape.name.lowercase().capitalize()}$modules" diff --git a/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/SupportedGradleVersionsE2ETest.kt b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/SupportedGradleVersionsE2ETest.kt new file mode 100644 index 00000000..b667d658 --- /dev/null +++ b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/SupportedGradleVersionsE2ETest.kt @@ -0,0 +1,69 @@ +package io.github.cdsap.projectgenerator + +import io.github.cdsap.projectgenerator.model.ClassesPerModule +import io.github.cdsap.projectgenerator.model.ClassesPerModuleType +import io.github.cdsap.projectgenerator.model.Gradle +import io.github.cdsap.projectgenerator.model.Language +import io.github.cdsap.projectgenerator.model.Project +import io.github.cdsap.projectgenerator.model.Shape +import io.github.cdsap.projectgenerator.model.TypeOfStringResources +import io.github.cdsap.projectgenerator.model.TypeProjectRequested +import io.github.cdsap.projectgenerator.model.Versions +import io.github.cdsap.projectgenerator.writer.GradleWrapper +import org.gradle.testkit.runner.GradleRunner +import org.junit.jupiter.api.io.TempDir +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import java.io.File +import java.nio.file.Path + +class SupportedGradleVersionsE2ETest { + + @TempDir + lateinit var tempDir: Path + + @ParameterizedTest(name = "builds generated JVM project with Gradle {0}") + @MethodSource("supportedGradleVersions") + fun `all supported gradle versions can build generated JVM project`(gradle: Gradle) { + val projectName = "gradle_${gradle.version.replace('.', '_')}" + val projectPath = tempDir.resolve(projectName).resolve("project_kts").toFile() + + ProjectGenerator( + modules = 6, + shape = Shape.FLAT, + language = Language.KTS, + typeOfProjectRequested = TypeProjectRequested.JVM, + classesPerModule = ClassesPerModule(ClassesPerModuleType.FIXED, 10), + versions = Versions(project = Project(jdk = "17")), + typeOfStringResources = TypeOfStringResources.NORMAL, + layers = 2, + generateUnitTest = false, + gradle = GradleWrapper(gradle), + projectRootPath = projectPath.absolutePath, + develocity = false, + projectName = projectName + ).write() + + val result = GradleRunner.create() + .withProjectDir(projectPath) + .withArguments("build") + .build() + + val sampleModuleBuildDir = File( + projectPath, + "${NameMappings.layerName(0)}/${NameMappings.moduleName("module_0_1")}/build" + ) + + assert(result.output.contains("BUILD SUCCESSFUL")) { + "Expected build to succeed for Gradle ${gradle.version}" + } + assert(sampleModuleBuildDir.exists() && sampleModuleBuildDir.isDirectory) { + "Expected sample module build directory to exist for Gradle ${gradle.version}" + } + } + + companion object { + @JvmStatic + fun supportedGradleVersions(): List = Gradle.supported() + } +} diff --git a/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/model/GradleTest.kt b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/model/GradleTest.kt new file mode 100644 index 00000000..8e57eb45 --- /dev/null +++ b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/model/GradleTest.kt @@ -0,0 +1,35 @@ +package io.github.cdsap.projectgenerator.model + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class GradleTest { + + @Test + fun `supported gradle list is capped to six entries`() { + assertEquals(6, Gradle.supported().size) + } + + @Test + fun `latest gradle is the first supported entry`() { + assertEquals(Gradle.supported().first(), Gradle.latest()) + } + + @Test + fun `fromValue accepts version cli value and legacy enum name`() { + val gradle = Gradle.supported()[1] + + assertEquals(gradle, Gradle.fromValue(gradle.version)) + assertEquals(gradle, Gradle.fromValue(gradle.cliValue)) + assertEquals(gradle, Gradle.fromValue(gradle.legacyEnumName)) + } + + @Test + fun `supported gradle list keeps three minor lines for each targeted major`() { + val grouped = Gradle.supported().groupBy { it.version.substringBefore('.') } + + assertEquals(setOf("8", "9"), grouped.keys) + assertEquals(3, grouped.getValue("8").size) + assertEquals(3, grouped.getValue("9").size) + } +} diff --git a/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/model/VersionsFileTest.kt b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/model/VersionsFileTest.kt index 3b5b2afa..2bc2bbac 100644 --- a/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/model/VersionsFileTest.kt +++ b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/model/VersionsFileTest.kt @@ -38,8 +38,8 @@ class VersionsFileTest { @Test fun `gradle override is preserved in versions file`() { - val versionsFile = VersionsFile(gradle = Gradle.GRADLE_9_3_1) + val versionsFile = VersionsFile(gradle = Gradle.supported()[1]) - assertEquals(Gradle.GRADLE_9_3_1, versionsFile.gradle) + assertEquals(Gradle.supported()[1], versionsFile.gradle) } } diff --git a/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/writer/ProjectWriterTest.kt b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/writer/ProjectWriterTest.kt index 3ddb10ff..996caf3a 100644 --- a/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/writer/ProjectWriterTest.kt +++ b/project-generator/src/test/kotlin/io/github/cdsap/projectgenerator/writer/ProjectWriterTest.kt @@ -1,6 +1,7 @@ package io.github.cdsap.projectgenerator import io.github.cdsap.projectgenerator.DefaultTestVersions.Companion.LATEST_GRADLE +import io.github.cdsap.projectgenerator.DefaultTestVersions.Companion.OLDEST_SUPPORTED_GRADLE import io.github.cdsap.projectgenerator.model.* import io.github.cdsap.projectgenerator.writer.GradleWrapper import io.github.cdsap.projectgenerator.writer.ProjectWriter @@ -39,7 +40,7 @@ class ProjectWriterTest { typeOfProjectRequested, TypeOfStringResources.NORMAL, false, - GradleWrapper(Gradle.GRADLE_8_14_3), + GradleWrapper(OLDEST_SUPPORTED_GRADLE), true, "awesomeapp" ) diff --git a/scripts/update_gradle_versions.sh b/scripts/update_gradle_versions.sh new file mode 100755 index 00000000..5c06a41f --- /dev/null +++ b/scripts/update_gradle_versions.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +RESOURCES_DIR="$ROOT_DIR/project-generator/src/main/resources" +VERSIONS_FILE="$RESOURCES_DIR/supported-gradle-versions.txt" +README_FILE="$ROOT_DIR/README.md" +TARGET_MAJORS="${TARGET_MAJORS:-9 8}" +MINORS_PER_MAJOR="${MINORS_PER_MAJOR:-3}" +VERSIONS_ENDPOINT="https://services.gradle.org/versions/all" +TEMP_DIR="$(mktemp -d)" +GRADLE_USER_HOME="$TEMP_DIR/gradle-user-home" + +cleanup() { + rm -rf "$TEMP_DIR" +} +trap cleanup EXIT + +fetch_versions() { + curl -fsSL "$VERSIONS_ENDPOINT" | python3 -c ' +import json +import os +import sys + +items = json.load(sys.stdin) +target_majors = os.environ["TARGET_MAJORS"].split() +minors_per_major = int(os.environ["MINORS_PER_MAJOR"]) +selected_by_minor = {major: [] for major in target_majors} +seen_minor = {major: set() for major in target_majors} + +for item in items: + version = item.get("version", "").strip() + if not version: + continue + if item.get("snapshot") or item.get("nightly") or item.get("releaseNightly") or item.get("broken"): + continue + if any(ch.isalpha() for ch in version): + continue + parts = version.split(".") + if len(parts) < 2: + continue + major = parts[0] + if major not in selected_by_minor: + continue + minor_key = ".".join(parts[:2]) + if minor_key in seen_minor[major]: + continue + selected_by_minor[major].append(version) + seen_minor[major].add(minor_key) + + if all(len(selected_by_minor[major]) >= minors_per_major for major in target_majors): + break + +for major in target_majors: + print("\n".join(selected_by_minor[major][:minors_per_major])) +' +} + +generate_wrapper_bundle() { + local version="$1" + local bundle_name="gradle_${version//./_}.zip" + local work_dir="$TEMP_DIR/$version" + + mkdir -p "$work_dir/gradle/wrapper" + cp "$ROOT_DIR/gradlew" "$ROOT_DIR/gradlew.bat" "$work_dir/" + cp "$ROOT_DIR/gradle/wrapper/gradle-wrapper.jar" "$ROOT_DIR/gradle/wrapper/gradle-wrapper.properties" "$work_dir/gradle/wrapper/" + printf 'rootProject.name = "wrapper-bundle"\n' > "$work_dir/settings.gradle.kts" + printf '\n' > "$work_dir/build.gradle.kts" + + GRADLE_USER_HOME="$GRADLE_USER_HOME" "$work_dir/gradlew" -p "$work_dir" wrapper --gradle-version "$version" --distribution-type bin + ( + cd "$work_dir" + zip -qr "$RESOURCES_DIR/$bundle_name" gradle gradlew gradlew.bat + ) +} + +prune_removed_bundles() { + local versions=("$@") + local keep_list=() + + for version in "${versions[@]}"; do + keep_list+=("gradle_${version//./_}.zip") + done + + while IFS= read -r existing; do + local base + base="$(basename "$existing")" + if [[ ! " ${keep_list[*]} " == *" ${base} "* ]]; then + rm -f "$existing" + fi + done < <(find "$RESOURCES_DIR" -maxdepth 1 -name 'gradle_*.zip' -type f | sort) +} + +update_readme() { + python3 - "$README_FILE" <<'PY' +from pathlib import Path +import re + +readme = Path(__import__("sys").argv[1]) +versions = Path(__import__("os").environ["VERSIONS_FILE"]).read_text().strip().splitlines() +majors = {} +for version in versions: + major = version.split(".", 1)[0] + majors.setdefault(major, []).append(version) + +lines = [] +for major, items in majors.items(): + lines.append(f"* Gradle {major}.x") + lines.extend(f" * {version}" for version in items) + +replacement = "Gradle used, versions supported:\n" + "\n".join(lines) + "\n* The newest bundled version is the default." +content = readme.read_text() +pattern = re.compile( + r"Gradle used, versions supported:\n(?:.*\n)*?\* The newest bundled version is the default\.", + re.MULTILINE +) +updated = pattern.sub(replacement, content) +readme.write_text(updated) +PY +} + +export TARGET_MAJORS MINORS_PER_MAJOR VERSIONS_FILE +mapfile -t versions < <(fetch_versions) + +if [[ "${#versions[@]}" -eq 0 ]]; then + echo "No stable Gradle versions found from $VERSIONS_ENDPOINT" >&2 + exit 1 +fi + +printf '%s\n' "${versions[@]}" > "$VERSIONS_FILE" + +for version in "${versions[@]}"; do + echo "Generating wrapper bundle for Gradle $version" + generate_wrapper_bundle "$version" +done + +prune_removed_bundles "${versions[@]}" +update_readme + +echo "Updated supported Gradle versions:" +cat "$VERSIONS_FILE"