Unverified Commit d51774a1 authored by Russell Cohen's avatar Russell Cohen Committed by GitHub
Browse files

Debug mode (#1335)

* Update to remove usages of deprecated writers

* Add Support for "debugMode"

Add a `debugMode` flag. This will render a best-efforts `file:line` number comment for every generated section of code.

* Fix server codegen
parent c9e6ab9a
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -88,7 +88,7 @@ class ServerCodegenVisitor(context: PluginContext, private val codegenDecorator:

        codegenContext = CodegenContext(model, symbolProvider, service, protocol, settings, mode = CodegenMode.Server)

        rustCrate = RustCrate(context.fileManifest, symbolProvider, DefaultPublicModules)
        rustCrate = RustCrate(context.fileManifest, symbolProvider, DefaultPublicModules, settings.codegenConfig)
        protocolGenerator = protocolGeneratorFactory.buildProtocolGenerator(codegenContext)
    }

+2 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ data class ServerCodegenConfig(
    val includeFluentClient: Boolean = false,
    val addMessageToErrors: Boolean = false,
    val formatTimeoutSeconds: Int = 20,
    val debugMode: Boolean = false,
    // TODO(EventStream): [CLEANUP] Remove this property when turning on Event Stream for all services
    val eventStreamAllowList: Set<String> = emptySet(),
) {
@@ -42,6 +43,7 @@ data class ServerCodegenConfig(
                    false,
                    false,
                    20,
                    debugMode = false,
                    emptySet()
                )
            }
+61 −28
Original line number Diff line number Diff line
@@ -10,8 +10,7 @@ import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import software.amazon.smithy.codegen.core.CodegenException
import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.codegen.core.writer.CodegenWriter
import software.amazon.smithy.codegen.core.writer.CodegenWriterFactory
import software.amazon.smithy.codegen.core.SymbolWriter
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.BooleanShape
import software.amazon.smithy.model.shapes.CollectionShape
@@ -23,7 +22,7 @@ import software.amazon.smithy.rust.codegen.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.smithy.isOptional
import software.amazon.smithy.rust.codegen.smithy.rustType
import software.amazon.smithy.rust.codegen.util.orNull
import software.amazon.smithy.utils.CodeWriter
import software.amazon.smithy.utils.AbstractCodeWriter
import java.util.function.BiFunction

/**
@@ -54,7 +53,7 @@ import java.util.function.BiFunction
 * are the recommended approach.
 */

fun <T : CodeWriter> T.withBlock(
fun <T : AbstractCodeWriter<T>> T.withBlock(
    textBeforeNewLine: String,
    textAfterNewLine: String,
    vararg args: Any,
@@ -63,13 +62,13 @@ fun <T : CodeWriter> T.withBlock(
    return conditionalBlock(textBeforeNewLine, textAfterNewLine, conditional = true, block = block, args = args)
}

fun <T : CodeWriter> T.assignment(variableName: String, vararg ctx: Pair<String, Any>, block: T.() -> Unit) {
fun <T : AbstractCodeWriter<T>> T.assignment(variableName: String, vararg ctx: Pair<String, Any>, block: T.() -> Unit) {
    withBlockTemplate("let $variableName =", ";", *ctx) {
        block()
    }
}

fun <T : CodeWriter> T.withBlockTemplate(
fun <T : AbstractCodeWriter<T>> T.withBlockTemplate(
    textBeforeNewLine: String,
    textAfterNewLine: String,
    vararg ctx: Pair<String, Any>,
@@ -80,7 +79,7 @@ fun <T : CodeWriter> T.withBlockTemplate(
    }
}

private fun <T : CodeWriter, U> T.withTemplate(
private fun <T : AbstractCodeWriter<T>, U> T.withTemplate(
    template: String,
    scope: Array<out Pair<String, Any>>,
    f: T.(String) -> U
@@ -105,7 +104,7 @@ private fun <T : CodeWriter, U> T.withTemplate(
 * }
 * ```
 */
fun <T : CodeWriter> T.conditionalBlock(
fun <T : AbstractCodeWriter<T>> T.conditionalBlock(
    textBeforeNewLine: String,
    textAfterNewLine: String,
    conditional: Boolean = true,
@@ -126,7 +125,7 @@ fun <T : CodeWriter> T.conditionalBlock(
/**
 * Convenience wrapper that tells Intellij that the contents of this block are Rust
 */
fun <T : CodeWriter> T.rust(
fun <T : AbstractCodeWriter<T>> T.rust(
    @Language("Rust", prefix = "macro_rules! foo { () =>  {{\n", suffix = "\n}}}") contents: String,
    vararg args: Any
) {
@@ -152,7 +151,7 @@ private fun transformTemplate(template: String, scope: Array<out Pair<String, An
/**
 * Sibling method to [rustBlock] that enables `#{variablename}` style templating
 */
fun <T : CodeWriter> T.rustBlockTemplate(
fun <T : AbstractCodeWriter<T>> T.rustBlockTemplate(
    @Language("Rust", prefix = "macro_rules! foo { () =>  {{ ", suffix = "}}}") contents: String,
    vararg ctx: Pair<String, Any>,
    block: T.() -> Unit
@@ -181,7 +180,7 @@ fun <T : CodeWriter> T.rustBlockTemplate(
 *
 * Variables are lower cased so that they become valid identifiers for named Smithy parameters.
 */
fun <T : CodeWriter> T.rustTemplate(
fun RustWriter.rustTemplate(
    @Language("Rust", prefix = "macro_rules! foo { () =>  {{ ", suffix = "}}}") contents: String,
    vararg ctx: Pair<String, Any>
) {
@@ -193,7 +192,7 @@ fun <T : CodeWriter> T.rustTemplate(
/*
 * Writes a Rust-style block, demarcated by curly braces
 */
fun <T : CodeWriter> T.rustBlock(
fun <T : AbstractCodeWriter<T>> T.rustBlock(
    @Language("Rust", prefix = "macro_rules! foo { () =>  {{ ", suffix = "}}}")
    header: String,
    vararg args: Any,
@@ -208,7 +207,12 @@ fun <T : CodeWriter> T.rustBlock(
/**
 * Generate a RustDoc comment for [shape]
 */
fun <T : CodeWriter> T.documentShape(shape: Shape, model: Model, autoSuppressMissingDocs: Boolean = true, note: String? = null): T {
fun <T : AbstractCodeWriter<T>> T.documentShape(
    shape: Shape,
    model: Model,
    autoSuppressMissingDocs: Boolean = true,
    note: String? = null
): T {
    val docTrait = shape.getMemberTrait(model, DocumentationTrait::class.java).orNull()

    when (docTrait?.value?.isNotBlank()) {
@@ -246,7 +250,7 @@ fun RustWriter.containerDocs(text: String, vararg args: Any): RustWriter {
 *    - Tabs are replaced with spaces
 *    - Empty newlines are removed
 */
fun <T : CodeWriter> T.docs(text: String, vararg args: Any, newlinePrefix: String = "/// "): T {
fun <T : AbstractCodeWriter<T>> T.docs(text: String, vararg args: Any, newlinePrefix: String = "/// "): T {
    pushState()
    setNewlinePrefix(newlinePrefix)
    val cleaned = text.lines()
@@ -260,7 +264,8 @@ fun <T : CodeWriter> T.docs(text: String, vararg args: Any, newlinePrefix: Strin
}

/** Escape the [expressionStart] character to avoid problems during formatting */
fun CodeWriter.escape(text: String): String = text.replace("$expressionStart", "$expressionStart$expressionStart")
fun <T : AbstractCodeWriter<T>> T.escape(text: String): String =
    text.replace("$expressionStart", "$expressionStart$expressionStart")

/** Parse input as HTML and normalize it */
fun normalizeHtml(input: String): String {
@@ -288,7 +293,7 @@ private fun Element.changeInto(tagName: String) {
/**
 * Write _exactly_ the text as written into the code writer without newlines or formatting
 */
fun CodeWriter.raw(text: String) = writeInline(escape(text))
fun RustWriter.raw(text: String) = writeInline(escape(text))

typealias Writable = RustWriter.() -> Unit

@@ -315,9 +320,11 @@ class RustWriter private constructor(
    private val filename: String,
    val namespace: String,
    private val commentCharacter: String = "//",
    private val printWarning: Boolean = true
    private val printWarning: Boolean = true,
    /** Insert comments indicating where code was generated */
    private val debugMode: Boolean = false,
) :
    CodegenWriter<RustWriter, UseDeclarations>(null, UseDeclarations(namespace)) {
    SymbolWriter<RustWriter, UseDeclarations>(UseDeclarations(namespace)) {
    companion object {
        fun root() = forModule(null)
        fun forModule(module: String?): RustWriter = if (module == null) {
@@ -326,18 +333,44 @@ class RustWriter private constructor(
            RustWriter("$module.rs", "crate::$module")
        }

        val Factory: CodegenWriterFactory<RustWriter> =
            CodegenWriterFactory<RustWriter> { fileName, namespace ->
        fun factory(debugMode: Boolean): Factory<RustWriter> = Factory { fileName: String, namespace: String ->
            when {
                    fileName.endsWith(".toml") -> RustWriter(fileName, namespace, "#")
                    fileName.endsWith(".md") -> rawWriter(fileName)
                    fileName == "LICENSE" -> rawWriter(fileName)
                    else -> RustWriter(fileName, namespace)
                fileName.endsWith(".toml") -> RustWriter(fileName, namespace, "#", debugMode = debugMode)
                fileName.endsWith(".md") -> rawWriter(fileName, debugMode = debugMode)
                fileName == "LICENSE" -> rawWriter(fileName, debugMode = debugMode)
                else -> RustWriter(fileName, namespace, debugMode = debugMode)
            }
        }

        private fun rawWriter(fileName: String, debugMode: Boolean): RustWriter =
            RustWriter(
                fileName,
                namespace = "ignore",
                commentCharacter = "ignore",
                printWarning = false,
                debugMode = debugMode
            )
    }

    override fun write(content: Any?, vararg args: Any?): RustWriter {
        if (debugMode) {
            val location = Thread.currentThread().stackTrace
            location.first { it.isRelevant() }?.let { "/* ${it.fileName}:${it.lineNumber} */" }
                ?.also { super.writeInline(it) }
        }

        return super.write(content, *args)
    }

        private fun rawWriter(fileName: String): RustWriter =
            RustWriter(fileName, namespace = "ignore", commentCharacter = "ignore", printWarning = false)
    /** Helper function to determine if a stack frame is relevant for debug purposes */
    private fun StackTraceElement.isRelevant(): Boolean {
        if (this.className.contains("AbstractCodeWriter") || this.className.startsWith("java.lang")) {
            return false
        }
        if (this.fileName == "RustWriter.kt") {
            return false
        }
        return true
    }

    private val preamble = mutableListOf<Writable>()
+1 −1
Original line number Diff line number Diff line
@@ -5,8 +5,8 @@

package software.amazon.smithy.rust.codegen.rustlang

import software.amazon.smithy.codegen.core.ImportContainer
import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.codegen.core.writer.ImportContainer

class UseDeclarations(private val namespace: String) : ImportContainer {
    private val imports: MutableSet<UseStatement> = mutableSetOf()
+5 −4
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@ package software.amazon.smithy.rust.codegen.smithy

import software.amazon.smithy.build.FileManifest
import software.amazon.smithy.codegen.core.SymbolProvider
import software.amazon.smithy.codegen.core.writer.CodegenWriterDelegator
import software.amazon.smithy.codegen.core.WriterDelegator
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.rust.codegen.rustlang.CargoDependency
@@ -45,9 +45,10 @@ open class RustCrate(
     * For core modules like `input`, `output`, and `error`, we need to specify whether these modules should be public or
     * private as well as any other metadata. [baseModules] enables configuring this. See [DefaultPublicModules].
     */
    baseModules: Map<String, RustModule>
    baseModules: Map<String, RustModule>,
    codegenConfig: CodegenConfig
) {
    private val inner = CodegenWriterDelegator(fileManifest, symbolProvider, RustWriter.Factory)
    private val inner = WriterDelegator(fileManifest, symbolProvider, RustWriter.factory(codegenConfig.debugMode))
    private val modules: MutableMap<String, RustModule> = baseModules.toMutableMap()
    private val features: MutableSet<Feature> = mutableSetOf()

@@ -164,7 +165,7 @@ val DefaultPublicModules = setOf(
 * - inlining inline dependencies that have been used
 * - generating (and writing) a Cargo.toml based on the settings & the required dependencies
 */
fun CodegenWriterDelegator<RustWriter>.finalize(
fun WriterDelegator<RustWriter>.finalize(
    settings: RustSettings,
    model: Model,
    manifestCustomizations: ManifestCustomizations,
Loading