Skip to content

Commit a0301d7

Browse files
authored
Improve pom detection algorithm (#4053)
* Use kotlins walkTopDown instead of intellijs collectChildrenRecursively. * Reuse validated build files instead of walking again. --------- Co-authored-by: Leonardo Araneda Freccero <[email protected]>
1 parent c499a60 commit a0301d7

File tree

7 files changed

+68
-35
lines changed

7 files changed

+68
-35
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "bugfix",
3+
"description": "Improved recursion when validating projects for Q Code Transform."
4+
}

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

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import com.intellij.openapi.project.modules
1818
import com.intellij.openapi.projectRoots.JavaSdkVersion
1919
import com.intellij.openapi.roots.ProjectRootManager
2020
import com.intellij.openapi.util.Disposer
21-
import com.intellij.openapi.vfs.VfsUtil
2221
import com.intellij.openapi.vfs.VirtualFile
2322
import com.intellij.openapi.wm.ToolWindowManager
2423
import kotlinx.coroutines.Job
@@ -44,6 +43,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModerni
4443
import software.aws.toolkits.jetbrains.services.codemodernizer.model.CustomerSelection
4544
import software.aws.toolkits.jetbrains.services.codemodernizer.model.InvalidTelemetryReason
4645
import software.aws.toolkits.jetbrains.services.codemodernizer.model.JobId
46+
import software.aws.toolkits.jetbrains.services.codemodernizer.model.MAVEN_CONFIGURATION_FILE_NAME
4747
import software.aws.toolkits.jetbrains.services.codemodernizer.model.ValidationResult
4848
import software.aws.toolkits.jetbrains.services.codemodernizer.panels.managers.CodeModernizerBottomWindowPanelManager
4949
import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeModernizerSessionState
@@ -69,6 +69,7 @@ import software.aws.toolkits.telemetry.CodeTransformPreValidationError
6969
import software.aws.toolkits.telemetry.CodeTransformStartSrcComponents
7070
import software.aws.toolkits.telemetry.CodetransformTelemetry
7171
import software.aws.toolkits.telemetry.Result
72+
import java.io.File
7273
import java.time.Instant
7374
import java.util.concurrent.atomic.AtomicBoolean
7475
import javax.swing.Icon
@@ -88,7 +89,7 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo
8889
Disposer.register(contentManager, it)
8990
}
9091
}
91-
private val supportedBuildFileNames = listOf("pom.xml")
92+
private val supportedBuildFileNames = listOf(MAVEN_CONFIGURATION_FILE_NAME)
9293
private val supportedJavaMappings = mapOf(
9394
JavaSdkVersion.JDK_1_8 to setOf(JavaSdkVersion.JDK_17),
9495
JavaSdkVersion.JDK_11 to setOf(JavaSdkVersion.JDK_17),
@@ -152,9 +153,9 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo
152153
)
153154
)
154155
}
155-
val valid = getSupportedBuildFilesInProject().isNotEmpty()
156-
return if (valid) {
157-
ValidationResult(true)
156+
val validatedBuildFiles = getSupportedBuildFilesInProject()
157+
return if (validatedBuildFiles.isNotEmpty()) {
158+
ValidationResult(true, validatedBuildFiles = validatedBuildFiles)
158159
} else {
159160
ValidationResult(
160161
false,
@@ -208,7 +209,7 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo
208209
val validationResult = validate(project)
209210
runInEdt {
210211
if (validationResult.valid) {
211-
runModernize()
212+
runModernize(validationResult.validatedBuildFiles) ?: isModernizationInProgress.set(false)
212213
} else {
213214
warnUnsupportedProject(validationResult.invalidReason)
214215
isModernizationInProgress.set(false)
@@ -245,9 +246,9 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo
245246
}
246247
}
247248

248-
fun runModernize(): Job? {
249+
fun runModernize(validatedBuildFiles: List<VirtualFile>): Job? {
249250
initStopParameters()
250-
val customerSelection = getCustomerSelection() ?: return null
251+
val customerSelection = getCustomerSelection(validatedBuildFiles) ?: return null
251252
CodetransformTelemetry.jobStartedCompleteFromPopupDialog(
252253
codeTransformJavaSourceVersionsAllowed = CodeTransformJavaSourceVersionsAllowed.from(customerSelection.sourceJavaVersion.name),
253254
codeTransformJavaTargetVersionsAllowed = CodeTransformJavaTargetVersionsAllowed.from(customerSelection.targetJavaVersion.name),
@@ -267,9 +268,9 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo
267268
CodeModernizerSessionState.getInstance(project).currentJobStopTime = Instant.MIN
268269
}
269270

270-
fun getCustomerSelection(): CustomerSelection? = PreCodeTransformUserDialog(
271+
fun getCustomerSelection(validatedBuildFiles: List<VirtualFile>): CustomerSelection? = PreCodeTransformUserDialog(
271272
project,
272-
getSupportedBuildFilesInProject(),
273+
validatedBuildFiles,
273274
supportedJavaMappings,
274275
).create()
275276

@@ -638,6 +639,29 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo
638639
managerState.flags.putAll(state.flags)
639640
}
640641

642+
private fun findBuildFiles(sourceFolder: File): List<File> {
643+
/**
644+
* For every dir, check if any supported build files (pom.xml etc) exists.
645+
* If found store it and stop further recursion.
646+
*/
647+
val buildFiles = mutableListOf<File>()
648+
sourceFolder.walkTopDown()
649+
.maxDepth(5)
650+
.onEnter { currentDir ->
651+
supportedBuildFileNames.forEach {
652+
val maybeSupportedFile = currentDir.resolve(MAVEN_CONFIGURATION_FILE_NAME)
653+
if (maybeSupportedFile.exists()) {
654+
buildFiles.add(maybeSupportedFile)
655+
return@onEnter false
656+
}
657+
}
658+
return@onEnter true
659+
}.forEach {
660+
// noop, collects the sequence
661+
}
662+
return buildFiles
663+
}
664+
641665
private fun getSupportedBuildFilesInProject(): List<VirtualFile> {
642666
/**
643667
* Strategy:
@@ -648,15 +672,14 @@ class CodeModernizerManager(private val project: Project) : PersistentStateCompo
648672
val probableProjectRoot = project.basePath?.toVirtualFile() // May point to only one intellij module (the first opened one)
649673
val probableContentRoots = projectRootManager.contentRoots.toMutableSet() // May not point to the topmost folder of modules
650674
probableContentRoots.add(probableProjectRoot) // dedupe
651-
val detectedBuildFiles = probableContentRoots.filterNotNull().flatMap { root ->
652-
VfsUtil.collectChildrenRecursively(root).filter { it.name in supportedBuildFileNames }
675+
val topLevelRoots = filterOnlyParentFiles(probableContentRoots)
676+
val detectedBuildFiles = topLevelRoots.flatMap { root ->
677+
findBuildFiles(root.toNioPath().toFile()).mapNotNull { it.path.toVirtualFile() }
653678
}
654679

655-
val topLevelBuildFiles = filterOnlyParentFiles(detectedBuildFiles)
656-
657680
val supportedModules = getSupportedModulesInProject().toSet()
658681
val validProjectJdk = project.getSupportedJavaMappingsForProject(supportedJavaMappings).isNotEmpty()
659-
return topLevelBuildFiles.filter {
682+
return detectedBuildFiles.filter {
660683
val moduleOfFile = runReadAction { projectRootManager.fileIndex.getModuleForFile(it) }
661684
return@filter (moduleOfFile in supportedModules) || (moduleOfFile == null && validProjectJdk)
662685
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ suspend fun JobId.pollTransformationStatusAndPlan(
252252
return PollingResult(true, transformationResponse?.transformationJob(), state, transformationPlan)
253253
}
254254

255-
fun filterOnlyParentFiles(filePaths: List<VirtualFile>): List<VirtualFile> {
255+
fun filterOnlyParentFiles(filePaths: Set<VirtualFile>): List<VirtualFile> {
256256
if (filePaths.isEmpty()) return listOf()
257257
// sorts it like:
258258
// foo

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33

44
package software.aws.toolkits.jetbrains.services.codemodernizer.model
55

6+
import com.intellij.openapi.vfs.VirtualFile
7+
68
data class ValidationResult(
79
val valid: Boolean,
810
val invalidReason: String? = null,
9-
val invalidTelemetryReason: InvalidTelemetryReason = InvalidTelemetryReason()
11+
val invalidTelemetryReason: InvalidTelemetryReason = InvalidTelemetryReason(),
12+
val validatedBuildFiles: List<VirtualFile> = emptyList()
1013
)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class CodeWhispererActionNodeTest {
107107
fun `runCodeModernizer`() {
108108
whenever(codeModernizerManager.validate(any())).thenReturn(ValidationResult(true))
109109
whenever(codeModernizerManager.getRunActionButtonIcon()).thenReturn(AllIcons.Actions.Execute)
110-
whenever(codeModernizerManager.runModernize()).thenReturn(Job())
110+
whenever(codeModernizerManager.runModernize(any())).thenReturn(Job())
111111
sut = CodeModernizerRunModernizeNode(project)
112112

113113
sut.onDoubleClick(mock())

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

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,13 @@ class CodeWhispererCodeModernizerTest : CodeWhispererCodeModernizerTestBase() {
4848

4949
@Test
5050
fun `test runModernize handles user cancelled dialog`() {
51-
doReturn(null).whenever(codeModernizerManagerSpy).getCustomerSelection()
51+
doReturn(null).whenever(codeModernizerManagerSpy).getCustomerSelection(any())
5252
runInEdtAndWait {
53-
val job = codeModernizerManagerSpy.runModernize()
53+
val buildFiles = listOf(emptyPomFile)
54+
val job = codeModernizerManagerSpy.runModernize(buildFiles)
5455
assertNull(job)
55-
verify(codeModernizerManagerSpy, times(1)).runModernize()
56-
verify(codeModernizerManagerSpy, times(1)).getCustomerSelection()
56+
verify(codeModernizerManagerSpy, times(1)).runModernize(buildFiles)
57+
verify(codeModernizerManagerSpy, times(1)).getCustomerSelection(buildFiles)
5758
// verify execution stopped after user cancelled
5859
verify(codeModernizerManagerSpy, times(0)).createCodeModernizerSession(any(), any())
5960
verify(codeModernizerManagerSpy, times(0)).launchModernizationJob(any())
@@ -68,14 +69,15 @@ class CodeWhispererCodeModernizerTest : CodeWhispererCodeModernizerTestBase() {
6869
`when`(mockFile.name).thenReturn("pomx.xml")
6970
`when`(mockFile.path).thenReturn("/mocked/path/pom.xml")
7071
val selection = CustomerSelection(mockFile, JavaSdkVersion.JDK_1_8, JavaSdkVersion.JDK_17)
71-
doReturn(selection).whenever(codeModernizerManagerSpy).getCustomerSelection()
72+
doReturn(selection).whenever(codeModernizerManagerSpy).getCustomerSelection(any())
7273
doNothing().whenever(codeModernizerManagerSpy).postModernizationJob(any())
7374
doReturn(CodeModernizerStartJobResult.Started(jobId)).whenever(testSessionSpy).createModernizationJob()
7475
runInEdtAndWait {
75-
val job = codeModernizerManagerSpy.runModernize()
76+
val buildFiles = listOf(emptyPomFile)
77+
val job = codeModernizerManagerSpy.runModernize(buildFiles)
7678
assertNotNull(job)
77-
verify(codeModernizerManagerSpy, times(1)).runModernize()
78-
verify(codeModernizerManagerSpy, times(1)).getCustomerSelection()
79+
verify(codeModernizerManagerSpy, times(1)).runModernize(buildFiles)
80+
verify(codeModernizerManagerSpy, times(1)).getCustomerSelection(buildFiles)
7981
verify(codeModernizerManagerSpy, times(1)).createCodeModernizerSession(any(), any())
8082
verify(codeModernizerManagerSpy, times(1)).launchModernizationJob(any())
8183
}
@@ -116,13 +118,13 @@ class CodeWhispererCodeModernizerTest : CodeWhispererCodeModernizerTestBase() {
116118
@Test
117119
fun `able to filter roots correctly`() {
118120
val tests = listOf(
119-
listOf<String>() to listOf(),
120-
listOf("foo/bar") to listOf("foo/bar"),
121-
listOf("foo/bar", "foo/baz", "foo/bar/qux") to listOf("foo/bar", "foo/baz"),
122-
listOf("foos", "foo/bar", "foo/baz", "foo/bar/qux") to listOf("foos", "foo/bar", "foo/baz"),
123-
listOf("foo", "foo/bar", "foo/baz", "foo/bar/qux") to listOf("foo"),
121+
setOf<String>() to listOf(),
122+
setOf("foo/bar") to listOf("foo/bar"),
123+
setOf("foo/bar", "foo/baz", "foo/bar/qux") to listOf("foo/bar", "foo/baz"),
124+
setOf("foos", "foo/bar", "foo/baz", "foo/bar/qux") to listOf("foos", "foo/bar", "foo/baz"),
125+
setOf("foo", "foo/bar", "foo/baz", "foo/bar/qux") to listOf("foo"),
124126
).map { (input, expected) -> input.map { "$it/pom.xml" } to expected.map { "$it/pom.xml" } }
125-
tests.map { (input, expected) -> Pair(input.map { LightVirtualFile(it) }, expected) }
127+
tests.map { (input, expected) -> Pair(input.map { LightVirtualFile(it) }.toSet(), expected) }
126128
.forEach { (input, expected) ->
127129
assertEquals(expected, filterOnlyParentFiles(input).map { it.name })
128130
}
@@ -131,10 +133,10 @@ class CodeWhispererCodeModernizerTest : CodeWhispererCodeModernizerTestBase() {
131133
@Test
132134
fun `able to filter roots correctly when multiple on same level`() {
133135
val tests = listOf(
134-
listOf("foo/tmp0.txt", "foo/bar/tmp.txt", "foo/bar/tmp2.txt", "foo/bar/qux/tmp3.txt") to listOf("foo/tmp0.txt"),
135-
listOf("foo/bar/tmp.txt", "foo/bar/tmp2.txt", "foo/bar/qux/tmp3.txt") to listOf("foo/bar/tmp.txt", "foo/bar/tmp2.txt"),
136+
setOf("foo/tmp0.txt", "foo/bar/tmp.txt", "foo/bar/tmp2.txt", "foo/bar/qux/tmp3.txt") to listOf("foo/tmp0.txt"),
137+
setOf("foo/bar/tmp.txt", "foo/bar/tmp2.txt", "foo/bar/qux/tmp3.txt") to listOf("foo/bar/tmp.txt", "foo/bar/tmp2.txt"),
136138
)
137-
tests.map { (input, expected) -> Pair(input.map { LightVirtualFile(it) }, expected) }
139+
tests.map { (input, expected) -> Pair(input.map { LightVirtualFile(it) }.toSet(), expected) }
138140
.forEach { (input, expected) ->
139141
assertEquals(expected, filterOnlyParentFiles(input).map { it.name })
140142
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ open class CodeWhispererCodeModernizerTestBase(
8989
internal lateinit var testSessionStateSpy: CodeModernizerSessionState
9090
internal val diffResource = "diff.patch".toResourceFile()
9191
internal val examplePatchVirtualFile = LightVirtualFile("diff.patch", diffResource.readText())
92+
internal val emptyPomFile = LightVirtualFile("pom.xml", "")
9293
internal val jobId = JobId("Test job id")
9394
internal val migrationStep = MigrationStep("Test migration step")
9495
internal lateinit var testCodeModernizerArtifact: CodeModernizerArtifact

0 commit comments

Comments
 (0)