Skip to content

Commit 99c19c6

Browse files
author
David Hasani
committed
handle case where 2 build systems present
1 parent f985442 commit 99c19c6

File tree

13 files changed

+144
-72
lines changed

13 files changed

+144
-72
lines changed

packages/amazonq/scripts/build/transformByQ/gradle_copy_deps.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,6 @@ def run(directory_path):
404404
except Exception as e:
405405
print(f"Error making gradlew executable, going to continue anyway: {e}")
406406
else:
407-
# TO-DO: get text approved
408407
print("gradlew executable not found. Please ensure you have a Gradle wrapper at the root of your project. Run 'gradle wrapper' to generate one.")
409408
sys.exit(1)
410409
try:

packages/amazonq/scripts/build/transformByQ/windows_gradle_copy_deps.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,6 @@ def run(directory_path):
404404
except Exception as e:
405405
print(f"Error making gradlew.bat executable, going to continue anyway: {e}")
406406
else:
407-
# TO-DO: get text approved
408407
print("gradlew.bat executable not found. Please ensure you have a Gradle wrapper at the root of your project. Run 'gradle wrapper' to generate one.")
409408
sys.exit(1)
410409
try:

packages/core/src/amazonqGumby/chat/controller/controller.ts

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,16 @@ import {
2424
openHilPomFile,
2525
postTransformationJob,
2626
processTransformFormInput,
27-
resetDebugArtifacts,
2827
startTransformByQ,
2928
stopTransformByQ,
3029
validateCanCompileProject,
3130
} from '../../../codewhisperer/commands/startTransformByQ'
32-
import { JDKVersion, TransformationCandidateProject, transformByQState } from '../../../codewhisperer/models/model'
31+
import {
32+
BuildSystem,
33+
JDKVersion,
34+
TransformationCandidateProject,
35+
transformByQState,
36+
} from '../../../codewhisperer/models/model'
3337
import {
3438
AlternateDependencyVersionsNotFoundError,
3539
JavaHomeNotSetError,
@@ -268,12 +272,17 @@ export class GumbyController {
268272
const typedAction = MessengerUtils.stringToEnumValue(ButtonActions, message.action as any)
269273
switch (typedAction) {
270274
case ButtonActions.CONFIRM_TRANSFORMATION_FORM:
271-
await resetDebugArtifacts()
272275
await this.initiateTransformationOnProject(message)
273276
break
274277
case ButtonActions.CANCEL_TRANSFORMATION_FORM:
275278
this.messenger.sendJobFinishedMessage(message.tabID, CodeWhispererConstants.jobCancelledChatMessage)
276279
break
280+
case ButtonActions.CONFIRM_BUILD_SYSTEM_FORM:
281+
await this.handleBuildSystemForm(message)
282+
break
283+
case ButtonActions.CANCEL_BUILD_SYSTEM_FORM:
284+
this.messenger.sendJobFinishedMessage(message.tabID, CodeWhispererConstants.jobCancelledChatMessage)
285+
break
277286
case ButtonActions.VIEW_TRANSFORMATION_HUB:
278287
await vscode.commands.executeCommand(GumbyCommands.FOCUS_TRANSFORMATION_HUB, CancelActionPositions.Chat)
279288
this.messenger.sendJobSubmittedMessage(message.tabID)
@@ -285,7 +294,6 @@ export class GumbyController {
285294
break
286295
case ButtonActions.CONFIRM_START_TRANSFORMATION_FLOW:
287296
this.resetTransformationChatFlow()
288-
await resetDebugArtifacts()
289297
this.messenger.sendCommandMessage({ ...message, command: GumbyCommands.CLEAR_CHAT })
290298
await this.transformInitiated(message)
291299
break
@@ -306,7 +314,7 @@ export class GumbyController {
306314
}
307315
}
308316

309-
// prompt user to pick project and specify source JDK version
317+
// show user which project they selected in chat
310318
private async initiateTransformationOnProject(message: any) {
311319
const authType = await getAuthType()
312320
telemetry.codeTransform_jobIsStartedFromChatPrompt.emit({
@@ -326,13 +334,34 @@ export class GumbyController {
326334
return
327335
}
328336

329-
const buildSystem = await checkBuildSystem(pathToProject)
330-
getLogger().info(`Selected project uses build system = ${buildSystem}`)
331-
await processTransformFormInput(pathToProject, fromJDKVersion, toJDKVersion, buildSystem)
337+
await processTransformFormInput(pathToProject, fromJDKVersion, toJDKVersion)
338+
339+
// at this point, buildSystems is either [Maven], [Gradle], or [Maven, Gradle]
340+
const buildSystems = await checkBuildSystem(pathToProject)
341+
let selectedProjectBuildSystem = undefined
342+
if (buildSystems.length === 2) {
343+
// TO-DO: revert to 1
344+
selectedProjectBuildSystem = buildSystems[0]
345+
} else {
346+
// multiple build systems present, so ask user to pick one
347+
await this.messenger.sendBuildSystemPrompt(message.tabID)
348+
return
349+
}
350+
getLogger().info(`Selected project uses build system: ${selectedProjectBuildSystem}`)
351+
transformByQState.setBuildSystem(selectedProjectBuildSystem)
352+
await this.validateBuildWithPromptOnError(message)
353+
}
354+
355+
private async handleBuildSystemForm(message: any) {
356+
const buildSystem: BuildSystem = message.formSelectedValues['GumbyTransformBuildSystemForm']
357+
getLogger().info(`Selected project uses Maven and Gradle; user selected build system: ${buildSystem}`)
358+
transformByQState.setBuildSystem(buildSystem)
359+
// this message obj is from the build system form, not the project selection form,
360+
// which is fine since validateBuildWithPromptOnError just needs the tab ID here
332361
await this.validateBuildWithPromptOnError(message)
333362
}
334363

335-
private async prepareProjectForSubmission(message: { pathToJavaHome: string; tabID: string }): Promise<void> {
364+
private async prepareProjectForSubmission(message: any): Promise<void> {
336365
if (message.pathToJavaHome) {
337366
transformByQState.setJavaHome(message.pathToJavaHome)
338367
getLogger().info(
@@ -385,7 +414,7 @@ export class GumbyController {
385414
await startTransformByQ()
386415
}
387416

388-
private async validateBuildWithPromptOnError(message: any | undefined = undefined): Promise<void> {
417+
private async validateBuildWithPromptOnError(message: any): Promise<void> {
389418
try {
390419
await validateCanCompileProject()
391420
} catch (err: any) {

packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,48 @@ export class Messenger {
115115
this.dispatcher.sendAuthenticationUpdate(new AuthenticationUpdateMessage(gumbyEnabled, authenticatingTabIDs))
116116
}
117117

118+
// for when both a pom.xml and a build.gradle / build.gradle.kts detected (rare)
119+
public async sendBuildSystemPrompt(tabID: string) {
120+
const formItems: ChatItemFormItem[] = []
121+
122+
formItems.push({
123+
id: 'GumbyTransformBuildSystemForm',
124+
type: 'select',
125+
title: 'Choose the build system of the project',
126+
mandatory: true,
127+
options: [
128+
{
129+
value: BuildSystem.Maven,
130+
label: BuildSystem.Maven.toString(),
131+
},
132+
{
133+
value: BuildSystem.Gradle,
134+
label: BuildSystem.Gradle.toString(),
135+
},
136+
],
137+
})
138+
139+
// TO-DO: do we need the below?
140+
this.dispatcher.sendAsyncEventProgress(
141+
new AsyncEventProgressMessage(tabID, {
142+
inProgress: false,
143+
message: undefined,
144+
})
145+
)
146+
147+
this.dispatcher.sendChatPrompt(
148+
new ChatPrompt(
149+
{
150+
message: 'Q Code Transformation',
151+
formItems: formItems,
152+
},
153+
'TransformBuildSystemForm',
154+
tabID,
155+
false
156+
)
157+
)
158+
}
159+
118160
public async sendProjectPrompt(projects: TransformationCandidateProject[], tabID: string) {
119161
const projectFormOptions: { value: any; label: string }[] = []
120162
const detectedJavaVersions = new Array<JDKVersion | undefined>()
@@ -165,7 +207,7 @@ export class Messenger {
165207
mandatory: true,
166208
options: [
167209
{
168-
value: JDKVersion.JDK17.toString(),
210+
value: JDKVersion.JDK17,
169211
label: JDKVersion.JDK17.toString(),
170212
},
171213
],
@@ -325,11 +367,10 @@ export class Messenger {
325367
message = CodeWhispererConstants.noPomXmlOrBuildGradleFoundChatMessage
326368
break
327369
case 'could-not-compile-project':
328-
// TO-DO: this is currently unused for Gradle since compilation (copy-deps) is treated as optional
329370
message =
330371
transformByQState.getBuildSystem() === BuildSystem.Maven
331372
? CodeWhispererConstants.cleanInstallErrorChatMessage
332-
: CodeWhispererConstants.aigGradleBuildErrorChatMessage
373+
: CodeWhispererConstants.gradleBuildErrorChatMessage
333374
break
334375
case 'invalid-java-home':
335376
message = CodeWhispererConstants.noJavaHomeFoundChatMessage

packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export enum ButtonActions {
1515
VIEW_TRANSFORMATION_HUB = 'gumbyViewTransformationHub',
1616
CONFIRM_TRANSFORMATION_FORM = 'gumbyTransformFormConfirm',
1717
CANCEL_TRANSFORMATION_FORM = 'gumbyTransformFormCancel',
18+
CONFIRM_BUILD_SYSTEM_FORM = 'gumbyTransformBuildSystemFormConfirm',
19+
CANCEL_BUILD_SYSTEM_FORM = 'gumbyTransformBuildSystemFormCancel',
1820
CONFIRM_DEPENDENCY_FORM = 'gumbyTransformDependencyFormConfirm',
1921
CANCEL_DEPENDENCY_FORM = 'gumbyTransformDependencyFormCancel',
2022
CONFIRM_JAVA_HOME_FORM = 'gumbyJavaHomeFormConfirm',

packages/core/src/codewhisperer/commands/startTransformByQ.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,17 @@ import { sleep } from '../../shared/utilities/timeoutUtils'
7171
import DependencyVersions from '../../amazonqGumby/models/dependencies'
7272
import { dependencyNoAvailableVersions } from '../../amazonqGumby/models/constants'
7373
import { HumanInTheLoopManager } from '../service/transformByQ/humanInTheLoopManager'
74+
import { makeTemporaryToolkitFolder } from '../../shared/filesystemUtilities'
7475

7576
export async function processTransformFormInput(
7677
pathToProject: string,
7778
fromJDKVersion: JDKVersion,
78-
toJDKVersion: JDKVersion,
79-
buildSystem: BuildSystem
79+
toJDKVersion: JDKVersion
8080
) {
8181
transformByQState.setProjectName(path.basename(pathToProject))
8282
transformByQState.setProjectPath(pathToProject)
8383
transformByQState.setSourceJDKVersion(fromJDKVersion)
8484
transformByQState.setTargetJDKVersion(toJDKVersion)
85-
transformByQState.setBuildSystem(buildSystem)
8685
}
8786

8887
// used to set the command we will use with the '-v' flag to get the JDK version on user's JAVA_HOME,
@@ -537,14 +536,26 @@ export async function pollTransformationStatusUntilPlanReady(jobId: string) {
537536
// Since we don't yet have a good way of knowing what the error was,
538537
// we try to fetch any build failure artifacts that may exist so that we can optionally
539538
// show them to the user if they exist.
540-
const tmpDir = path.join(os.tmpdir(), 'q-transformation-build-logs')
541-
await downloadAndExtractResultArchive(jobId, undefined, tmpDir, 'Logs')
542-
const pathToLog = path.join(tmpDir, 'buildCommandOutput.log')
543-
transformByQState.setPreBuildLogFilePath(pathToLog)
539+
let pathToLog = ''
540+
try {
541+
const tempToolkitFolder = await makeTemporaryToolkitFolder()
542+
const tempBuildLogsDir = path.join(tempToolkitFolder, 'q-transformation-build-logs')
543+
await downloadAndExtractResultArchive(jobId, undefined, tempBuildLogsDir, 'Logs')
544+
pathToLog = path.join(tempBuildLogsDir, 'buildCommandOutput.log')
545+
transformByQState.setPreBuildLogFilePath(pathToLog)
546+
} catch (e) {
547+
transformByQState.setPreBuildLogFilePath('')
548+
getLogger().error(
549+
'CodeTransformation: failed to download any possible build error logs: ' + (e as Error).message
550+
)
551+
throw e
552+
}
544553

545554
if (fs.existsSync(pathToLog) && !transformByQState.isCancelled()) {
546555
throw new TransformationPreBuildError()
547556
} else {
557+
// not strictly needed to reset path here and above; doing it just to represent unavailable logs
558+
transformByQState.setPreBuildLogFilePath('')
548559
throw new PollJobError()
549560
}
550561
}
@@ -737,14 +748,6 @@ export async function cleanupTransformationJob() {
737748
CodeTransformTelemetryState.instance.resetCodeTransformMetaDataField()
738749
}
739750

740-
export async function resetDebugArtifacts() {
741-
const buildLogDir = path.join(os.tmpdir(), 'q-transformation-build-logs')
742-
if (fs.existsSync(buildLogDir)) {
743-
fs.rmSync(buildLogDir, { recursive: true, force: true })
744-
transformByQState.setPreBuildLogFilePath('')
745-
}
746-
}
747-
748751
export async function stopTransformByQ(
749752
jobId: string,
750753
cancelSrc: CancelActionPositions = CancelActionPositions.BottomHubPanel

packages/core/src/codewhisperer/models/constants.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -588,17 +588,9 @@ export const cleanInstallErrorChatMessage =
588588
export const cleanInstallErrorNotification =
589589
'Amazon Q could not run the Maven clean install command to build your project. For more information, see the [Amazon Q Code Transformation documentation](https://docs.aws.amazon.com/amazonq/latest/aws-builder-use-ug/troubleshooting-code-transformation.html#maven-commands-failing).'
590590

591-
// TO-DO: delete, this just for AIG
592-
export const aigGradleBuildErrorNotification = "Amazon Q couldn't build your Gradle project."
591+
export const gradleBuildErrorNotification = "Amazon Q couldn't build your Gradle project."
593592

594-
export const gradleBuildErrorNotification =
595-
"Amazon Q couldn't build your Gradle project. If your project uses 1P dependencies, they weren't copied over. The transformation is continuing as it might succeed if your project does not use any 1P dependencies. If your project uses 1P dependencies, you can stop the transformation and address the issues shown in the build-logs.txt file, and then try again."
596-
597-
export const aigGradleBuildErrorChatMessage = "Sorry, I couldn't build your Gradle project."
598-
599-
// TO-DO: this is currently unused for Gradle since compilation (copy-deps) is treated as optional
600-
export const gradleBuildErrorChatMessage =
601-
"Sorry, I couldn't build your Gradle project, meaning any 1P dependencies in your project weren't copied over. I'm continuing the transformation in case your project doesn't use 1P dependencies. If your project uses 1P dependencies, you can stop the transformation and address the issues shown in the build-logs.txt file, and then try again."
593+
export const gradleBuildErrorChatMessage = "Sorry, I couldn't build your Gradle project."
602594

603595
export const enterJavaHomeChatMessage = 'Enter the path to JDK '
604596

packages/core/src/codewhisperer/models/model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ export class ZipManifest {
321321
version: string = '1.0'
322322
hilCapabilities: string[] | undefined = ['HIL_1pDependency_VersionUpgrade']
323323
// transformCapabilities: string[] = ['EXPLAINABILITY_V1'] // TO-DO: uncomment this once Gradle plan becomes dynamic
324-
buildTool: BuildSystem | undefined = undefined // TO-DO: a project may contain both a pom.xml and a build.gradle; in which case need to ask customer
324+
buildTool: BuildSystem | undefined = undefined
325325
}
326326

327327
export interface IHilZipManifestParams {

packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -913,17 +913,17 @@ export async function downloadAndExtractResultArchive(
913913
await fsCommon.mkdir(pathToArchiveDir)
914914
}
915915
const pathToArchive = path.join(pathToArchiveDir, 'ExportResultsArchive.zip')
916-
await downloadResultArchive(jobId, downloadArtifactId, pathToArchive, downloadArtifactType)
917916

918917
let downloadErrorMessage = undefined
919918
try {
920919
// Download and deserialize the zip
920+
await downloadResultArchive(jobId, downloadArtifactId, pathToArchive, downloadArtifactType)
921921
const zip = new AdmZip(pathToArchive)
922922
zip.extractAllTo(pathToArchiveDir)
923923
} catch (e) {
924924
downloadErrorMessage = (e as Error).message
925925
getLogger().error(`CodeTransformation: ExportResultArchive error = ${downloadErrorMessage}`)
926-
throw new Error('Error downloading transformation result artifacts')
926+
throw new Error('Error downloading transformation result artifacts: ' + downloadErrorMessage)
927927
}
928928
}
929929

packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,26 @@ export async function writeLogs() {
2828
return logFilePath
2929
}
3030

31-
// TO-DO: work with PM/UX to handle case where there is both a pom.xml and a build.gradle / build.gradle.kts
32-
// AIG will not have this issue; but keep this in mind
31+
// TO-DO: add unit tests
3332
export async function checkBuildSystem(projectPath: string) {
33+
const buildSystems = []
3434
const mavenBuildFilePath = path.join(projectPath, 'pom.xml')
3535
if (existsSync(mavenBuildFilePath)) {
36-
return BuildSystem.Maven
36+
buildSystems.push(BuildSystem.Maven)
3737
}
3838
const gradleBuildFilePath = path.join(projectPath, 'build.gradle')
3939
if (existsSync(gradleBuildFilePath)) {
40-
return BuildSystem.Gradle
40+
buildSystems.push(BuildSystem.Gradle)
4141
}
4242
const gradleKtsBuildFilePath = path.join(projectPath, 'build.gradle.kts')
4343
if (existsSync(gradleKtsBuildFilePath)) {
44-
return BuildSystem.Gradle
44+
buildSystems.push(BuildSystem.Gradle)
4545
}
46-
return BuildSystem.Unknown
46+
if (buildSystems.length === 0) {
47+
buildSystems.push(BuildSystem.Unknown)
48+
}
49+
// in case there is a build.gradle and a build.gradle.kts (should never happen), remove duplicates
50+
return Array.from(new Set(buildSystems))
4751
}
4852

4953
// for Maven (Gradle) projects, ignore the 'target' ('build', '.gradle', 'bin', 'START') directory as it includes large JAR files and .class files

0 commit comments

Comments
 (0)