diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index 06eea822eb..16269cdee0 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -41,6 +41,7 @@ import { telemetry } from '../../../shared/telemetry/telemetry' import { MetadataResult } from '../../../shared/telemetry/telemetryClient' import { CodeTransformTelemetryState } from '../../telemetry/codeTransformTelemetryState' import { getAuthType } from '../../../codewhisperer/service/transformByQ/transformApiHandler' +import { getJavaVersionStringUsedByMaven } from '../../../codewhisperer/service/transformByQ/transformMavenHandler' import DependencyVersions from '../../models/dependencies' // These events can be interactions within the chat, @@ -65,7 +66,7 @@ export class GumbyController { private readonly messenger: Messenger private readonly sessionStorage: ChatSessionManager private authController: AuthController - + private readonly MaximumJavaHomeRetries = 3 public constructor( private readonly chatControllerMessageListeners: ChatControllerEventEmitters, messenger: Messenger, @@ -294,13 +295,6 @@ export class GumbyController { } private async prepareProjectForSubmission(message: { pathToJavaHome: string; tabID: string }): Promise { - if (message.pathToJavaHome) { - transformByQState.setJavaHome(message.pathToJavaHome) - getLogger().info( - `CodeTransformation: using JAVA_HOME = ${transformByQState.getJavaHome()} since source JDK does not match Maven JDK` - ) - } - try { this.sessionStorage.getSession().conversationState = ConversationState.COMPILING this.messenger.sendCompilationInProgress(message.tabID) @@ -380,6 +374,39 @@ export class GumbyController { const pathToJavaHome = extractPath(data.message) if (pathToJavaHome) { + transformByQState.setJavaHome(pathToJavaHome) + getLogger().info( + `CodeTransformation: using JAVA_HOME = ${transformByQState.getJavaHome()} since source JDK does not match Maven JDK` + ) + + try { + await validateCanCompileProject() + } catch (err: any) { + if (err instanceof JavaHomeNotSetError) { + const providedJdkVersion = + (await getJavaVersionStringUsedByMaven()) ?? JDKVersion.UNSUPPORTED + const expectedJdkVersion = transformByQState.getSourceJDKVersion() ?? JDKVersion.UNSUPPORTED + getLogger().warn( + `CodeTransformation: non matching JAVA_HOME provided: ${providedJdkVersion} expected: ${expectedJdkVersion} JDK release must match` + ) + if (transformByQState.incrementAndGetJavaHomeAttempts() > this.MaximumJavaHomeRetries) { + transformByQState.resetJavaHomeAttempts() + transformByQState.resetJavaHome() + this.messenger.sendUnrecoverableErrorResponse('invalid-java-home', data.tabID) + return + } + this.sessionStorage.getSession().conversationState = ConversationState.PROMPT_JAVA_HOME + this.messenger.sendInvalidJavaHomeProvidedMessage(data.tabID, expectedJdkVersion) + this.messenger.sendUpdatePlaceholder( + data.tabID, + MessengerUtils.createInvalidJavaHomePlaceholder(expectedJdkVersion) + ) + this.messenger.sendChatInputEnabled(data.tabID, true) + return + } + throw err + } + await this.prepareProjectForSubmission({ pathToJavaHome, tabID: data.tabID, diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts index 6082fc96db..1d49326ed7 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts @@ -34,6 +34,7 @@ export type StaticTextResponseType = | 'java-home-not-set' | 'start-transformation-confirmed' | 'job-transmitted' + | 'invalid-java-home-provided' | 'end-HIL-early' export type UnrecoverableErrorType = @@ -198,9 +199,22 @@ export class Messenger { this.dispatcher.sendAsyncEventProgress(new AsyncEventProgressMessage(tabID, { inProgress, message, messageId })) } + public sendInvalidJavaHomeProvidedMessage(tabID: string, expectedJdkVersion: string) { + this.dispatcher.sendChatMessage( + new ChatMessage( + { + message: MessengerUtils.createInvalidJavaHomePromptChatMessage(expectedJdkVersion), + messageType: 'ai-prompt', + }, + tabID + ) + ) + } + public sendCompilationInProgress(tabID: string) { const message = CodeWhispererConstants.buildStartedChatMessage + // Mynah UI requires us sending `message: undefined` before passing the `message` for animation to work this.dispatcher.sendAsyncEventProgress( new AsyncEventProgressMessage(tabID, { inProgress: true, message: undefined }) ) @@ -401,6 +415,7 @@ export class Messenger { } public sendTransformationIntroduction(tabID: string) { + // Mynah UI requires us sending `message: undefined` before passing the `message` for animation to work this.dispatcher.sendAsyncEventProgress( new AsyncEventProgressMessage(tabID, { inProgress: true, message: undefined }) ) diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts index ea921fc477..93357cf02c 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts @@ -30,26 +30,36 @@ export enum GumbyCommands { } export default class MessengerUtils { - static createJavaHomePrompt = (): string => { - let javaHomePrompt = `${ - CodeWhispererConstants.enterJavaHomeChatMessage - } ${transformByQState.getSourceJDKVersion()}. \n` + static createInstructionsForFindingJavaHome = () => { + const jdkVersion = transformByQState.getSourceJDKVersion() if (os.platform() === 'win32') { - javaHomePrompt += CodeWhispererConstants.windowsJavaHomeHelpChatMessage.replace( - 'JAVA_VERSION_HERE', - transformByQState.getSourceJDKVersion()! - ) + return CodeWhispererConstants.windowsJavaHomeHelpChatMessage.replace('JAVA_VERSION_HERE', jdkVersion!) } else { - const jdkVersion = transformByQState.getSourceJDKVersion() if (jdkVersion === JDKVersion.JDK8) { - javaHomePrompt += ` ${CodeWhispererConstants.nonWindowsJava8HomeHelpChatMessage}` + return ` ${CodeWhispererConstants.nonWindowsJava8HomeHelpChatMessage}` } else if (jdkVersion === JDKVersion.JDK11) { - javaHomePrompt += ` ${CodeWhispererConstants.nonWindowsJava11HomeHelpChatMessage}` + return ` ${CodeWhispererConstants.nonWindowsJava11HomeHelpChatMessage}` } } - return javaHomePrompt + return '' } + static createJavaHomePrompt = (): string => { + return ( + `${CodeWhispererConstants.enterJavaHomeChatMessage} ${transformByQState.getSourceJDKVersion()}.\n` + + this.createInstructionsForFindingJavaHome() + ) + } + + static createInvalidJavaHomePromptChatMessage = (expectedJdkVersion: string): string => + CodeWhispererConstants.invalidJavaHomeProvidedChatMessage( + expectedJdkVersion, + this.createInstructionsForFindingJavaHome() + ) + + static createInvalidJavaHomePlaceholder = (expectedJdkVersion: string): string => + CodeWhispererConstants.invalidJavaHomeProvidedPlaceholder(expectedJdkVersion) + static stringToEnumValue = ( enumObject: T, value: `${T[K]}` diff --git a/packages/core/src/codewhisperer/commands/startTransformByQ.ts b/packages/core/src/codewhisperer/commands/startTransformByQ.ts index 8fe8d96e11..da5e15a5e4 100644 --- a/packages/core/src/codewhisperer/commands/startTransformByQ.ts +++ b/packages/core/src/codewhisperer/commands/startTransformByQ.ts @@ -39,6 +39,7 @@ import { getOpenProjects, validateOpenProjects } from '../service/transformByQ/t import { getVersionData, prepareProjectDependencies, + getJavaVersionUsedByMaven, runMavenDependencyUpdateCommands, } from '../service/transformByQ/transformMavenHandler' import { @@ -104,16 +105,7 @@ async function setMaven() { } async function validateJavaHome(): Promise { - const versionData = await getVersionData() - let javaVersionUsedByMaven = versionData[1] - if (javaVersionUsedByMaven !== undefined) { - javaVersionUsedByMaven = javaVersionUsedByMaven.slice(0, 3) - if (javaVersionUsedByMaven === '1.8') { - javaVersionUsedByMaven = JDKVersion.JDK8 - } else if (javaVersionUsedByMaven === '11.') { - javaVersionUsedByMaven = JDKVersion.JDK11 - } - } + const javaVersionUsedByMaven = await getJavaVersionUsedByMaven() if (javaVersionUsedByMaven !== transformByQState.getSourceJDKVersion()) { telemetry.codeTransform_isDoubleClickedToTriggerInvalidProject.emit({ codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), @@ -637,8 +629,8 @@ export async function postTransformationJob() { const resultStatusMessage = transformByQState.getStatus() const versionInfo = await getVersionData() - const mavenVersionInfoMessage = `${versionInfo[0]} (${transformByQState.getMavenName()})` - const javaVersionInfoMessage = `${versionInfo[1]} (${transformByQState.getMavenName()})` + const mavenVersionInfoMessage = `${versionInfo.mavenVersion} (${transformByQState.getMavenName()})` + const javaVersionInfoMessage = `${versionInfo.javaVersion} (${transformByQState.getMavenName()})` // Note: IntelliJ implementation of ResultStatusMessage includes additional metadata such as jobId. telemetry.codeTransform_totalRunTime.emit({ diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index 7e8cc5eaa4..904d15c935 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -562,6 +562,12 @@ export const cleanInstallErrorNotification = export const enterJavaHomeChatMessage = 'Enter the path to JDK ' +export const invalidJavaHomeProvidedChatMessage = (expectedJdkVersion: string, instructions: string) => + `It looks like the path you provided is not where JDK ${expectedJdkVersion} is installed. Since you're transforming Java ${expectedJdkVersion} code, I need the JDK ${expectedJdkVersion} path. To find the JDK ${expectedJdkVersion} path, run the following command in a new IDE terminal:\n ${instructions}` + +export const invalidJavaHomeProvidedPlaceholder = (expectedJdkVersion: string) => + `Enter the JDK ${expectedJdkVersion} path` + export const projectPromptChatMessage = 'I can upgrade your JAVA_VERSION_HERE. To start the transformation, I need some information from you. Choose the project you want to upgrade and the target code version to upgrade to. Then, choose Transform.' diff --git a/packages/core/src/codewhisperer/models/model.ts b/packages/core/src/codewhisperer/models/model.ts index 028a85e890..b19fffc008 100644 --- a/packages/core/src/codewhisperer/models/model.ts +++ b/packages/core/src/codewhisperer/models/model.ts @@ -409,6 +409,8 @@ export class TransformByQState { private intervalId: NodeJS.Timeout | undefined = undefined + private numAttemptsToGetJavaHome = 0 + public isNotStarted() { return this.transformByQState === TransformByQStatus.NotStarted } @@ -621,6 +623,18 @@ export class TransformByQState { this.javaHome = javaHome } + public resetJavaHome() { + this.javaHome = undefined + } + + public incrementAndGetJavaHomeAttempts() { + return ++this.numAttemptsToGetJavaHome + } + + public resetJavaHomeAttempts() { + this.numAttemptsToGetJavaHome = 0 + } + public setChatControllers(controllers: ChatControllerEventEmitters) { this.chatControllers = controllers } diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformMavenHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformMavenHandler.ts index aa42cffe25..64f315fa10 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformMavenHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformMavenHandler.ts @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ import * as vscode from 'vscode' -import { FolderInfo, transformByQState } from '../../models/model' +import { FolderInfo, transformByQState, JDKVersion } from '../../models/model' import { getLogger } from '../../../shared/logger' import * as CodeWhispererConstants from '../../models/constants' -import { spawnSync } from 'child_process' // Consider using ChildProcess once we finalize all spawnSync calls +import { spawnSync, SpawnSyncOptionsWithStringEncoding } from 'child_process' // Consider using ChildProcess once we finalize all spawnSync calls import { CodeTransformMavenBuildCommand, telemetry } from '../../../shared/telemetry/telemetry' import { CodeTransformTelemetryState } from '../../../amazonqGumby/telemetry/codeTransformTelemetryState' import { MetadataResult } from '../../../shared/telemetry/telemetryClient' @@ -14,6 +14,11 @@ import { ToolkitError } from '../../../shared/errors' import { writeLogs } from './transformFileHandler' import { throwIfCancelled } from './transformApiHandler' +interface MavenVersionData { + mavenVersion: string | undefined + javaVersion: string | undefined +} + // run 'install' with either 'mvnw.cmd', './mvnw', or 'mvn' (if wrapper exists, we use that, otherwise we use regular 'mvn') function installProjectDependencies(dependenciesFolder: FolderInfo, modulePath: string) { // baseCommand will be one of: '.\mvnw.cmd', './mvnw', 'mvn' @@ -174,11 +179,37 @@ export async function prepareProjectDependencies(dependenciesFolder: FolderInfo, void vscode.window.showInformationMessage(CodeWhispererConstants.buildSucceededNotification) } -export async function getVersionData() { +export async function getJavaVersionStringUsedByMaven() { + const versionData = await getVersionData() + return versionData.javaVersion?.slice(0, 3) +} + +export async function getJavaVersionUsedByMaven() { + const javaVersion = await getJavaVersionStringUsedByMaven() + switch (javaVersion) { + case '1.8': + return JDKVersion.JDK8 + case '11.': + return JDKVersion.JDK11 + default: + return JDKVersion.UNSUPPORTED + } +} + +export async function getVersionData(): Promise { const baseCommand = transformByQState.getMavenName() // will be one of: 'mvnw.cmd', './mvnw', 'mvn' const modulePath = transformByQState.getProjectPath() + const javaHome = transformByQState.getJavaHome() // If customer provided JAVA_HOME use that + const args = ['-v'] - const spawnResult = spawnSync(baseCommand, args, { cwd: modulePath, shell: true, encoding: 'utf-8' }) + let env = process.env + if (javaHome) { + getLogger().info(`CodeTransformation: using customer provided JAVA_HOME = ${javaHome}`) + env = { ...env, JAVA_HOME: javaHome } + } + + const options: SpawnSyncOptionsWithStringEncoding = { cwd: modulePath, shell: true, encoding: 'utf-8', env: env } + const spawnResult = spawnSync(baseCommand, args, options) let localMavenVersion: string | undefined = '' let localJavaVersion: string | undefined = '' @@ -202,7 +233,7 @@ export async function getVersionData() { getLogger().info( `CodeTransformation: Ran ${baseCommand} to get Maven version = ${localMavenVersion} and Java version = ${localJavaVersion} with project JDK = ${transformByQState.getSourceJDKVersion()}` ) - return [localMavenVersion, localJavaVersion] + return { mavenVersion: localMavenVersion, javaVersion: localJavaVersion } } // run maven 'versions:dependency-updates-aggregate-report' with either 'mvnw.cmd', './mvnw', or 'mvn' (if wrapper exists, we use that, otherwise we use regular 'mvn')