Skip to content

Commit 7ace3c3

Browse files
smoring2Parsa JoratjoratpXi Shenrli
authored
Amazon Q Transform: Provide IDE bundled MavenRunner for transform (#4070)
* added the addParentsPom to the maven command * added the changelog * Updated the change log message to be shorter per request. * Updated the chnage log to include amazon q transform * testing for updated mavne command * temp mvnrunner * format * show transformation hub once the mvnrunner finished * remove the showtransformationhub to the finally * add telemetry * todo:unit test * a mavenrunner bug * fix format * add exception for mavenrunner * change to catch throwable * add a unit test * slove the tread leak in unit test * resume the .idea * resume the .idea * add the feature log * resume to throw the exception when mvn * revise the mavne failure notification message * change the java class to kotlin & catch the NoClassDefFoundError exception * fix the format * fix detektMain * Update .changes/next-release/feature-94919c2c-0d33-4f58-9c8e-f5b8cb661d1f.json Co-authored-by: Richard Li <[email protected]> * Update jetbrains-core/src/software/aws/toolkits/jetbrains/services/codemodernizer/ideMaven/TransformMavenRunner.kt Co-authored-by: Richard Li <[email protected]> * Apply suggestions from code review Co-authored-by: Richard Li <[email protected]> * dedupe the commands * remove unnecessary ? --------- Co-authored-by: Parsa Jorat <[email protected]> Co-authored-by: joratp <[email protected]> Co-authored-by: Xi Shen <[email protected]> Co-authored-by: Richard Li <[email protected]>
1 parent 081a0d8 commit 7ace3c3

File tree

8 files changed

+199
-24
lines changed

8 files changed

+199
-24
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "feature",
3+
"description" : "Amazon Q Transform: Use the IDE Maven runner as a fallback"
4+
}

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ mockito = "4.6.1"
2121
mockitoKotlin = "4.0.0"
2222
mockk = "1.13.8"
2323
node-gradle = "7.0.1"
24-
telemetryGenerator = "1.0.171"
24+
telemetryGenerator = "1.0.176"
2525
testLogger = "3.1.0"
2626
testRetry = "1.5.2"
2727
slf4j = "1.7.36"

