All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.smithy.kotlin.codegen.rendering.GradleGenerator.kt Maven / Gradle / Ivy

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */
package software.amazon.smithy.kotlin.codegen.rendering

import software.amazon.smithy.build.FileManifest
import software.amazon.smithy.kotlin.codegen.KotlinSettings
import software.amazon.smithy.kotlin.codegen.core.*
import software.amazon.smithy.utils.AbstractCodeWriter

// Determines the jvmTarget version emitted to the build file
private val JVM_TARGET_VERSION: String = System.getProperty("smithy.kotlin.codegen.jvmTargetVersion", "1.8")

/**
 * Generate Gradle build files
 * @param settings [KotlinSettings] describe how the build files should be generated
 * @param manifest Where the generated files are added
 * @param dependencies list of [KotlinDependency]s for the project
 */
fun writeGradleBuild(
    settings: KotlinSettings,
    manifest: FileManifest,
    dependencies: List,
) {
    val writer = GradleWriter()

    val isKmp = settings.build.generateMultiplatformProject
    val isRootModule = settings.build.generateFullProject

    val annotationRenderer: InlineCodeWriter = {
        val annotations = settings.build.optInAnnotations ?: emptyList()
        if (annotations.isNotEmpty()) {
            val formatted = annotations.joinToString(
                separator = ",\n",
                transform = {
                    "\"$it\""
                },
            )

            write(formatted)
        }
    }

    val pluginsBodyRenderer: InlineCodeWriter = {
        val pluginName = if (isKmp) "multiplatform" else "jvm"

        write(
            "kotlin(\"$pluginName\") #W",
            { w: AbstractCodeWriter<*> ->
                if (isRootModule) {
                    w.write("version #S", KOTLIN_COMPILER_VERSION)
                }
            },
        )
    }

    when {
        isKmp -> renderKmpGradleBuild(
            writer,
            isRootModule,
            dependencies,
            pluginsBodyRenderer,
            repositoryRenderer,
            annotationRenderer,
        )
        else -> renderJvmGradleBuild(
            writer,
            isRootModule,
            dependencies,
            pluginsBodyRenderer,
            repositoryRenderer,
            annotationRenderer,
        )
    }

    val contents = writer.toString()
    manifest.writeFile("build.gradle.kts", contents)
    if (settings.build.generateFullProject) {
        manifest.writeFile("settings.gradle.kts", "")
    }
}

fun renderKmpGradleBuild(
    writer: GradleWriter,
    isRootModule: Boolean,
    dependencies: List,
    pluginsRenderer: InlineCodeWriter,
    repositoryRenderer: InlineCodeWriter,
    annotationRenderer: InlineCodeWriter,
) {
    writer.write(
        """
        plugins {
            #W
        }  
        #W
        
        kotlin {
            #W
            #W
            sourceSets {
                val commonMain by getting {
                    dependencies {
                        #W
                    }
                }
                val commonTest by getting {
                    dependencies {
                        #W
                    }
                }
            }
            val optInAnnotations = listOf(
                #W
            )
            kotlin.sourceSets.all {
                optInAnnotations.forEach { languageSettings.optIn(it) }
            }
        }
        """.trimIndent(),
        pluginsRenderer,
        { w: GradleWriter -> if (isRootModule) w.write("explicitApi()") },
        { w: GradleWriter -> if (isRootModule) repositoryRenderer(w) },
        { w: GradleWriter -> if (isRootModule) renderRootJvmPluginConfig(w) else w.write("jvm()") },
        { w: GradleWriter -> renderDependencies(w, scope = Scope.SOURCE, isKmp = true, dependencies = dependencies) },
        { w: GradleWriter -> renderDependencies(w, scope = Scope.TEST, isKmp = true, dependencies = dependencies) },
        annotationRenderer,
    )
}

fun renderRootJvmPluginConfig(writer: GradleWriter) {
    writer.write(
        """
            jvm {
                compilations.all {
                    kotlinOptions.jvmTarget = #S
                }
                testRuns["test"].executionTask.configure {
                    useJUnitPlatform()
                    testLogging {
                        events("passed", "skipped", "failed")
                        showStandardStreams = true
                        showStackTraces = true
                        showExceptions = true
                        exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
                    }
                }
            }
        """.trimIndent(),
        JVM_TARGET_VERSION,
    )
}

fun renderJvmGradleBuild(
    writer: GradleWriter,
    isRootModule: Boolean,
    dependencies: List,
    pluginsRenderer: InlineCodeWriter,
    repositoryRenderer: InlineCodeWriter,
    annotationRenderer: InlineCodeWriter,
) {
    writer.write(
        """
        plugins {
            #W
        }
        #W

        dependencies {
            #W
        }
        val optInAnnotations = listOf(
            #W
        )
        kotlin {
            #W
            sourceSets.all {
                optInAnnotations.forEach { languageSettings.optIn(it) }
            }
        }

        tasks.test {
            useJUnitPlatform()
            testLogging {
                events("passed", "skipped", "failed")
                showStandardStreams = true
                showStackTraces = true
                showExceptions = true
                exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
            }
        }
        """.trimIndent(),
        pluginsRenderer,
        { w: GradleWriter -> if (isRootModule) repositoryRenderer(w) },
        { w: GradleWriter -> renderDependencies(w, scope = Scope.SOURCE, isKmp = false, dependencies = dependencies) },
        annotationRenderer,
        { w: GradleWriter -> if (isRootModule) w.write("explicitApi()") },
    )
}

// Specifies if a given codegen operation is under a source or test scope
private enum class Scope {
    SOURCE, TEST
}

private fun renderDependencies(writer: GradleWriter, scope: Scope, isKmp: Boolean, dependencies: List) {
    if (!isKmp) {
        writer.write("implementation(kotlin(\"stdlib\"))")
    }

    // TODO - can we make kotlin dependencies not specify a version e.g. kotlin("kotlin-test")
    // TODO - Kotlin MPP setup (pass through KotlinSettings) - maybe separate gradle writers
    val orderedDependencies = dependencies.sortedWith(compareBy({ it.config }, { it.artifact }))
    orderedDependencies
        .filter {
            if (isKmp) {
                if (scope == Scope.SOURCE) !it.config.isTestScope else it.config.isTestScope
            } else {
                true
            }
        }
        .forEach { dependency ->
            writer.write(
                "${dependency.config}(\"#L:#L:#L\")",
                dependency.group,
                dependency.artifact,
                dependency.version,
            )
        }
}

private val repositoryRenderer: InlineCodeWriter = {
    write(
        """
            repositories {
                mavenLocal()
                mavenCentral()
            }
        """.trimIndent(),
    )
}

class GradleWriter(parent: GradleWriter? = null) : AbstractCodeWriter() {
    init {
        trimBlankLines(parent?.trimBlankLines ?: 1)
        trimTrailingSpaces(parent?.trimTrailingSpaces ?: true)
        indentText = parent?.indentText ?: "    "
        expressionStart = parent?.expressionStart ?: '#'
        putFormatter('W', InlineCodeWriterFormatter(::GradleWriter))
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy