Unverified Commit 684c15f3 authored by Fahad Zubair's avatar Fahad Zubair Committed by GitHub
Browse files

Set MSRV in rust-toolchain.toml using the gradle.property (#3841)



The `rust-toolchain.toml` file for tests now uses the `rust.msrv` value
from the `gradle.properties` file.

This PR also fixes an issue where the `rust-toolchain.toml` file was not
created in the overridden test directory when `overrideTestDir` was set.
This caused the installed compiler version to be used, resulting in
errors with the latest compiler and preventing the use of
`overrideTestDir`.

Closes: #2048

---------

Co-authored-by: default avatarFahad Zubair <fahadzub@amazon.com>
parent abd28bc7
Loading
Loading
Loading
Loading
+43 −9
Original line number Diff line number Diff line
@@ -40,9 +40,11 @@ import software.amazon.smithy.rust.codegen.core.util.letIf
import software.amazon.smithy.rust.codegen.core.util.orNullIfEmpty
import software.amazon.smithy.rust.codegen.core.util.runCommand
import java.io.File
import java.io.FileInputStream
import java.nio.file.Files
import java.nio.file.Files.createTempDirectory
import java.nio.file.Path
import java.util.Properties
import kotlin.io.path.absolutePathString
import kotlin.io.path.writeText

@@ -54,6 +56,8 @@ val TestModuleDocProvider =
            }
    }

val projectRootDir by lazy { File("git rev-parse --show-toplevel".runCommand().replace("\n", "")) }

/**
 * Waiting for Kotlin to stabilize their temp directory functionality
 */
@@ -65,6 +69,38 @@ private fun tempDir(directory: File? = null): File {
    }
}

/**
 * This function returns the minimum supported Rust version, as specified in the `gradle.properties` file
 * located at the root of the project.
 */
fun msrv(): String {
    val properties = Properties()
    val propertiesFilePath = projectRootDir.resolve("gradle.properties")

    FileInputStream(propertiesFilePath).use { inputStream ->
        properties.load(inputStream)
    }

    return properties.getProperty("rust.msrv")
}

/**
 * Generates the `rust-toolchain.toml` file in the specified directory.
 *
 * The compiler version is set in `gradle.properties` under the `rust.msrv` property.
 * The Gradle task `GenerateMsrvTask` generates the Kotlin class
 * `software.amazon.smithy.rust.codegen.core.Msrv` and writes the value of `rust.msrv` into it.
 */
private fun File.generateRustToolchainToml() {
    resolve("rust-toolchain.toml").writeText(
        // Help rust select the right version when we run cargo test.
        """
        [toolchain]
        channel = "${msrv()}"
        """.trimIndent(),
    )
}

/**
 * Creates a Cargo workspace shared among all tests
 *
@@ -87,8 +123,7 @@ object TestWorkspace {
    private val subprojects = mutableListOf<String>()

    private val cargoLock: File by lazy {
        val projectDir = "git rev-parse --show-toplevel".runCommand().replace("\n", "")
        File(projectDir).resolve("aws/sdk/Cargo.lock")
        projectRootDir.resolve("aws/sdk/Cargo.lock")
    }

    init {
@@ -121,12 +156,7 @@ object TestWorkspace {
                version = "0.0.1"
                """.trimIndent(),
            )
            newProject.resolve("rust-toolchain.toml").writeText(
                // help rust select the right version when we run cargo test
                // TODO(https://github.com/smithy-lang/smithy-rs/issues/2048): load this from the msrv property using a
                //  method as we do for runtime crate versions
                "[toolchain]\nchannel = \"1.78.0\"\n",
            )
            newProject.generateRustToolchainToml()
            // ensure there at least an empty lib.rs file to avoid broken crates
            newProject.resolve("src").mkdirs()
            newProject.resolve("src/lib.rs").writeText("")
@@ -181,7 +211,11 @@ fun generatePluginContext(
    runtimeConfig: RuntimeConfig? = null,
    overrideTestDir: File? = null,
): Pair<PluginContext, Path> {
    val testDir = overrideTestDir ?: TestWorkspace.subproject()
    val testDir =
        overrideTestDir?.apply {
            mkdirs()
            generateRustToolchainToml()
        } ?: TestWorkspace.subproject()
    val moduleName = "test_${testDir.nameWithoutExtension}"
    val testPath = testDir.toPath()
    val manifest = FileManifest.create(testPath)
+88 −0
Original line number Diff line number Diff line
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package software.amazon.smithy.rust.codegen.core.util

import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.paths.shouldExist
import io.kotest.matchers.shouldNotBe
import org.junit.jupiter.api.Test
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.generatePluginContext
import software.amazon.smithy.rust.codegen.core.testutil.projectRootDir
import java.nio.file.Files.createTempDirectory
import java.util.regex.Pattern

internal class RustToolChainTomlTest {
    val model =
        """
        namespace test

        service TestService {
            version: "123",
            operations: [TestOperation]
        }

        operation TestOperation {
            input:= {}
            output:= {}
        }
        """.asSmithyModel(smithyVersion = "2")

    @Test
    fun `override test directory in integration test has a rust-toolchain toml file`() {
        val dir = createTempDirectory("smithy-test").toFile()
        val (_, path) = generatePluginContext(model, overrideTestDir = dir)
        path.shouldExist()
        val rustToolchainTomlPath = path.resolve("rust-toolchain.toml")
        rustToolchainTomlPath.shouldExist()
    }

    @Test
    fun `rust-toolchain toml file has correct value from gradle properties for rust-msrv`() {
        val (_, path) = generatePluginContext(model)
        val rustToolchainTomlPath = path.resolve("rust-toolchain.toml")
        rustToolchainTomlPath.shouldExist()

        // Read the MSRV written in `gradle.properties` file.
        val msrvPattern = Pattern.compile("rust\\.msrv=(.+)")
        val gradlePropertiesPath = projectRootDir.resolve("gradle.properties")
        val msrv =
            gradlePropertiesPath.useLines { lines ->
                lines.firstNotNullOfOrNull { line ->
                    msrvPattern.matcher(line).let { matcher ->
                        if (matcher.find()) matcher.group(1) else null
                    }
                }
            }
        msrv shouldNotBe null

        // Read `channel = (\d+)` from `rust-toolchain.toml` file, and
        // ensure it matches the one in `gradle.properties`.
        val toolchainPattern = Pattern.compile("\\[toolchain]")
        val channelPattern = Pattern.compile("channel\\s*=\\s*\"(.+)\"")

        val channelMatches =
            rustToolchainTomlPath.toFile().useLines { lines ->
                // Skip lines until the [toolchain] table is found, then take all lines until the next table.
                val toolchainSection =
                    lines
                        .dropWhile { !toolchainPattern.matcher(it).find() }
                        .drop(1)
                        .takeWhile { !it.trim().startsWith("[") }

                // There should be a [toolchain] table, and it must have a key called 'channel' whose value must
                // match the `rust.msrv` specified in gradle.properties.
                toolchainSection != null &&
                    toolchainSection.any { line ->
                        channelPattern.matcher(line).let { matcher ->
                            matcher.find() && matcher.group(1) == msrv
                        }
                    }
            }

        channelMatches.shouldBeTrue()
    }
}