jetbrains-core/src/software/aws/toolkits/jetbrains/services/codemodernizer/ArtifactHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class ArtifactHandler(private val project: Project, private val clientAdaptor: G
8686
downloadResultsResponse = clientAdaptor.downloadExportResultArchive(job)
8787
} catch (e: Exception) {
8888
CodetransformTelemetry.logApiError(
89-
codeTransformApiNames = CodeTransformApiNames.StartTransformation,
89+
codeTransformApiNames = CodeTransformApiNames.ExportResultArchive,
9090
codeTransformSessionId = CodeTransformTelemetryState.instance.getSessionId(),
9191
codeTransformApiErrorMessage = e.message.toString(),
9292
codeTransformJobId = job.id,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.codemodernizer.ideMaven
5+
6+
import com.intellij.execution.process.ProcessAdapter
7+
import com.intellij.execution.process.ProcessEvent
8+
import com.intellij.execution.runners.ProgramRunner
9+
import com.intellij.execution.ui.RunContentDescriptor
10+
import com.intellij.openapi.fileEditor.FileDocumentManager
11+
import com.intellij.openapi.project.Project
12+
import org.jetbrains.idea.maven.execution.MavenRunConfigurationType
13+
import org.jetbrains.idea.maven.execution.MavenRunnerParameters
14+
import org.jetbrains.idea.maven.execution.MavenRunnerSettings
15+
16+
class TransformMavenRunner(val project: Project) {
17+
18+
fun run(parameters: MavenRunnerParameters, settings: MavenRunnerSettings, onComplete: TransformRunnable) {
19+
FileDocumentManager.getInstance().saveAllDocuments()
20+
val callback = ProgramRunner.Callback { descriptor: RunContentDescriptor ->
21+
val handler = descriptor.processHandler
22+
if (handler == null) {
23+
// add log error here
24+
onComplete.exitCode(-1)
25+
return@Callback
26+
}
27+
handler.addProcessListener(object : ProcessAdapter() {
28+
override fun processTerminated(event: ProcessEvent) {
29+
onComplete.exitCode(event.exitCode)
30+
}
31+
})
32+
}
33+
MavenRunConfigurationType.runConfiguration(project, parameters, null, settings, callback, false)
34+
}
35+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.codemodernizer.ideMaven
5+
6+
class TransformRunnable : Runnable {
7+
private var isComplete: Int? = null
8+
9+
fun exitCode(i: Int) {
10+
isComplete = i
11+
}
12+
13+
override fun run() {
14+
// do nothing
15+
}
16+
17+
fun isComplete(): Int? = isComplete
18+
}

jetbrains-core/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/CodeModernizerSessionContext.kt

Lines changed: 108 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,54 @@ import com.intellij.execution.configurations.GeneralCommandLine
88
import com.intellij.execution.process.ProcessNotCreatedException
99
import com.intellij.execution.process.ProcessOutput
1010
import com.intellij.execution.util.ExecUtil
11+
import com.intellij.openapi.application.runInEdt
1112
import com.intellij.openapi.application.runReadAction
1213
import com.intellij.openapi.project.Project
1314
import com.intellij.openapi.projectRoots.JavaSdkVersion
1415
import com.intellij.openapi.vfs.VfsUtil
1516
import com.intellij.openapi.vfs.VirtualFile
17+
import com.intellij.openapi.wm.ToolWindowManager
18+
import org.jetbrains.idea.maven.execution.MavenRunner
19+
import org.jetbrains.idea.maven.execution.MavenRunnerParameters
1620
import software.aws.toolkits.core.utils.createTemporaryZipFile
1721
import software.aws.toolkits.core.utils.error
1822
import software.aws.toolkits.core.utils.getLogger
1923
import software.aws.toolkits.core.utils.putNextEntry
2024
import software.aws.toolkits.core.utils.warn
25+
import software.aws.toolkits.jetbrains.services.codemodernizer.ideMaven.TransformMavenRunner
26+
import software.aws.toolkits.jetbrains.services.codemodernizer.ideMaven.TransformRunnable
27+
import software.aws.toolkits.jetbrains.services.codemodernizer.panels.managers.CodeModernizerBottomWindowPanelManager
2128
import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeTransformTelemetryState
29+
import software.aws.toolkits.jetbrains.services.codemodernizer.toolwindow.CodeModernizerBottomToolWindowFactory
30+
import software.aws.toolkits.resources.message
2231
import software.aws.toolkits.telemetry.CodeTransformMavenBuildCommand
2332
import software.aws.toolkits.telemetry.CodetransformTelemetry
2433
import java.io.File
2534
import java.io.IOException
35+
import java.lang.Thread.sleep
2636
import java.nio.file.FileVisitOption
2737
import java.nio.file.FileVisitResult
2838
import java.nio.file.Files
2939
import java.nio.file.Path
3040
import java.nio.file.SimpleFileVisitor
3141
import java.nio.file.attribute.BasicFileAttributes
42+
import kotlin.io.NoSuchFileException
43+
import kotlin.io.byteInputStream
44+
import kotlin.io.deleteRecursively
45+
import kotlin.io.inputStream
3246
import kotlin.io.path.Path
47+
import kotlin.io.relativeTo
48+
import kotlin.io.resolve
49+
import kotlin.io.resolveSibling
50+
import kotlin.io.walkTopDown
3351

3452
const val MANIFEST_PATH = "manifest.json"
3553
const val ZIP_SOURCES_PATH = "sources"
3654
const val ZIP_DEPENDENCIES_PATH = "dependencies"
3755
const val MAVEN_CONFIGURATION_FILE_NAME = "pom.xml"
3856
const val MAVEN_DEFAULT_BUILD_DIRECTORY_NAME = "target"
3957
const val IDEA_DIRECTORY_NAME = ".idea"
58+
4059
data class CodeModernizerSessionContext(
4160
val project: Project,
4261
val configurationFile: VirtualFile,
@@ -135,15 +154,19 @@ data class CodeModernizerSessionContext(
135154
fun runMavenCommand(sourceFolder: File): File? {
136155
val currentTimestamp = System.currentTimeMillis()
137156
val destinationDir = Files.createTempDirectory("transformation_dependencies_temp_" + currentTimestamp)
157+
val commandList = listOf(
158+
"dependency:copy-dependencies",
159+
"-DoutputDirectory=$destinationDir",
160+
"-Dmdep.useRepositoryLayout=true",
161+
"-Dmdep.copyPom=true",
162+
"-Dmdep.addParentPoms=true"
163+
)
138164
fun runCommand(mavenCommand: String): ProcessOutput {
139-
val commandLine = GeneralCommandLine(
140-
mavenCommand,
141-
"dependency:copy-dependencies",
142-
"-DoutputDirectory=$destinationDir",
143-
"-Dmdep.useRepositoryLayout=true",
144-
"-Dmdep.copyPom=true",
145-
"-Dmdep.addParentPoms=true"
146-
)
165+
val command = buildList {
166+
add(mavenCommand)
167+
addAll(commandList)
168+
}
169+
val commandLine = GeneralCommandLine(command)
147170
.withWorkDirectory(sourceFolder)
148171
.withRedirectErrorStream(true)
149172
val output = ExecUtil.execAndGetOutput(commandLine)
@@ -203,6 +226,7 @@ data class CodeModernizerSessionContext(
203226
)
204227
return null
205228
} else {
229+
shouldTryMvnCommand = false
206230
LOG.warn { "Maven executed successfully" }
207231
}
208232
} catch (e: ProcessNotCreatedException) {
@@ -213,7 +237,6 @@ data class CodeModernizerSessionContext(
213237
reason = error
214238
)
215239
LOG.warn { error }
216-
return null
217240
} catch (e: Exception) {
218241
CodetransformTelemetry.mvnBuildFailed(
219242
codeTransformSessionId = CodeTransformTelemetryState.instance.getSessionId(),
@@ -225,6 +248,73 @@ data class CodeModernizerSessionContext(
225248
}
226249
}
227250

251+
// 3. intellij-bundled maven runner
252+
if (shouldTryMvnCommand) {
253+
LOG.warn { "Executing IntelliJ bundled Maven" }
254+
val explicitenabled = emptyList<String>()
255+
try {
256+
val params = MavenRunnerParameters(
257+
false,
258+
sourceFolder.absolutePath,
259+
null,
260+
commandList,
261+
explicitenabled,
262+
null
263+
)
264+
265+
// Create MavenRunnerParametersMavenRunnerParameters
266+
val mvnrunner = MavenRunner.getInstance(project)
267+
val transformMvnRunner = TransformMavenRunner(project)
268+
val mvnsettings = mvnrunner.settings
269+
val createdDependencies = TransformRunnable()
270+
runInEdt {
271+
try {
272+
transformMvnRunner.run(params, mvnsettings, createdDependencies)
273+
} catch (t: Throwable) {
274+
createdDependencies.exitCode(Integer.MIN_VALUE) // to stop looking for the exitCode
275+
LOG.error { t.message.toString() }
276+
CodetransformTelemetry.mvnBuildFailed(
277+
codeTransformSessionId = CodeTransformTelemetryState.instance.getSessionId(),
278+
codeTransformMavenBuildCommand = CodeTransformMavenBuildCommand.IDEBundledMaven,
279+
reason = t.message
280+
)
281+
}
282+
}
283+
while (createdDependencies.isComplete() == null) {
284+
// waiting mavenrunner building
285+
sleep(50)
286+
}
287+
if (createdDependencies.isComplete() == 0) {
288+
LOG.warn { "IntelliJ bundled Maven executed successfully" }
289+
} else if (createdDependencies.isComplete() != Integer.MIN_VALUE) {
290+
val error = "The exitCode should be 0 while it was ${createdDependencies.isComplete()}"
291+
LOG.error { error }
292+
CodetransformTelemetry.mvnBuildFailed(
293+
codeTransformSessionId = CodeTransformTelemetryState.instance.getSessionId(),
294+
codeTransformMavenBuildCommand = CodeTransformMavenBuildCommand.IDEBundledMaven,
295+
reason = error
296+
)
297+
return null
298+
} else {
299+
// when exit code is MIN_VALUE
300+
// return null
301+
return null
302+
}
303+
} catch (t: Throwable) {
304+
LOG.error { t.message.toString() }
305+
CodetransformTelemetry.mvnBuildFailed(
306+
codeTransformSessionId = CodeTransformTelemetryState.instance.getSessionId(),
307+
codeTransformMavenBuildCommand = CodeTransformMavenBuildCommand.IDEBundledMaven,
308+
reason = t.message
309+
)
310+
return null
311+
} finally {
312+
// after the ide bundled maven building finished
313+
// change the bottom window to transformation hub
314+
showTransformationHub()
315+
}
316+
}
317+
228318
return destinationDir.toFile()
229319
}
230320

@@ -241,12 +331,21 @@ data class CodeModernizerSessionContext(
241331
}
242332
return FileVisitResult.CONTINUE
243333
}
334+
244335
override fun visitFileFailed(file: Path?, exc: IOException?): FileVisitResult =
245336
FileVisitResult.CONTINUE
246337
}
247338
)
248339
return dependencyfiles
249340
}
341+
342+
fun showTransformationHub() = runInEdt {
343+
val appModernizerBottomWindow = ToolWindowManager.getInstance(project).getToolWindow(CodeModernizerBottomToolWindowFactory.id)
344+
?: error(message("codemodernizer.toolwindow.problems_window_not_found"))
345+
appModernizerBottomWindow.show()
346+
CodeModernizerBottomWindowPanelManager.getInstance(project).setJobStartingUI()
347+
}
348+
250349
companion object {
251350
private val LOG = getLogger<CodeModernizerSessionContext>()
252351
}

jetbrains-core/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codemodernizer/CodeWhispererCodeModernizerSessionTest.kt

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import com.github.tomakehurst.wiremock.client.WireMock.put
88
import com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo
99
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
1010
import com.github.tomakehurst.wiremock.junit.WireMockRule
11+
import com.intellij.openapi.application.ApplicationManager
1112
import com.intellij.openapi.projectRoots.JavaSdkVersion
1213
import com.intellij.openapi.roots.ModuleRootManager
14+
import com.intellij.testFramework.common.ThreadLeakTracker
1315
import com.intellij.testFramework.runInEdtAndWait
1416
import kotlinx.coroutines.runBlocking
1517
import org.apache.commons.codec.digest.DigestUtils
@@ -22,6 +24,7 @@ import org.junit.Rule
2224
import org.junit.Test
2325
import org.mockito.Mockito.doReturn
2426
import org.mockito.Mockito.mock
27+
import org.mockito.Mockito.spy
2528
import org.mockito.Mockito.`when`
2629
import org.mockito.kotlin.any
2730
import org.mockito.kotlin.atLeastOnce
@@ -62,21 +65,27 @@ class CodeWhispererCodeModernizerSessionTest : CodeWhispererCodeModernizerTestBa
6265
@JvmField
6366
val wireMock = WireMockRule(WireMockConfiguration.wireMockConfig().dynamicPort())
6467

65-
lateinit var gumbyUploadUrlResponse: CreateUploadUrlResponse
66-
6768
@Before
6869
override fun setup() {
6970
super.setup()
70-
val s3endpoint = "http://127.0.0.1:${wireMock.port()}"
71-
gumbyUploadUrlResponse = CreateUploadUrlResponse.builder()
72-
.uploadUrl(s3endpoint)
73-
.uploadId("1234")
74-
.kmsKeyArn("0000000000000000000000000000000000:key/1234abcd")
75-
.responseMetadata(DefaultAwsResponseMetadata.create(mapOf(ResponseMetadata.AWS_REQUEST_ID to CodeWhispererTestUtil.testRequestId)))
76-
.sdkHttpResponse(
77-
SdkHttpResponse.builder().headers(mapOf(CodeWhispererService.KET_SESSION_ID to listOf(CodeWhispererTestUtil.testSessionId))).build()
78-
)
79-
.build() as CreateUploadUrlResponse
71+
ThreadLeakTracker.longRunningThreadCreated(ApplicationManager.getApplication(), "Process Proxy: Launcher")
72+
}
73+
74+
// when maven is not installed in the local machine and mvnw does not support this pom.xml
75+
@Test
76+
fun `CodeModernizerSessionContext shows the transformation hub once ide maven finishes`() {
77+
val module = projectRule.module
78+
val fileText = "Morning"
79+
projectRule.fixture.addFileToModule(module, "src/tmp.txt", fileText)
80+
81+
// get project.projectFile because project.projectFile can not be null
82+
val roots = ModuleRootManager.getInstance(module).contentRoots
83+
val root = roots[0]
84+
val context = spy(CodeModernizerSessionContext(project, root.children[0], JavaSdkVersion.JDK_1_8, JavaSdkVersion.JDK_11))
85+
runInEdtAndWait {
86+
context.createZipWithModuleFiles().payload
87+
verify(context, times(1)).showTransformationHub()
88+
}
8089
}
8190

8291
@Test
@@ -328,6 +337,16 @@ class CodeWhispererCodeModernizerSessionTest : CodeWhispererCodeModernizerTestBa
328337

329338
@Test
330339
fun `test uploadPayload()`() {
340+
val s3endpoint = "http://127.0.0.1:${wireMock.port()}"
341+
val gumbyUploadUrlResponse = CreateUploadUrlResponse.builder()
342+
.uploadUrl(s3endpoint)
343+
.uploadId("1234")
344+
.kmsKeyArn("0000000000000000000000000000000000:key/1234abcd")
345+
.responseMetadata(DefaultAwsResponseMetadata.create(mapOf(ResponseMetadata.AWS_REQUEST_ID to CodeWhispererTestUtil.testRequestId)))
346+
.sdkHttpResponse(
347+
SdkHttpResponse.builder().headers(mapOf(CodeWhispererService.KET_SESSION_ID to listOf(CodeWhispererTestUtil.testSessionId))).build()
348+
)
349+
.build() as CreateUploadUrlResponse
331350
val expectedSha256checksum: String =
332351
Base64.getEncoder().encodeToString(DigestUtils.sha256(FileInputStream(expectedFilePath.toAbsolutePath().toString())))
333352
clientAdaptorSpy.stub {

resources/resources/software/aws/toolkits/resources/MessagesBundle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ codemodernizer.migration_plan.substeps.description_succeed=Build succeeded
511511
codemodernizer.migration_summary.header.title=Transformation summary
512512
codemodernizer.notification.info.download.started.content=Downloading the updated code
513513
codemodernizer.notification.info.download.started.title=Download Started
514-
codemodernizer.notification.info.maven_failed.content=Failed to execute Maven. It is possible that the upload does not include dependencies.
514+
codemodernizer.notification.info.maven_failed.content=Maven build failed. It is possible that the upload does not include dependencies.
515515
codemodernizer.notification.info.maven_failed.title=Q Transform caution
516516
codemodernizer.notification.info.modernize_complete.content=Transformation job successfully finished. You can view the changes and accept changes to further test the transformed code and push it to production.
517517
codemodernizer.notification.info.modernize_complete.title=Transform Complete

0 commit comments

Comments
 (0)