diff --git a/.changes/next-release/feature-065d979a-e921-4252-a9a7-f0c2079015f3.json b/.changes/next-release/feature-065d979a-e921-4252-a9a7-f0c2079015f3.json new file mode 100644 index 00000000000..ae117a79932 --- /dev/null +++ b/.changes/next-release/feature-065d979a-e921-4252-a9a7-f0c2079015f3.json @@ -0,0 +1,4 @@ +{ + "type" : "feature", + "description" : "/transform: run all builds client-side" +} \ No newline at end of file diff --git a/plugins/amazonq/build.gradle.kts b/plugins/amazonq/build.gradle.kts index 39bbdd368fa..67c5f08033e 100644 --- a/plugins/amazonq/build.gradle.kts +++ b/plugins/amazonq/build.gradle.kts @@ -133,6 +133,8 @@ val prepareBundledFlare by tasks.registering(Copy::class) { } tasks.withType().configureEach { + intoChild(intellijPlatform.projectName.map { "$it/lib" }) + .from(file("contrib/QCT-Maven-6-16.jar")) intoChild(intellijPlatform.projectName.map { "$it/flare" }) .from(prepareBundledFlare) } diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt index cab313282b7..7533259fd6d 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt @@ -72,8 +72,6 @@ import java.util.concurrent.atomic.AtomicBoolean import javax.net.ssl.SSLHandshakeException const val MAX_ZIP_SIZE = 2000000000 // 2GB -const val EXPLAINABILITY_V1 = "EXPLAINABILITY_V1" -const val SELECTIVE_TRANSFORMATION_V2 = "SELECTIVE_TRANSFORMATION_V2" // constants for handling SDKClientException const val CONNECTION_REFUSED_ERROR: String = "Connection refused" @@ -554,6 +552,8 @@ class CodeModernizerSession( else -> { LOG.error(e) { e.message.toString() } + LOG.info { "Stopping transformation job [$jobId] due to unexpected error." } + stopTransformation(jobId.id) CodeModernizerJobCompletedResult.RetryableFailure( jobId, message("codemodernizer.notification.info.modernize_failed.connection_failed", e.message.orEmpty()), diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/constants/CodeTransformChatItems.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/constants/CodeTransformChatItems.kt index 9baf422c500..bab116949c0 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/constants/CodeTransformChatItems.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/constants/CodeTransformChatItems.kt @@ -348,7 +348,7 @@ fun buildUserInputSQLConversionMetadataChatContent() = CodeTransformChatMessageC ) fun buildUserInputCustomDependencyVersionsChatContent() = CodeTransformChatMessageContent( - message = "Optionally, provide a .YAML file which specifies custom dependency versions you want Q to upgrade to.", + message = message("codemodernizer.chat.message.custom_dependency_upgrades_prompt"), buttons = listOf( confirmCustomDependencyVersionsButton, continueTransformationButton, @@ -368,7 +368,7 @@ fun buildInvalidTargetJdkNameChatContent(jdkName: String) = CodeTransformChatMes ) fun buildCustomDependencyVersionsFileValidChatContent() = CodeTransformChatMessageContent( - message = "I received your .yaml file and will upload it to Q.", + message = message("codemodernizer.chat.message.custom_dependency_upgrades_valid"), type = CodeTransformChatMessageType.FinalizedAnswer, ) @@ -415,7 +415,7 @@ fun buildSQLMetadataValidationErrorChatContent(errorReason: String) = CodeTransf fun buildCustomDependencyVersionsFileInvalidChatContent() = CodeTransformChatMessageContent( type = CodeTransformChatMessageType.FinalizedAnswer, - message = "The file you uploaded does not follow the format of the sample YAML file provided.", + message = message("codemodernizer.chat.message.custom_dependency_upgrades_invalid"), ) fun buildUserCancelledChatContent() = CodeTransformChatMessageContent( @@ -455,7 +455,7 @@ fun buildUserLanguageUpgradeSelectionSummaryChatContent(moduleName: String, targ fun buildContinueTransformationChatContent() = CodeTransformChatMessageContent( type = CodeTransformChatMessageType.FinalizedAnswer, - message = "Ok, I will continue without this information.", + message = message("codemodernizer.chat.message.custom_dependency_upgrades_continue"), ) fun buildCompileLocalInProgressChatContent() = CodeTransformChatMessageContent( diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatController.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatController.kt index 735e0008a8f..3c9709f9fef 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatController.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/controller/CodeTransformChatController.kt @@ -4,17 +4,21 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.controller import com.intellij.ide.BrowserUtil +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runInEdt import com.intellij.openapi.fileChooser.FileChooser import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory +import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.module.ModuleUtil import com.intellij.openapi.projectRoots.JavaSdkVersion import com.intellij.openapi.projectRoots.ProjectJdkTable import com.intellij.openapi.util.io.FileUtil.createTempDirectory import com.intellij.openapi.vfs.VirtualFile +import com.intellij.testFramework.LightVirtualFile import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext +import org.jetbrains.yaml.YAMLFileType import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationDownloadArtifactType import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.error @@ -28,10 +32,8 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.ArtifactHandler import software.aws.toolkits.jetbrains.services.codemodernizer.CodeModernizerManager import software.aws.toolkits.jetbrains.services.codemodernizer.CodeModernizerManager.Companion.LOG import software.aws.toolkits.jetbrains.services.codemodernizer.CodeTransformTelemetryManager -import software.aws.toolkits.jetbrains.services.codemodernizer.EXPLAINABILITY_V1 import software.aws.toolkits.jetbrains.services.codemodernizer.HilTelemetryMetaData import software.aws.toolkits.jetbrains.services.codemodernizer.InboundAppMessagesHandler -import software.aws.toolkits.jetbrains.services.codemodernizer.SELECTIVE_TRANSFORMATION_V2 import software.aws.toolkits.jetbrains.services.codemodernizer.client.GumbyClient import software.aws.toolkits.jetbrains.services.codemodernizer.commands.CodeTransformActionMessage import software.aws.toolkits.jetbrains.services.codemodernizer.commands.CodeTransformCommand @@ -76,6 +78,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildTr import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildTransformStoppingChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserCancelledChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserHilSelection +import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserInputCustomDependencyVersionsChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserInputLanguageUpgradeChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserInputSQLConversionMetadataChatContent import software.aws.toolkits.jetbrains.services.codemodernizer.constants.buildUserInputSkipTestsFlagChatContent @@ -89,6 +92,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.messages.Authenti import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTransformChatMessage import software.aws.toolkits.jetbrains.services.codemodernizer.messages.CodeTransformCommandMessage import software.aws.toolkits.jetbrains.services.codemodernizer.messages.IncomingCodeTransformMessage +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CLIENT_SIDE_BUILD import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerArtifact import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerJobCompletedResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformConversationState @@ -97,12 +101,14 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransfo import software.aws.toolkits.jetbrains.services.codemodernizer.model.CustomerSelection import software.aws.toolkits.jetbrains.services.codemodernizer.model.DownloadArtifactResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.DownloadFailureReason +import software.aws.toolkits.jetbrains.services.codemodernizer.model.EXPLAINABILITY_V1 import software.aws.toolkits.jetbrains.services.codemodernizer.model.InvalidTelemetryReason import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId import software.aws.toolkits.jetbrains.services.codemodernizer.model.MAVEN_BUILD_RUN_UNIT_TESTS import software.aws.toolkits.jetbrains.services.codemodernizer.model.MAVEN_BUILD_SKIP_UNIT_TESTS import software.aws.toolkits.jetbrains.services.codemodernizer.model.MavenCopyCommandsResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.MavenDependencyReportCommandsResult +import software.aws.toolkits.jetbrains.services.codemodernizer.model.SELECTIVE_TRANSFORMATION_V2 import software.aws.toolkits.jetbrains.services.codemodernizer.model.UploadFailureReason import software.aws.toolkits.jetbrains.services.codemodernizer.model.ValidationResult import software.aws.toolkits.jetbrains.services.codemodernizer.panels.managers.CodeModernizerBottomWindowPanelManager @@ -412,14 +418,11 @@ class CodeTransformChatController( codeModernizerManager.codeTransformationSession?.let { it.sessionContext.customBuildCommand = customBuildCommand } - // TODO: add CLIENT_SIDE_BUILD below when releasing CSB - val transformCapabilities = listOf(EXPLAINABILITY_V1, SELECTIVE_TRANSFORMATION_V2) + val transformCapabilities = listOf(EXPLAINABILITY_V1, CLIENT_SIDE_BUILD, SELECTIVE_TRANSFORMATION_V2) codeModernizerManager.codeTransformationSession?.let { it.sessionContext.transformCapabilities = transformCapabilities - codeModernizerManager.runLocalMavenBuild(context.project, it) } - // TODO: when releasing CSB, delete "runLocalMavenBuild" line above and uncomment line below - // promptForCustomYamlFile() + promptForCustomYamlFile() } override suspend fun processCodeTransformCustomDependencyVersions(message: IncomingCodeTransformMessage.CodeTransformConfirmCustomDependencyVersions) { @@ -461,14 +464,11 @@ class CodeTransformChatController( } } - // TODO: uncomment when releasing CSB -/* private suspend fun promptForCustomYamlFile() { codeTransformChatHelper.addNewMessage(buildUserInputCustomDependencyVersionsChatContent()) val sampleYAML = """ -name: "custom-dependency-management" +name: "dependency-upgrade" description: "Custom dependency version management for Java migration from JDK 8/11/17 to JDK 17/21" - dependencyManagement: dependencies: - identifier: "com.example:library1" @@ -479,18 +479,18 @@ dependencyManagement: targetVersion: "3.0.0" originType: "THIRD_PARTY" plugins: - - identifier: "com.example.plugin" + - identifier: "com.example:plugin" targetVersion: "1.2.0" versionProperty: "plugin.version" # Optional """.trimIndent() - val virtualFile = LightVirtualFile("sample-dependency-management.yaml", YAMLFileType.YML, sampleYAML) + val virtualFile = LightVirtualFile("dependency_upgrade.yml", YAMLFileType.YML, sampleYAML) virtualFile.isWritable = true ApplicationManager.getApplication().invokeLater { FileEditorManager.getInstance(context.project).openFile(virtualFile, true) } } -*/ + override suspend fun processCodeTransformContinueAction(message: IncomingCodeTransformMessage.CodeTransformContinue) { codeTransformChatHelper.addNewMessage(buildContinueTransformationChatContent()) promptForTargetJdkName(message.tabId) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/MavenRunnerUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/MavenRunnerUtils.kt index 35ecb95564e..5f24806cc16 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/MavenRunnerUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/MavenRunnerUtils.kt @@ -17,6 +17,8 @@ import org.jetbrains.idea.maven.execution.MavenRunnerSettings import org.slf4j.Logger import software.aws.toolkits.core.utils.error import software.aws.toolkits.core.utils.info +import software.aws.toolkits.jetbrains.AwsPlugin +import software.aws.toolkits.jetbrains.AwsToolkit import software.aws.toolkits.jetbrains.services.codemodernizer.CodeModernizerManager import software.aws.toolkits.jetbrains.services.codemodernizer.CodeTransformTelemetryManager import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerSessionContext @@ -29,79 +31,20 @@ import java.io.File import java.nio.file.Files import java.nio.file.Path -fun runClientSideBuild(targetDir: VirtualFile, logger: Logger, project: Project): Pair { - // run mvn test-compile or mvn test - val transformMvnRunner = TransformMavenRunner(project) - val mvnSettings = MavenRunner.getInstance(project).settings.clone() - val buildRunnable = runClientSideBuild(targetDir, mvnSettings, transformMvnRunner, logger, project) - buildRunnable.await() - // write build output to a new text file and open it - val buildLogOutput = buildRunnable.getOutput().toString() - // first line is a long Maven String showing the build command; not useful or needed - val outputWithoutFirstLine = buildLogOutput.lines().drop(1).joinToString("\n") - val buildLogOutputFile = Files.createTempFile("build-logs-", ".txt") - Files.write(buildLogOutputFile, outputWithoutFirstLine.toByteArray()) - val buildLogOutputVirtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(buildLogOutputFile.toFile()) - runInEdt { - if (buildLogOutputVirtualFile != null) { - FileEditorManager.getInstance(project).openFile(buildLogOutputVirtualFile, true) - } - } - return buildRunnable.getExitCode() to buildRunnable.getOutput().toString() -} - -fun runHilMavenCopyDependency( - context: CodeModernizerSessionContext, - sourceFolder: File, - destinationDir: File, - logBuilder: StringBuilder, - logger: Logger, - project: Project, -): MavenCopyCommandsResult { - logger.info { "Executing IntelliJ bundled Maven" } - try { - // Create shared parameters - val transformMvnRunner = TransformMavenRunner(project) - context.mavenRunnerQueue.add(transformMvnRunner) - val mvnSettings = MavenRunner.getInstance(project).settings.clone() // clone required to avoid editing user settings - - // run copy dependencies - val copyDependenciesRunnable = - runMavenCopyDependencies(sourceFolder, logBuilder, mvnSettings, transformMvnRunner, destinationDir.toPath(), logger) - copyDependenciesRunnable.await() - logBuilder.appendLine(copyDependenciesRunnable.getOutput()) - if (copyDependenciesRunnable.isComplete()) { - val successMsg = "IntelliJ IDEA bundled Maven copy-dependencies executed successfully" - logger.info { successMsg } - logBuilder.appendLine(successMsg) - } else if (copyDependenciesRunnable.isTerminated()) { - return MavenCopyCommandsResult.Cancelled - } - } catch (t: Throwable) { - logger.error(t) { "IntelliJ bundled Maven copy-dependencies failed" } - return MavenCopyCommandsResult.Failure - } - // When all commands executed successfully, show the transformation hub - return MavenCopyCommandsResult.Success(destinationDir) -} - fun runMavenCopyCommands( context: CodeModernizerSessionContext, sourceFolder: File, logBuilder: StringBuilder, logger: Logger, project: Project, - shouldSkipTests: Boolean, ): MavenCopyCommandsResult { val currentTimestamp = System.currentTimeMillis() val destinationDir = Files.createTempDirectory("transformation_dependencies_temp_$currentTimestamp") val telemetry = CodeTransformTelemetryManager.getInstance(project) var telemetryErrorMessage = "" var telemetryLocalBuildResult = Result.Succeeded - logger.info { "Executing IntelliJ bundled Maven" } try { - // Create shared parameters val transformMvnRunner = TransformMavenRunner(project) context.mavenRunnerQueue.add(transformMvnRunner) val mvnSettings = MavenRunner.getInstance(project).settings.clone() // clone required to avoid editing user settings @@ -111,108 +54,94 @@ fun runMavenCopyCommands( val sdk = moduleSdk ?: ProjectRootManager.getInstance(project).projectSdk // edge case: module SDK and project SDK are null, and Maven Runner Settings is using the null project SDK, so Maven Runner will definitely fail if (sdk == null && mvnSettings.jreName == "#USE_PROJECT_JDK") return MavenCopyCommandsResult.NoJdk - - // run copy dependencies - val copyDependenciesRunnable = - runMavenCopyDependencies(sourceFolder, logBuilder, mvnSettings, transformMvnRunner, destinationDir, logger) - copyDependenciesRunnable.await() - logBuilder.appendLine(copyDependenciesRunnable.getOutput()) - if (copyDependenciesRunnable.isComplete()) { - val successMsg = "IntelliJ IDEA bundled Maven copy-dependencies executed successfully" + // run clean test-compile with Maven JAR + val jarRunnable = runMavenJar(sourceFolder, logBuilder, mvnSettings, transformMvnRunner, destinationDir, logger) + jarRunnable.await() + val output = jarRunnable.getOutput()?.lowercase()?.replace("elasticgumby", "QCT") + logBuilder.appendLine(output) + if (jarRunnable.isComplete()) { + val successMsg = "IntelliJ bundled Maven JAR executed successfully" logger.info { successMsg } logBuilder.appendLine(successMsg) - } else if (copyDependenciesRunnable.isTerminated()) { + } else if (jarRunnable.isTerminated()) { telemetryLocalBuildResult = Result.Cancelled return MavenCopyCommandsResult.Cancelled } else { - telemetryErrorMessage += "Maven Copy: bundled Maven failed. " - } - - // Run clean - val cleanRunnable = runMavenClean(sourceFolder, logBuilder, mvnSettings, transformMvnRunner, logger, destinationDir) - cleanRunnable.await() - logBuilder.appendLine(cleanRunnable.getOutput()) - if (cleanRunnable.isComplete()) { - val successMsg = "IntelliJ bundled Maven clean executed successfully" - logger.info { successMsg } - logBuilder.appendLine(successMsg) - } else if (cleanRunnable.isTerminated()) { - telemetryLocalBuildResult = Result.Cancelled - return MavenCopyCommandsResult.Cancelled - } else { - telemetryErrorMessage += "Maven Clean: bundled Maven failed." - - telemetryLocalBuildResult = Result.Failed - return MavenCopyCommandsResult.Failure - } - - // Run install - val installRunnable = runMavenInstall(sourceFolder, logBuilder, mvnSettings, transformMvnRunner, logger, destinationDir, shouldSkipTests) - installRunnable.await() - logBuilder.appendLine(installRunnable.getOutput()) - if (installRunnable.isComplete()) { - val successMsg = "IntelliJ bundled Maven install executed successfully" - logger.info { successMsg } - logBuilder.appendLine(successMsg) - } else if (installRunnable.isTerminated()) { - telemetryLocalBuildResult = Result.Cancelled - return MavenCopyCommandsResult.Cancelled - } else { - telemetryErrorMessage += "Maven Install: bundled Maven failed." - + telemetryErrorMessage += "Maven JAR: bundled Maven failed." telemetryLocalBuildResult = Result.Failed return MavenCopyCommandsResult.Failure } } catch (t: Throwable) { - val errorMessage = "IntelliJ bundled Maven executed failed: ${t.message}" + val error = t.message?.lowercase()?.replace("elasticgumby", "QCT") + val errorMessage = "IntelliJ bundled Maven JAR executed failed: $error" logger.error(t) { errorMessage } telemetryErrorMessage = errorMessage telemetryLocalBuildResult = Result.Failed return MavenCopyCommandsResult.Failure } finally { - // emit telemetry telemetry.localBuildProject(CodeTransformBuildCommand.IDEBundledMaven, telemetryLocalBuildResult, telemetryErrorMessage) } - // When all commands executed successfully, show the transformation hub return MavenCopyCommandsResult.Success(destinationDir.toFile()) } -private fun runMavenCopyDependencies( +private fun runMavenJar( sourceFolder: File, - buildlogBuilder: StringBuilder, + logBuilder: StringBuilder, mvnSettings: MavenRunnerSettings, transformMavenRunner: TransformMavenRunner, destinationDir: Path, logger: Logger, ): TransformRunnable { - buildlogBuilder.appendLine("Command Run: IntelliJ IDEA bundled Maven dependency:copy-dependencies") - val copyCommandList = listOf( - "dependency:copy-dependencies", - "-DoutputDirectory=$destinationDir", - "-Dmdep.useRepositoryLayout=true", - "-Dmdep.copyPom=true", - "-Dmdep.addParentPoms=true", + logBuilder.appendLine("Command Run: IntelliJ IDEA bundled Maven JAR") + val jarPath = AwsToolkit.PLUGINS_INFO[AwsPlugin.Q]?.path?.resolve("lib/QCT-Maven-6-16.jar") + + val commandList = listOf( + "-Dmaven.ext.class.path=$jarPath", + "-Dcom.amazon.aws.developer.transform.jobDirectory=$destinationDir", + "clean", + "test-compile" ) - val copyParams = MavenRunnerParameters( + val jarParams = MavenRunnerParameters( false, sourceFolder.absolutePath, null, - copyCommandList, + commandList, emptyList(), null ) - val copyTransformRunnable = TransformRunnable() + val jarRunnable = TransformRunnable() runInEdt { try { - transformMavenRunner.run(copyParams, mvnSettings, copyTransformRunnable) + transformMavenRunner.run(jarParams, mvnSettings, jarRunnable) } catch (t: Throwable) { - val error = "Maven Copy: Unexpected error when executing bundled Maven copy dependencies" - copyTransformRunnable.setExitCode(Integer.MIN_VALUE) // to stop looking for the exitCode + val error = "Maven JAR: Unexpected error when executing bundled Maven JAR" + jarRunnable.setExitCode(Integer.MIN_VALUE) // to stop looking for the exitCode logger.info(t) { error } - buildlogBuilder.appendLine("IntelliJ bundled Maven copy dependencies failed: ${t.message}") + logBuilder.appendLine("IntelliJ bundled Maven JAR failed: ${t.message}") } } - return copyTransformRunnable + return jarRunnable +} + +fun runClientSideBuild(targetDir: VirtualFile, logger: Logger, project: Project): Pair { + // run mvn test-compile or mvn test + val transformMvnRunner = TransformMavenRunner(project) + val mvnSettings = MavenRunner.getInstance(project).settings.clone() + val buildRunnable = runClientSideBuild(targetDir, mvnSettings, transformMvnRunner, logger, project) + buildRunnable.await() + // write build output to a new text file and open it + val buildLogOutput = buildRunnable.getOutput().toString() + // first line is a long Maven String showing the build command; not useful or needed + val outputWithoutFirstLine = buildLogOutput.lines().drop(1).joinToString("\n") + val buildLogOutputFile = Files.createTempFile("build-logs-", ".txt") + Files.write(buildLogOutputFile, outputWithoutFirstLine.toByteArray()) + val buildLogOutputVirtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(buildLogOutputFile.toFile()) + runInEdt { + if (buildLogOutputVirtualFile != null) { + FileEditorManager.getInstance(project).openFile(buildLogOutputVirtualFile, true) + } + } + return buildRunnable.getExitCode() to buildRunnable.getOutput().toString() } private fun runClientSideBuild( @@ -245,70 +174,79 @@ private fun runClientSideBuild( return buildTransformRunnable } -private fun runMavenClean( +// TODO: all functions below are for HIL; consider removing once client-side build released + +fun runHilMavenCopyDependency( + context: CodeModernizerSessionContext, sourceFolder: File, - buildlogBuilder: StringBuilder, - mvnSettings: MavenRunnerSettings, - transformMavenRunner: TransformMavenRunner, + destinationDir: File, + logBuilder: StringBuilder, logger: Logger, - destinationDir: Path, -): TransformRunnable { - buildlogBuilder.appendLine("Command Run: IntelliJ IDEA bundled Maven clean") - val cleanParams = MavenRunnerParameters( - false, - sourceFolder.absolutePath, - null, - listOf("-Dmaven.repo.local=$destinationDir", "clean"), - null, - null - ) - val cleanTransformRunnable = TransformRunnable() - runInEdt { - try { - transformMavenRunner.run(cleanParams, mvnSettings, cleanTransformRunnable) - } catch (t: Throwable) { - logger.error { "Maven Clean: Unexpected error when executing bundled Maven clean" } - cleanTransformRunnable.setExitCode(Integer.MIN_VALUE) // to stop looking for the exitCode - buildlogBuilder.appendLine("IntelliJ bundled Maven clean failed: ${t.message}") + project: Project, +): MavenCopyCommandsResult { + logger.info { "Executing IntelliJ bundled Maven" } + try { + // Create shared parameters + val transformMvnRunner = TransformMavenRunner(project) + context.mavenRunnerQueue.add(transformMvnRunner) + val mvnSettings = MavenRunner.getInstance(project).settings.clone() // clone required to avoid editing user settings + + // run copy dependencies + val copyDependenciesRunnable = + runMavenCopyDependencies(sourceFolder, logBuilder, mvnSettings, transformMvnRunner, destinationDir.toPath(), logger) + copyDependenciesRunnable.await() + logBuilder.appendLine(copyDependenciesRunnable.getOutput()) + if (copyDependenciesRunnable.isComplete()) { + val successMsg = "IntelliJ IDEA bundled Maven copy-dependencies executed successfully" + logger.info { successMsg } + logBuilder.appendLine(successMsg) + } else if (copyDependenciesRunnable.isTerminated()) { + return MavenCopyCommandsResult.Cancelled } + } catch (t: Throwable) { + logger.error(t) { "IntelliJ bundled Maven copy-dependencies failed" } + return MavenCopyCommandsResult.Failure } - return cleanTransformRunnable + // When all commands executed successfully, show the transformation hub + return MavenCopyCommandsResult.Success(destinationDir) } -private fun runMavenInstall( +private fun runMavenCopyDependencies( sourceFolder: File, logBuilder: StringBuilder, mvnSettings: MavenRunnerSettings, transformMavenRunner: TransformMavenRunner, - logger: Logger, destinationDir: Path, - shouldSkipTests: Boolean, + logger: Logger, ): TransformRunnable { - logBuilder.appendLine("Command Run: IntelliJ IDEA bundled Maven install") - val flags = if (shouldSkipTests) { - listOf("-Dmaven.repo.local=$destinationDir", "install", "-DskipTests") - } else { - listOf("-Dmaven.repo.local=$destinationDir", "install") - } - val installParams = MavenRunnerParameters( + logBuilder.appendLine("Command Run: IntelliJ IDEA bundled Maven dependency:copy-dependencies") + val copyCommandList = listOf( + "dependency:copy-dependencies", + "-DoutputDirectory=$destinationDir", + "-Dmdep.useRepositoryLayout=true", + "-Dmdep.copyPom=true", + "-Dmdep.addParentPoms=true", + ) + val copyParams = MavenRunnerParameters( false, sourceFolder.absolutePath, null, - flags, - null, + copyCommandList, + emptyList(), null ) - val installTransformRunnable = TransformRunnable() + val copyTransformRunnable = TransformRunnable() runInEdt { try { - transformMavenRunner.run(installParams, mvnSettings, installTransformRunnable) + transformMavenRunner.run(copyParams, mvnSettings, copyTransformRunnable) } catch (t: Throwable) { - logger.error(t) { "Maven Install: Unexpected error when executing bundled Maven install" } - installTransformRunnable.setExitCode(Integer.MIN_VALUE) // to stop looking for the exitCode - logBuilder.appendLine("IntelliJ bundled Maven install failed: ${t.message}") + val error = "Maven Copy: Unexpected error when executing bundled Maven copy dependencies" + copyTransformRunnable.setExitCode(Integer.MIN_VALUE) // to stop looking for the exitCode + logger.info(t) { error } + logBuilder.appendLine("IntelliJ bundled Maven copy dependencies failed: ${t.message}") } } - return installTransformRunnable + return copyTransformRunnable } private fun runMavenDependencyUpdatesReport( diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/TransformMavenRunner.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/TransformMavenRunner.kt index 4590c529793..338d465aca8 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/TransformMavenRunner.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/TransformMavenRunner.kt @@ -38,7 +38,7 @@ class TransformMavenRunner(val project: Project) { override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { // IntelliJ includes some unneeded lines in stdout; exclude those from build logs - if (!event.text.startsWith("[IJ]")) { + if (!event.text.startsWith("[IJ]") && !event.text.startsWith("Progress (")) { output += event.text } } diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerSessionContext.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerSessionContext.kt index 0f567285a09..058b47c3800 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerSessionContext.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerSessionContext.kt @@ -44,12 +44,13 @@ import kotlin.io.path.pathString const val MANIFEST_PATH = "manifest.json" const val ZIP_SOURCES_PATH = "sources" const val ZIP_DEPENDENCIES_PATH = "dependencies" -const val BUILD_LOG_PATH = "build-logs.txt" -const val CUSTOM_DEPENDENCY_VERSIONS_FILE_PATH = "custom-upgrades.yaml" +const val COMPILATIONS_JSON_FILE = "compilations.json" +const val CUSTOM_DEPENDENCY_VERSIONS_FILE_PATH = "dependency_upgrade.yml" const val UPLOAD_ZIP_MANIFEST_VERSION = "1.0" const val HIL_1P_UPGRADE_CAPABILITY = "HIL_1pDependency_VersionUpgrade" const val EXPLAINABILITY_V1 = "EXPLAINABILITY_V1" const val SELECTIVE_TRANSFORMATION_V2 = "SELECTIVE_TRANSFORMATION_V2" +const val IDE = "IDE" const val CLIENT_SIDE_BUILD = "CLIENT_SIDE_BUILD" const val MAVEN_CONFIGURATION_FILE_NAME = "pom.xml" const val MAVEN_BUILD_RUN_UNIT_TESTS = "clean test" @@ -111,8 +112,7 @@ data class CodeModernizerSessionContext( fun executeMavenCopyCommands(sourceFolder: File, buildLogBuilder: StringBuilder): MavenCopyCommandsResult { if (isDisposed) return MavenCopyCommandsResult.Cancelled - val shouldSkipTests = customBuildCommand == MAVEN_BUILD_SKIP_UNIT_TESTS - return runMavenCopyCommands(this, sourceFolder, buildLogBuilder, LOG, project, shouldSkipTests) + return runMavenCopyCommands(this, sourceFolder, buildLogBuilder, LOG, project) } private fun executeHilMavenCopyDependency(sourceFolder: File, destinationFolder: File, buildLogBuilder: StringBuilder) = runHilMavenCopyDependency( @@ -210,7 +210,6 @@ data class CodeModernizerSessionContext( fun createZipWithModuleFiles(copyResult: MavenCopyCommandsResult?): ZipCreationResult { val root = configurationFile?.parent val sourceFolder = File(root?.path) - val buildLogBuilder = StringBuilder("Starting Build Log...\n") val depDirectory = if (copyResult is MavenCopyCommandsResult.Success) { showTransformationHub() copyResult.dependencyDirectory @@ -236,7 +235,6 @@ data class CodeModernizerSessionContext( } val zipSources = File(ZIP_SOURCES_PATH) - val depSources = File(ZIP_DEPENDENCIES_PATH) val outputFile = createTemporaryZipFile { zip -> // 1) Manifest file var manifest = ZipManifest(transformCapabilities = transformCapabilities, customBuildCommand = customBuildCommand) @@ -249,6 +247,9 @@ data class CodeModernizerSessionContext( ) ) } + if (customDependencyVersionsFile != null) { + manifest.dependencyUpgradeConfigFile = CUSTOM_DEPENDENCY_VERSIONS_FILE_PATH + } mapper.writeValueAsString(manifest) .byteInputStream() .use { @@ -259,14 +260,22 @@ data class CodeModernizerSessionContext( if (depDirectory != null) { dependencyFiles.forEach { depFile -> val relativePath = File(depFile.path).relativeTo(depDirectory) - val paddedPath = depSources.resolve(relativePath) - var paddedPathString = paddedPath.toPath().toString() + if (depFile.path.contains("compilations.json") && File.separatorChar != '/') { + var content = depFile.readText() + content = content.replace("\\\\", "/") + depFile.writeText(content) + } + var relativePathString = relativePath.toPath().toString() + if (copyResult == null) { + // null copyResult means doing a SQL conversion; put metadata under dependencies folder + relativePathString = File(ZIP_DEPENDENCIES_PATH).resolve(relativePath).toPath().toString() + } // Convert Windows file path to work on Linux if (File.separatorChar != '/') { - paddedPathString = paddedPathString.replace('\\', '/') + relativePathString = relativePathString.replace('\\', '/') } depFile.inputStream().use { - zip.putNextEntry(paddedPathString, it) + zip.putNextEntry(relativePathString, it) } } } @@ -274,10 +283,13 @@ data class CodeModernizerSessionContext( LOG.info { "Dependency files size = ${dependencyFiles.sumOf { it.length().toInt() }}" } // 3) Custom YAML file - // TODO: where to put this? VS Code puts it in custom-upgrades/dependency-versions.yaml; here we put it at the root if (customDependencyVersionsFile != null) { + var yamlPath = "$ZIP_SOURCES_PATH/$CUSTOM_DEPENDENCY_VERSIONS_FILE_PATH" + if (File.separatorChar != '/') { + yamlPath = yamlPath.replace('\\', '/') + } customDependencyVersionsFile?.inputStream?.use { - zip.putNextEntry(Path(CUSTOM_DEPENDENCY_VERSIONS_FILE_PATH).toString(), it) + zip.putNextEntry(yamlPath, it) } } @@ -286,7 +298,6 @@ data class CodeModernizerSessionContext( val relativePath = File(file.path).relativeTo(sourceFolder) val paddedPath = zipSources.resolve(relativePath) var paddedPathString = paddedPath.toPath().toString() - // Convert Windows file path to work on Linux if (File.separatorChar != '/') { paddedPathString = paddedPathString.replace('\\', '/') } @@ -301,11 +312,6 @@ data class CodeModernizerSessionContext( } LOG.info { "Source code files size = ${files?.sumOf { it.length.toInt() }}" } - - // 5) Initial Maven copy-deps / install build log - buildLogBuilder.toString().byteInputStream().use { - zip.putNextEntry(Path(BUILD_LOG_PATH).toString(), it) - } }.toFile() // depDirectory should never be null if (depDirectory != null) ZipCreationResult.Succeeded(outputFile) else ZipCreationResult.Missing1P(outputFile) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ZipManifest.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ZipManifest.kt index 4dd20e630d0..eb7cbe54812 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ZipManifest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ZipManifest.kt @@ -3,18 +3,17 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.model -// TODO: include custom yaml file path in manifest.json? data class ZipManifest( val sourcesRoot: String = ZIP_SOURCES_PATH, val dependenciesRoot: String = ZIP_DEPENDENCIES_PATH, - val buildLogs: String = BUILD_LOG_PATH, val version: String = UPLOAD_ZIP_MANIFEST_VERSION, val hilCapabilities: List = listOf(HIL_1P_UPGRADE_CAPABILITY), - // TODO: add CLIENT_SIDE_BUILD to transformCapabilities when releasing CSB - val transformCapabilities: List = listOf(EXPLAINABILITY_V1, SELECTIVE_TRANSFORMATION_V2), - val noInteractiveMode: Boolean = true, + val transformCapabilities: List = listOf(EXPLAINABILITY_V1, CLIENT_SIDE_BUILD, SELECTIVE_TRANSFORMATION_V2), val customBuildCommand: String = MAVEN_BUILD_RUN_UNIT_TESTS, val requestedConversions: RequestedConversions? = null, // only used for SQL conversions for now + var dependencyUpgradeConfigFile: String? = null, + val noInteractiveMode: Boolean = true, + val compilationsJsonFile: String = COMPILATIONS_JSON_FILE, ) data class RequestedConversions( diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeModernizerState.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeModernizerState.kt index 9c4f4727962..2c8f30975e6 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeModernizerState.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/state/CodeModernizerState.kt @@ -7,10 +7,12 @@ import com.intellij.openapi.components.BaseState import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.JavaSdkVersion import com.intellij.util.xmlb.annotations.Property -import software.aws.toolkits.jetbrains.services.codemodernizer.EXPLAINABILITY_V1 +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CLIENT_SIDE_BUILD import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerSessionContext +import software.aws.toolkits.jetbrains.services.codemodernizer.model.EXPLAINABILITY_V1 import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId import software.aws.toolkits.jetbrains.services.codemodernizer.model.MAVEN_BUILD_RUN_UNIT_TESTS +import software.aws.toolkits.jetbrains.services.codemodernizer.model.SELECTIVE_TRANSFORMATION_V2 import software.aws.toolkits.jetbrains.services.codemodernizer.utils.toVirtualFile enum class JobDetails { @@ -67,7 +69,7 @@ class CodeModernizerState : BaseState() { configurationFile, sourceJavaSdkVersion, targetJavaSdkVersion, - listOf(EXPLAINABILITY_V1), // default to one diff + listOf(EXPLAINABILITY_V1, SELECTIVE_TRANSFORMATION_V2, CLIENT_SIDE_BUILD), lastJobContext[JobDetails.CUSTOM_BUILD_COMMAND] ?: MAVEN_BUILD_RUN_UNIT_TESTS // default to running unit tests ) } diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt index f3b424d8e3c..de8785b5c94 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt @@ -66,8 +66,6 @@ data class PollingResult( val transformationPlan: TransformationPlan?, ) -private const val IS_CLIENT_SIDE_BUILD_ENABLED = false - /** * Wrapper around [waitUntil] that polls the API DescribeMigrationJob to check the migration job status. */ @@ -88,6 +86,7 @@ suspend fun JobId.pollTransformationStatusAndPlan( var transformationResponse: GetTransformationResponse? = null var transformationPlan: TransformationPlan? = null var didSleepOnce = false + var hasSeenTransforming = false val maxRefreshes = 10 var numRefreshes = 0 @@ -116,13 +115,16 @@ suspend fun JobId.pollTransformationStatusAndPlan( if (isDisposed.get()) throw AlreadyDisposedException("The invoker is disposed.") transformationResponse = clientAdaptor.getCodeModernizationJob(this.id) val newStatus = transformationResponse?.transformationJob()?.status() ?: throw RuntimeException("Unable to get job status") + if (newStatus == TransformationStatus.TRANSFORMING) { + hasSeenTransforming = true + } var newPlan: TransformationPlan? = null - if (newStatus in STATES_WHERE_PLAN_EXIST && transformType != CodeTransformType.SQL_CONVERSION) { // no plan for SQL conversions + if (hasSeenTransforming && transformType != CodeTransformType.SQL_CONVERSION) { // no plan for SQL conversions delay(sleepDurationMillis) newPlan = clientAdaptor.getCodeModernizationPlan(this).transformationPlan() } - // TODO: remove flag when releasing CSB - if (IS_CLIENT_SIDE_BUILD_ENABLED && newStatus == TransformationStatus.TRANSFORMING && newPlan != null) { + // TODO: handle case where PlannerAgent may request mvn dependency:tree; not needed for now + if (hasSeenTransforming && newPlan != null) { attemptLocalBuild(newPlan, this, project) } if (newStatus != state) { @@ -189,44 +191,47 @@ suspend fun processClientInstructions(clientInstructionsPath: Path, jobId: JobId val targetDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(copyOfProjectSources.toFile()) ?: throw RuntimeException("Cannot find copy of project sources directory") - withContext(EDT) { - runWriteAction { - // create temp module with project copy so that we can apply diff.patch - val modifiableModel = ModuleManager.getInstance(project).getModifiableModel() - val tempModule = modifiableModel.newModule( - Paths.get(targetDir.path).resolve("temp.iml").toString(), - JavaModuleType.getModuleType().id - ) + if (clientInstructionsPath.toFile().readText().trim().isNotEmpty()) { + withContext(EDT) { + runWriteAction { + // create temp module with project copy so that we can apply diff.patch + val modifiableModel = ModuleManager.getInstance(project).getModifiableModel() + val tempModule = modifiableModel.newModule( + Paths.get(targetDir.path).resolve("temp.iml").toString(), + JavaModuleType.getModuleType().id + ) - try { - val moduleModel = ModuleRootManager.getInstance(tempModule).modifiableModel - moduleModel.addContentEntry(targetDir.url) - moduleModel.commit() - modifiableModel.commit() + try { + val moduleModel = ModuleRootManager.getInstance(tempModule).modifiableModel + moduleModel.addContentEntry(targetDir.url) + moduleModel.commit() + modifiableModel.commit() - // apply diff.patch - val patchReader = PatchReader(clientInstructionsPath) - patchReader.parseAllPatches() - PatchApplier( - project, - targetDir, - patchReader.allPatches, - null, - null - ).execute() - getLogger().info { "Successfully applied patch file at $clientInstructionsPath" } + // apply diff.patch + val patchReader = PatchReader(clientInstructionsPath) + patchReader.parseAllPatches() + PatchApplier( + project, + targetDir, + patchReader.allPatches, + null, + null + ).execute() + getLogger().info { "Successfully applied patch file at $clientInstructionsPath" } - val virtualFile = LocalFileSystem.getInstance().findFileByIoFile(clientInstructionsPath.toFile()) - ?: throw RuntimeException("Cannot find patch file at $clientInstructionsPath") - FileEditorManager.getInstance(project).openFile(virtualFile, true) - } catch (e: Exception) { - getLogger().error { - "Error applying intermediate diff.patch for job ${jobId.id} and artifact $artifactId located at " + - "$clientInstructionsPath: $e" - } - } finally { - runWriteAction { - ModuleManager.getInstance(project).disposeModule(tempModule) + val virtualFile = LocalFileSystem.getInstance().findFileByIoFile(clientInstructionsPath.toFile()) + ?: throw RuntimeException("Cannot find patch file at $clientInstructionsPath") + FileEditorManager.getInstance(project).openFile(virtualFile, true) + } catch (e: Exception) { + getLogger().error { + "Error applying intermediate diff.patch for job ${jobId.id} and artifact $artifactId located at " + + "$clientInstructionsPath: $e" + } + throw e + } finally { + runWriteAction { + ModuleManager.getInstance(project).disposeModule(tempModule) + } } } } @@ -244,10 +249,19 @@ suspend fun processClientInstructions(clientInstructionsPath: Path, jobId: JobId CodeModernizerManager.getInstance(project).codeTransformationSession?.uploadPayload(uploadZip, uploadContext) getLogger().info { "Upload succeeded; about to call ResumeTransformation for job ${jobId.id} and artifact $artifactId now" } CodeModernizerManager.getInstance(project).codeTransformationSession?.resumeTransformation() + getLogger().info { "ResumeTransformation succeeded for job ${jobId.id}" } + } catch (e: Exception) { + getLogger().error { "Upload / resume job failed for job ${jobId.id} and artifact $artifactId: $e" } + if (e.message?.contains("find a step in desired state:AWAITING_CLIENT_ACTION") == true) { + getLogger().info { "Resuming job after server-side timeout" } + CodeModernizerManager.getInstance(project).codeTransformationSession?.resumeTransformation() + } else { + throw e + } } finally { uploadZip.deleteRecursively() copyOfProjectSources.toFile().deleteRecursively() - getLogger().info { "Deleted copy of project sources and client-side build upload ZIP" } + getLogger().info { "Deleted uploadZip and copyOfProjectSources" } } // switch back to Transformation Hub view runInEdt { @@ -262,6 +276,7 @@ suspend fun downloadClientInstructions(jobId: JobId, artifactId: String, project val downloadBytes = client.downloadExportResultArchive(jobId, artifactId) val downloadZipPath = zipToPath(downloadBytes, exportZipPath.toPath()).first.toAbsolutePath() unzipFile(downloadZipPath, exportZipPath.toPath()) + downloadZipPath.toFile().deleteRecursively() return exportZipPath.toPath().resolve("diff.patch") } diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformFileUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformFileUtils.kt index ea19a954342..f5b97786c30 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformFileUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformFileUtils.kt @@ -197,7 +197,8 @@ fun parseXmlDependenciesReport(pathToXmlDependency: Path): DependencyUpdatesRepo } fun validateCustomVersionsFile(file: VirtualFile): Boolean { - if (!file.name.lowercase().endsWith(".yaml")) { + val validFileEndings = listOf("yaml", "yml") + if (!validFileEndings.any { file.name.lowercase().endsWith(it) }) { getLogger().error { "Custom versions file is not a YAML file: ${file.name}" } return false } diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt index ab7242be2ea..cca45b5d79f 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt @@ -49,13 +49,17 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Transformation import software.amazon.awssdk.services.codewhispererruntime.model.UploadContext import software.amazon.awssdk.services.ssooidc.model.SsoOidcException import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CLIENT_SIDE_BUILD import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerJobCompletedResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerSessionContext import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerStartJobResult import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformHilDownloadArtifact import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType +import software.aws.toolkits.jetbrains.services.codemodernizer.model.EXPLAINABILITY_V1 +import software.aws.toolkits.jetbrains.services.codemodernizer.model.IDE import software.aws.toolkits.jetbrains.services.codemodernizer.model.MAVEN_BUILD_SKIP_UNIT_TESTS import software.aws.toolkits.jetbrains.services.codemodernizer.model.MavenCopyCommandsResult +import software.aws.toolkits.jetbrains.services.codemodernizer.model.SELECTIVE_TRANSFORMATION_V2 import software.aws.toolkits.jetbrains.services.codemodernizer.model.UploadFailureReason import software.aws.toolkits.jetbrains.services.codemodernizer.model.ZipCreationResult import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService @@ -146,7 +150,7 @@ class CodeWhispererCodeModernizerSessionTest : CodeWhispererCodeModernizerTestBa root.children[0], JavaSdkVersion.JDK_1_8, JavaSdkVersion.JDK_11, - listOf(EXPLAINABILITY_V1, SELECTIVE_TRANSFORMATION_V2), + listOf(EXPLAINABILITY_V1, SELECTIVE_TRANSFORMATION_V2, CLIENT_SIDE_BUILD, IDE), MAVEN_BUILD_SKIP_UNIT_TESTS ) val mockFile = mock(File::class.java) @@ -164,16 +168,18 @@ class CodeWhispererCodeModernizerSessionTest : CodeWhispererCodeModernizerTestBa Path("manifest.json") -> { assertThat(fileContent).isNotNull() assertThat(fileContent).contains(MAVEN_BUILD_SKIP_UNIT_TESTS) + assertThat(fileContent).contains(EXPLAINABILITY_V1) assertThat(fileContent).contains(SELECTIVE_TRANSFORMATION_V2) + assertThat(fileContent).contains(CLIENT_SIDE_BUILD) + assertThat(fileContent).contains(IDE) assertThat(fileContent).contains("\"noInteractiveMode\":true") } Path("sources/src/tmp.txt") -> assertThat(fileContent).isEqualTo(fileText) - Path("build-logs.txt") -> assertThat(fileContent).isNotNull() else -> fail("Unexpected entry in zip file: $entry") } } zipFile.close() - assert(numEntries == 3) + assert(numEntries == 2) } } @@ -207,7 +213,6 @@ class CodeWhispererCodeModernizerSessionTest : CodeWhispererCodeModernizerTestBa Path("manifest.json") -> assertThat(fileContent).isNotNull() Path("sources/src/tmp.java") -> assertThat(fileContent).isEqualTo(fileText) Path("sources/pom.xml") -> assertThat(fileContent).isEqualTo(fileText) - Path("build-logs.txt") -> assertThat(fileContent).isNotNull() else -> fail("Unexpected entry in zip file: $entry") } } @@ -244,7 +249,6 @@ class CodeWhispererCodeModernizerSessionTest : CodeWhispererCodeModernizerTestBa Path("manifest.json") -> assertThat(fileContent).isNotNull() Path("sources/src/tmp.java") -> assertThat(fileContent).isEqualTo(fileText) Path("sources/pom.xml") -> assertThat(fileContent).isEqualTo(fileText) - Path("build-logs.txt") -> assertThat(fileContent).isNotNull() else -> fail("Unexpected entry in zip file: $entry") } } @@ -284,7 +288,6 @@ class CodeWhispererCodeModernizerSessionTest : CodeWhispererCodeModernizerTestBa Path("sources/pom.xml") -> assertThat(fileContent).isEqualTo("pom.xml") Path("sources/someModule/src/helloworld.java") -> assertThat(fileContent).isEqualTo("someModule/src/helloworld.java") Path("sources/someModule/pom.xml") -> assertThat(fileContent).isEqualTo("someModule/pom.xml") - Path("build-logs.txt") -> assertThat(fileContent).isNotNull() else -> fail("Unexpected entry in zip file: $entry") } } @@ -324,7 +327,6 @@ class CodeWhispererCodeModernizerSessionTest : CodeWhispererCodeModernizerTestBa Path("sources/pom.xml") -> assertThat(fileContent).isEqualTo("pom.xml") Path("sources/someModule/src/helloworld.java") -> assertThat(fileContent).isEqualTo("someModule\\src\\helloworld.java") Path("sources/someModule/pom.xml") -> assertThat(fileContent).isEqualTo("someModule\\pom.xml") - Path("build-logs.txt") -> assertThat(fileContent).isNotNull() else -> fail("Unexpected entry in zip file: $entry") } } @@ -362,7 +364,6 @@ class CodeWhispererCodeModernizerSessionTest : CodeWhispererCodeModernizerTestBa Path("sources/pom.xml") -> assertThat(fileContent).isEqualTo("pom.xml") Path("sources/src/tmp.java") -> assertThat(fileContent).isEqualTo("src/tmp.java") Path("sources/someModule/pom.xml") -> assertThat(fileContent).isEqualTo("someModule/pom.xml") - Path("build-logs.txt") -> assertThat(fileContent).isNotNull() else -> throw AssertionError("Unexpected entry in zip file: $entry") } } diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt index a257245a23c..38335097869 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt @@ -397,7 +397,7 @@ class CodeWhispererCodeModernizerUtilsTest : CodeWhispererCodeModernizerTestBase @Test fun `WHEN validateCustomVersionsFile on fully valid yaml file THEN passes validation`() { - val sampleFileContents = """name: "custom-dependency-management" + val sampleFileContents = """name: "dependency-upgrade" description: "Custom dependency version management for Java migration from JDK 8/11/17 to JDK 17/21" dependencyManagement: dependencies: @@ -406,7 +406,7 @@ dependencyManagement: versionProperty: "library1.version" originType: "FIRST_PARTY" plugins: - - identifier: "com.example.plugin" + - identifier: "com.example:plugin" targetVersion: "1.2.0" versionProperty: "plugin.version" """.trimIndent() @@ -418,7 +418,7 @@ dependencyManagement: @Test fun `WHEN validateCustomVersionsFile on invalid yaml file THEN fails validation`() { - val sampleFileContents = """name: "custom-dependency-management" + val sampleFileContents = """name: "dependency-upgrade" description: "Custom dependency version management for Java migration from JDK 8/11/17 to JDK 17/21" invalidKey: dependencies: @@ -427,7 +427,7 @@ invalidKey: versionProperty: "library1.version" originType: "FIRST_PARTY" plugins: - - identifier: "com.example.plugin" + - identifier: "com.example:plugin" targetVersion: "1.2.0" versionProperty: "plugin.version" """.trimIndent() @@ -439,7 +439,7 @@ invalidKey: @Test fun `WHEN validateCustomVersionsFile on non-yaml file THEN fails validation`() { - val sampleFileContents = """name: "custom-dependency-management" + val sampleFileContents = """name: "dependency-upgrade" description: "Custom dependency version management for Java migration from JDK 8/11/17 to JDK 17/21" dependencyManagement: dependencies: @@ -448,7 +448,7 @@ dependencyManagement: versionProperty: "library1.version" originType: "FIRST_PARTY" plugins: - - identifier: "com.example.plugin" + - identifier: "com.example:plugin" targetVersion: "1.2.0" versionProperty: "plugin.version" """.trimIndent() diff --git a/plugins/amazonq/contrib/QCT-Maven-6-16.jar b/plugins/amazonq/contrib/QCT-Maven-6-16.jar new file mode 100644 index 00000000000..bdc734b4d7b Binary files /dev/null and b/plugins/amazonq/contrib/QCT-Maven-6-16.jar differ diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties index ce7f4f6921a..45c4caea56b 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -646,6 +646,10 @@ codemodernizer.chat.message.button.view_summary=View summary codemodernizer.chat.message.changes_applied=I applied the changes to your project. codemodernizer.chat.message.choose_objective=I can help you with the following tasks:\n- Upgrade your Java 8, Java 11, and Java 17 codebases to Java 17 or Java 21.\n- Upgrade Java 17 or Java 21 code with up-to-date libraries and other dependencies.\n- Convert embedded SQL code for Oracle to PostgreSQL database migrations in AWS DMS.\n\nWhat would you like to do? You can enter "language upgrade" or "sql conversion". codemodernizer.chat.message.choose_objective_placeholder=Enter "language upgrade" or "sql conversion" +codemodernizer.chat.message.custom_dependency_upgrades_continue=Ok, I will continue the transformation without additional dependency upgrade information. +codemodernizer.chat.message.custom_dependency_upgrades_invalid=I wasn't able to parse the dependency upgrade file. Check that it's configured properly and try again. For an example of the required dependency upgrade file format, see the [documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/code-transformation.html#dependency-upgrade-file). +codemodernizer.chat.message.custom_dependency_upgrades_prompt=Would you like to provide a dependency upgrade file? You can specify first-party dependencies to upgrade in a YAML file, and I will upgrade them during the JDK upgrade (for example, Java 8 to 17). You can initiate a separate transformation (17 to 17 or 21 to 21) after the initial JDK upgrade to transform third-party dependencies.\n\nWithout a YAML file, I can perform a minimum JDK upgrade, and then you can initiate a separate transformation to upgrade all third-party dependencies as part of a maximum transformation. For an example dependency upgrade file, see the [documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/code-transformation.html#dependency-upgrade-file). +codemodernizer.chat.message.custom_dependency_upgrades_valid=The dependency upgrade file looks good. I will use this information to upgrade the dependencies you specified. codemodernizer.chat.message.download_failed_client_instructions_expired=Your transformation is not available anymore. Your code and transformation summary are deleted 24 hours after the transformation completes. Please try starting the transformation again. codemodernizer.chat.message.download_failed_invalid_artifact=Sorry, I was unable to find your {0}. Artifacts are deleted after 24 hours. Please try starting the transformation again. codemodernizer.chat.message.download_failed_other=Sorry, I ran into an issue while trying to download your {0}. Please try again. {1} @@ -674,17 +678,17 @@ codemodernizer.chat.message.hil.start_message=I was not able to upgrade all depe codemodernizer.chat.message.hil.trying_resume=Trying to resume transformation with your selected version. codemodernizer.chat.message.hil.user_rejected=I'll continue upgrading your module. When I'm done, you can review the dependency error in the Transformation summary. codemodernizer.chat.message.local_build_begin=I'm building your module. This can take up to 10 minutes, depending on the size of your module. -codemodernizer.chat.message.local_build_failed=Sorry, I couldn't run the Maven clean install command to build your module. +codemodernizer.chat.message.local_build_failed=Sorry, I couldn't run the Maven clean test-compile command to build your module. codemodernizer.chat.message.local_build_success=I was able to build your module and will start uploading your code. codemodernizer.chat.message.result.fail=Sorry, I ran into an issue during the transformation. Please try again. codemodernizer.chat.message.result.fail_initial_build=I am having trouble building your project in the secure build environment and couldn't complete the transformation. codemodernizer.chat.message.result.fail_initial_build_no_build_log=I am having trouble building your project in the secure build environment: {0}. codemodernizer.chat.message.result.fail_with_known_reason=Sorry, I couldn''t complete the transformation. {0} -codemodernizer.chat.message.result.partially_success=I transformed part of your code. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated and the errors that prevented a complete transformation. After successfully building in Java 17 or 21, an additional transformation is required to upgrade your libraries and dependencies. Choose the same source code version and target code version (for example, 17 to 17) to do this. -codemodernizer.chat.message.result.success=I successfully completed your transformation. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the changes I am proposing. After successfully building in Java 17 or 21, an additional transformation is required to upgrade your libraries and dependencies. Choose the same source code version and target code version (for example, 17 to 17) to do this. +codemodernizer.chat.message.result.partially_success=I transformed part of your code. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated and the errors that prevented a complete transformation. After successfully transforming to Java 17 or 21, an additional transformation is required to upgrade your libraries and dependencies. Choose the same source code version and target code version (for example, 17 to 17) to do this. +codemodernizer.chat.message.result.success=I successfully completed your transformation. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the changes I am proposing. After successfully transforming to Java 17 or 21, an additional transformation is required to upgrade your libraries and dependencies. Choose the same source code version and target code version (for example, 17 to 17) to do this. codemodernizer.chat.message.result.zip_too_large=Sorry, your project size exceeds the Amazon Q Code Transformation upload limit of 2GB. codemodernizer.chat.message.resume_ongoing=I'm still transforming your code. It can take 10 to 30 minutes to upgrade your code, depending on the size of your module. To monitor progress, go to the Transformation Hub. -codemodernizer.chat.message.skip_tests=I will build your project using `mvn clean test` by default. If you would like me to build your project without running unit tests, I will use `mvn clean test-compile`. +codemodernizer.chat.message.skip_tests=I will build generated code in your local environment, not on the server side. For information on how I scan code to reduce security risks associated with building the code in your local environment, see the [Amazon Q Developer documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/code-transformation.html#java-local-builds).\n\nI will build your project using `mvn clean test` by default. If you would like me to build your project without running unit tests, I will use `mvn clean test-compile`. codemodernizer.chat.message.skip_tests_form.response=Okay, I will {0} when building your module. codemodernizer.chat.message.skip_tests_form.run_tests=Run unit tests codemodernizer.chat.message.skip_tests_form.skip=Skip unit tests @@ -702,7 +706,7 @@ codemodernizer.chat.message.upload_failed_other=Sorry, I was unable to upload yo codemodernizer.chat.message.upload_failed_ssl_error=Sorry, I was unable to upload your project. This might have been caused by your IDE not trusting the certificate of your HTTP proxy. Ensure all certificates for your proxy client have been configured in your IDE, and then retry transformation. codemodernizer.chat.message.upload_failed_url_expired=Sorry, I couldn't upload your project to begin the transformation. The Amazon S3 pre-signed URL used to upload your code expired after 30 minutes. This might have been caused by delays introduced by intermediate services in your network infrastructure.\n\nCheck your network configuration for services that might be causing delays. If the issue persists, you might need to allow list the following Amazon S3 bucket: 'amazonq-code-transformation-us-east-1-c6160f047e0.s3.amazonaws.com'. codemodernizer.chat.message.validation.check_eligible_modules=Checking for eligible modules... -codemodernizer.chat.message.validation.check_passed=I can upgrade your Java module. To start the transformation, I need some information from you. Choose the module you want to upgrade and the target code version to upgrade to. Then, choose **Confirm**.\n\nIf you do not see the module you want to transform, you might need to configure your project so that I can find it. Go to File and choose Project Structure. In the Projects tab, set the correct project JDK and language level. In the Modules tab, set the correct module JDK and language level.\n\nAfter successfully building in Java 17 or 21, an additional transformation is required to upgrade your libraries and dependencies. Choose the same source code version and target code version (for example, 17 to 17) to do this. +codemodernizer.chat.message.validation.check_passed=I can upgrade your Java module. To start the transformation, I need some information from you. Choose the module you want to upgrade and the target code version to upgrade to. Then, choose **Confirm**.\n\nIf you do not see the module you want to transform, you might need to configure your project so that I can find it. Go to File and choose Project Structure. In the Projects tab, set the correct project JDK and language level. In the Modules tab, set the correct module JDK and language level.\n\nAfter successfully transforming to Java 17 or 21, an additional transformation is required to upgrade your libraries and dependencies. Choose the same source code version and target code version (for example, 17 to 17) to do this.\n\nI will perform the transformation based on your project's requests, descriptions, and content. To maintain security, avoid including external, unvetted artifacts in your project repository prior to starting the transformation and always validate transformed code for both functionality and security. codemodernizer.chat.message.validation.error.downgrade_attempt=I can't transform a project from Java 21 to Java 17, but I can upgrade Java 21 code with up-to-date libraries and other dependencies. Try again with a supported language upgrade. codemodernizer.chat.message.validation.error.invalid_sct=It looks like the .sct file you provided isn't valid. Make sure that you've uploaded the .zip file you retrieved from your schema conversion in AWS DMS. codemodernizer.chat.message.validation.error.invalid_source_db=I can only convert SQL for migrations from an Oracle source database. The provided .sct file indicates another source database for this migration. @@ -789,7 +793,7 @@ codemodernizer.notification.warn.expired_credentials.title=Your connection to Q codemodernizer.notification.warn.invalid_project.description.reason.missing_content_roots=None of your open modules are supported for code transformation with Amazon Q. Amazon Q can upgrade Java 8, Java 11, Java 17, and Java 21 projects built on Maven, with content roots configured. codemodernizer.notification.warn.invalid_project.description.reason.not_logged_in=Amazon Q cannot start the transformation as you are not logged in with Identity Center or Builder ID. Also ensure that you are not using IntelliJ version 232.8660.185 and that you are not developing on a remote host (uncommon). codemodernizer.notification.warn.invalid_project.description.reason.remote_backend=None of your open modules are supported for code transformation with Amazon Q. Amazon Q cannot transform modules running on a remote host. -codemodernizer.notification.warn.maven_failed.content=Amazon Q could not run the Maven clean install command to build your module. +codemodernizer.notification.warn.maven_failed.content=Amazon Q could not run the Maven clean test-compile command to build your module. codemodernizer.notification.warn.maven_failed.title=Amazon Q Code Transform unable to zip dependencies codemodernizer.notification.warn.on_resume.unknown_status_response.content=We received data from Amazon Q in a format that the plugin cannot handle. You may need to update the plugin and then try again. codemodernizer.notification.warn.on_resume.unknown_status_response.title=Unable to resume job diff --git a/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/transformTests/TransformChatTest.kt b/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/transformTests/TransformChatTest.kt index 7fe70ccb337..e4858f2e36b 100644 --- a/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/transformTests/TransformChatTest.kt +++ b/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/transformTests/TransformChatTest.kt @@ -71,7 +71,7 @@ async function testNavigation() { const button = document.querySelector('button[action-id="codetransform-input-confirm-skip-tests"]') button.click() }) -/* + const selectCustomVersionsForm = await page.waitForSelector('button[action-id="codetransform-input-confirm-custom-dependency-versions"]', { timeout: 5000 }) @@ -88,11 +88,6 @@ async function testNavigation() { const errorMessage = await page.waitForSelector('text/I could not find "dummy-target-jdk-name-here" in File > Project Structure > Platform Settings > SDKs.', { timeout: 5000 }) -*/ - // TODO: delete errorMessage below, and uncomment the above when releasing CSB - const errorMessage = await page.waitForSelector('text/Sorry, I couldn\'t run the Maven clean install command', { - timeout: 5000 - }) console.log('Error message:', await errorMessage.evaluate(el => el.textContent)) } } finally { @@ -155,10 +150,8 @@ class TransformChatTest { "Choose a module to transform", "Choose the target code version", "Skip tests form appeared: true", - "couldn't run the Maven clean install command" - // TODO: delete line above, and uncomment lines below when releasing CSB - // "I could not find \"dummy-target-jdk-name-here\"", - // "Custom dependency versions file form appeared: true", + "Custom dependency versions file form appeared: true", + "I could not find \"dummy-target-jdk-name-here\"" ) } }