Skip to content

Commit 2f4e591

Browse files
author
David Hasani
committed
more refactoring
1 parent 51c3667 commit 2f4e591

File tree

7 files changed

+97
-92
lines changed

7 files changed

+97
-92
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,8 @@ export async function postTransformationJob() {
744744
}
745745

746746
if (transformByQState.getPayloadFilePath() !== '') {
747-
fs.rmSync(transformByQState.getPayloadFilePath(), { recursive: true, force: true }) // delete ZIP if it exists
747+
// delete original upload ZIP at very end of transformation
748+
fs.rmSync(transformByQState.getPayloadFilePath(), { recursive: true, force: true })
748749
}
749750

750751
// attempt download for user

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

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ import { MetadataResult } from '../../../shared/telemetry/telemetryClient'
4141
import request from '../../../shared/request'
4242
import { JobStoppedError, ZipExceedsSizeLimitError } from '../../../amazonqGumby/errors'
4343
import {
44-
copyDirectory,
4544
createLocalBuildUploadZip,
45+
extractOriginalProjectSources,
4646
loadManifestFile,
4747
writeAndShowBuildLogs,
4848
} from './transformFileHandler'
@@ -253,7 +253,8 @@ export async function uploadPayload(payloadFileName: string, uploadContext?: Upl
253253
*/
254254
const mavenExcludedExtensions = ['.repositories', '.sha1']
255255

256-
const sourceExcludedExtensions = ['.DS_Store']
256+
// exclude .DS_Store (not relevant) and Maven executables (can cause permissions issues when building if user has not ran 'chmod')
257+
const sourceExcludedExtensions = ['.DS_Store', 'mvnw', 'mvnw.cmd']
257258

258259
/**
259260
* Determines if the specified file path corresponds to a Maven metadata file
@@ -726,8 +727,14 @@ export async function pollTransformationJob(jobId: string, validStates: string[]
726727

727728
async function attemptLocalBuild() {
728729
const jobId = transformByQState.getJobId()
729-
const artifactId = await getClientInstructionArtifactId(jobId)
730-
getLogger().info(`CodeTransformation: found artifactId = ${artifactId}`)
730+
let artifactId
731+
try {
732+
artifactId = await getClientInstructionArtifactId(jobId)
733+
getLogger().info(`CodeTransformation: found artifactId = ${artifactId}`)
734+
} catch (e: any) {
735+
// don't throw error so that we can try to get progress updates again in next polling cycle
736+
getLogger().error(`CodeTransformation: failed to get client instruction artifact ID = %O`, e)
737+
}
731738
if (artifactId) {
732739
const clientInstructionsPath = await downloadClientInstructions(jobId, artifactId)
733740
getLogger().info(
@@ -750,7 +757,7 @@ async function getClientInstructionArtifactId(jobId: string) {
750757

751758
async function downloadClientInstructions(jobId: string, artifactId: string) {
752759
const exportDestination = `downloadClientInstructions_${jobId}_${artifactId}`
753-
const exportZipPath = path.join(os.tmpdir(), `${exportDestination}.zip`)
760+
const exportZipPath = path.join(os.tmpdir(), exportDestination)
754761

755762
const exportContext: ExportContext = {
756763
transformationExportContext: {
@@ -766,34 +773,30 @@ async function downloadClientInstructions(jobId: string, artifactId: string) {
766773
}
767774

768775
async function processClientInstructions(jobId: string, clientInstructionsPath: any, artifactId: string) {
769-
const sourcePath = transformByQState.getProjectPath()
770-
const destinationPath = path.join(os.tmpdir(), jobId, artifactId, 'originalCopy')
771-
await copyDirectory(sourcePath, destinationPath)
776+
const destinationPath = path.join(os.tmpdir(), `originalCopy_${jobId}_${artifactId}`)
777+
await extractOriginalProjectSources(destinationPath)
772778
getLogger().info(`CodeTransformation: copied project to ${destinationPath}`)
773779
const diffModel = new DiffModel()
774-
diffModel.parseDiff(clientInstructionsPath, destinationPath, undefined, 1, true)
780+
diffModel.parseDiff(clientInstructionsPath, path.join(destinationPath, 'sources'), undefined, 1, true)
775781
// show user the diff.patch
776782
const doc = await vscode.workspace.openTextDocument(clientInstructionsPath)
777783
await vscode.window.showTextDocument(doc, { viewColumn: vscode.ViewColumn.One })
778784
await runClientSideBuild(transformByQState.getProjectCopyFilePath(), artifactId)
779785
}
780786

781-
export async function runClientSideBuild(projectPath: string, clientInstructionArtifactId: string) {
782-
// baseCommand will be one of: '.\mvnw.cmd', './mvnw', 'mvn'
787+
export async function runClientSideBuild(projectCopyPath: string, clientInstructionArtifactId: string) {
783788
const baseCommand = transformByQState.getMavenName()
784-
const args = ['clean']
789+
const args = []
785790
if (transformByQState.getCustomBuildCommand() === CodeWhispererConstants.skipUnitTestsBuildCommand) {
786791
args.push('test-compile')
787792
} else {
788793
args.push('test')
789794
}
790-
// TO-DO / QUESTION: why not use the build command from the downloaded manifest?
791-
transformByQState.appendToBuildLog(`Running ${baseCommand} ${args}`)
792795
const environment = { ...process.env, JAVA_HOME: transformByQState.getTargetJavaHome() }
793796

794797
const argString = args.join(' ')
795798
const spawnResult = spawnSync(baseCommand, args, {
796-
cwd: projectPath,
799+
cwd: projectCopyPath,
797800
shell: true,
798801
encoding: 'utf-8',
799802
env: environment,
@@ -804,11 +807,11 @@ export async function runClientSideBuild(projectPath: string, clientInstructionA
804807
transformByQState.appendToBuildLog(buildLogs)
805808
await writeAndShowBuildLogs()
806809

807-
const baseDir = path.join(
810+
const uploadZipBaseDir = path.join(
808811
os.tmpdir(),
809812
`clientInstructionsResult_${transformByQState.getJobId()}_${clientInstructionArtifactId}`
810813
)
811-
const zipPath = await createLocalBuildUploadZip(baseDir, spawnResult.status, spawnResult.stdout)
814+
const uploadZipPath = await createLocalBuildUploadZip(uploadZipBaseDir, spawnResult.status, spawnResult.stdout)
812815

813816
// upload build results
814817
const uploadContext: UploadContext = {
@@ -817,14 +820,16 @@ export async function runClientSideBuild(projectPath: string, clientInstructionA
817820
uploadArtifactType: 'ClientBuildResult',
818821
},
819822
}
820-
getLogger().info(`CodeTransformation: uploading client build results at ${zipPath} and resuming job now`)
821-
await uploadPayload(zipPath, uploadContext)
823+
getLogger().info(`CodeTransformation: uploading client build results at ${uploadZipPath} and resuming job now`)
824+
await uploadPayload(uploadZipPath, uploadContext)
822825
await resumeTransformationJob(transformByQState.getJobId(), 'COMPLETED')
823826
try {
824-
await fs.delete(transformByQState.getProjectCopyFilePath(), { recursive: true })
827+
await fs.delete(projectCopyPath, { recursive: true })
828+
await fs.delete(uploadZipBaseDir, { recursive: true })
829+
// TODO: do we need to delete the downloaded client instructions and uploadZipPath? they can help in debugging
825830
} catch {
826831
getLogger().error(
827-
`CodeTransformation: failed to delete project copy at ${transformByQState.getProjectCopyFilePath()} after client-side build`
832+
`CodeTransformation: failed to delete project copy and uploadZipBaseDir after client-side build`
828833
)
829834
}
830835
}

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

Lines changed: 19 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import globals from '../../../shared/extensionGlobals'
1616
import { ChatSessionManager } from '../../../amazonqGumby/chat/storages/chatSession'
1717
import { AbsolutePathDetectedError } from '../../../amazonqGumby/errors'
1818
import { getLogger } from '../../../shared/logger/logger'
19-
import { isWin } from '../../../shared/vscode/env'
2019
import AdmZip from 'adm-zip'
2120

2221
export function getDependenciesFolderInfo(): FolderInfo {
@@ -28,7 +27,6 @@ export function getDependenciesFolderInfo(): FolderInfo {
2827
}
2928
}
3029

31-
// TO-DO: what happens when intermediate build logs are massive from downloading dependencies? exlude those lines?
3230
export async function writeAndShowBuildLogs() {
3331
const logFilePath = path.join(os.tmpdir(), 'build-logs.txt')
3432
writeFileSync(logFilePath, transformByQState.getBuildLog())
@@ -48,42 +46,18 @@ export async function loadManifestFile(directory: string) {
4846
return manifest
4947
}
5048

51-
export async function copyDirectory(sourcePath: string, destinationPath: string) {
52-
await fs.mkdir(destinationPath)
53-
const files = await fs.readdir(sourcePath)
54-
55-
for (const file of files) {
56-
const sourceFilePath = path.join(sourcePath, file[0])
57-
const destinationFilePath = path.join(destinationPath, file[0])
58-
if (file[1] === vscode.FileType.Directory) {
59-
// if the item is a directory, recursively copy it
60-
const destinationFilePath = path.join(destinationPath, path.relative(sourcePath, sourceFilePath))
61-
await copyDirectory(sourceFilePath, destinationFilePath)
62-
} else {
63-
// if the item is a file, copy its contents
64-
try {
65-
await fs.copy(sourceFilePath, destinationFilePath)
66-
} catch (err: any) {
67-
getLogger().error(
68-
`CodeTransformation: error copying file ${sourceFilePath} to ${destinationFilePath}: ${err}`
69-
)
70-
}
71-
}
72-
}
73-
}
74-
7549
export async function createLocalBuildUploadZip(baseDir: string, exitCode: number | null, stdout: string) {
7650
const manifestFilePath = path.join(baseDir, 'manifest.json')
7751
const buildResultsManifest = {
7852
capability: 'CLIENT_SIDE_BUILD',
7953
exitCode: exitCode,
80-
commandLogFileName: 'clientBuildLogs.log',
54+
commandLogFileName: 'build-output.log',
8155
}
8256
const formattedManifest = JSON.stringify(buildResultsManifest)
83-
await fs.writeFile(manifestFilePath, formattedManifest, 'utf8')
57+
await fs.writeFile(manifestFilePath, formattedManifest)
8458

85-
const buildLogsFilePath = path.join(baseDir, 'clientBuildLogs.log')
86-
await fs.writeFile(buildLogsFilePath, stdout, 'utf8')
59+
const buildLogsFilePath = path.join(baseDir, 'build-output.log')
60+
await fs.writeFile(buildLogsFilePath, stdout)
8761

8862
const zip = new AdmZip()
8963
zip.addLocalFile(buildLogsFilePath)
@@ -95,6 +69,17 @@ export async function createLocalBuildUploadZip(baseDir: string, exitCode: numbe
9569
return zipPath
9670
}
9771

72+
// extract the 'sources' directory of the upload ZIP so that we can apply the diff.patch to a copy of the source code
73+
export async function extractOriginalProjectSources(destinationPath: string) {
74+
const zip = new AdmZip(transformByQState.getPayloadFilePath())
75+
const zipEntries = zip.getEntries()
76+
for (const zipEntry of zipEntries) {
77+
if (zipEntry.entryName.startsWith('sources')) {
78+
zip.extractEntryTo(zipEntry, destinationPath, true, true)
79+
}
80+
}
81+
}
82+
9883
export async function checkBuildSystem(projectPath: string) {
9984
const mavenBuildFilePath = path.join(projectPath, 'pom.xml')
10085
if (existsSync(mavenBuildFilePath)) {
@@ -193,20 +178,10 @@ export async function validateSQLMetadataFile(fileContents: string, message: any
193178
return true
194179
}
195180

196-
export async function setMaven() {
197-
let mavenWrapperExecutableName = isWin() ? 'mvnw.cmd' : 'mvnw'
198-
const mavenWrapperExecutablePath = path.join(transformByQState.getProjectPath(), mavenWrapperExecutableName)
199-
if (existsSync(mavenWrapperExecutablePath)) {
200-
if (mavenWrapperExecutableName === 'mvnw') {
201-
mavenWrapperExecutableName = './mvnw' // add the './' for non-Windows
202-
} else if (mavenWrapperExecutableName === 'mvnw.cmd') {
203-
mavenWrapperExecutableName = '.\\mvnw.cmd' // add the '.\' for Windows
204-
}
205-
transformByQState.setMavenName(mavenWrapperExecutableName)
206-
} else {
207-
transformByQState.setMavenName('mvn')
208-
}
209-
getLogger().info(`CodeTransformation: using Maven ${transformByQState.getMavenName()}`)
181+
export function setMaven() {
182+
// for now, just use regular Maven since the Maven executables can
183+
// cause permissions issues when building if user has not ran 'chmod'
184+
transformByQState.setMavenName('mvn')
210185
}
211186

212187
export async function openBuildLogFile() {

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

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,13 @@ import { setMaven, writeAndShowBuildLogs } from './transformFileHandler'
1515
import { throwIfCancelled } from './transformApiHandler'
1616
import { sleep } from '../../../shared/utilities/timeoutUtils'
1717

18-
// run 'install' with either 'mvnw.cmd', './mvnw', or 'mvn' (if wrapper exists, we use that, otherwise we use regular 'mvn')
1918
function installProjectDependencies(dependenciesFolder: FolderInfo, modulePath: string) {
2019
telemetry.codeTransform_localBuildProject.run(() => {
2120
telemetry.record({ codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId() })
2221

23-
// baseCommand will be one of: '.\mvnw.cmd', './mvnw', 'mvn'
22+
// will always be 'mvn'
2423
const baseCommand = transformByQState.getMavenName()
2524

26-
// Note: IntelliJ runs 'clean' separately from 'install'. Evaluate benefits (if any) of this.
2725
const args = [`-Dmaven.repo.local=${dependenciesFolder.path}`, 'clean', 'install', '-q']
2826

2927
transformByQState.appendToBuildLog(`Running ${baseCommand} ${args.join(' ')}`)
@@ -47,14 +45,7 @@ function installProjectDependencies(dependenciesFolder: FolderInfo, modulePath:
4745
maxBuffer: CodeWhispererConstants.maxBufferSize,
4846
})
4947

50-
let mavenBuildCommand = transformByQState.getMavenName()
51-
// slashes not allowed in telemetry
52-
if (mavenBuildCommand === './mvnw') {
53-
mavenBuildCommand = 'mvnw'
54-
} else if (mavenBuildCommand === '.\\mvnw.cmd') {
55-
mavenBuildCommand = 'mvnw.cmd'
56-
}
57-
48+
const mavenBuildCommand = transformByQState.getMavenName()
5849
telemetry.record({ codeTransformBuildCommand: mavenBuildCommand as CodeTransformBuildCommand })
5950

6051
if (spawnResult.status !== 0) {
@@ -73,7 +64,6 @@ function installProjectDependencies(dependenciesFolder: FolderInfo, modulePath:
7364
}
7465

7566
function copyProjectDependencies(dependenciesFolder: FolderInfo, modulePath: string) {
76-
// baseCommand will be one of: '.\mvnw.cmd', './mvnw', 'mvn'
7767
const baseCommand = transformByQState.getMavenName()
7868

7969
const args = [
@@ -142,7 +132,7 @@ export async function prepareProjectDependencies(dependenciesFolder: FolderInfo,
142132
}
143133

144134
export async function getVersionData() {
145-
const baseCommand = transformByQState.getMavenName() // will be one of: 'mvnw.cmd', './mvnw', 'mvn'
135+
const baseCommand = transformByQState.getMavenName()
146136
const projectPath = transformByQState.getProjectPath()
147137
const args = ['-v']
148138
const spawnResult = spawnSync(baseCommand, args, { cwd: projectPath, shell: true, encoding: 'utf-8' })
@@ -172,12 +162,9 @@ export async function getVersionData() {
172162
return [localMavenVersion, localJavaVersion]
173163
}
174164

175-
// 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')
176165
export function runMavenDependencyUpdateCommands(dependenciesFolder: FolderInfo) {
177-
// baseCommand will be one of: '.\mvnw.cmd', './mvnw', 'mvn'
178-
const baseCommand = transformByQState.getMavenName() // will be one of: 'mvnw.cmd', './mvnw', 'mvn'
166+
const baseCommand = transformByQState.getMavenName()
179167

180-
// Note: IntelliJ runs 'clean' separately from 'install'. Evaluate benefits (if any) of this.
181168
const args = [
182169
'versions:dependency-updates-aggregate-report',
183170
`-DoutputDirectory=${dependenciesFolder.path}`,

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,16 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider
327327
jobPlanProgress['generatePlan'] === StepProgress.Succeeded &&
328328
transformByQState.isRunning()
329329
) {
330-
planSteps = await getTransformationSteps(transformByQState.getJobId())
331-
transformByQState.setPlanSteps(planSteps)
330+
try {
331+
planSteps = await getTransformationSteps(transformByQState.getJobId())
332+
transformByQState.setPlanSteps(planSteps)
333+
} catch (e: any) {
334+
// no-op; re-use current plan steps and try again in next polling cycle
335+
getLogger().error(
336+
`CodeTransformation: failed to get plan steps to show updates in transformation hub, continuing transformation; error = %O`,
337+
e
338+
)
339+
}
332340
}
333341
let progressHtml
334342
// for each step that has succeeded, increment activeStepId by 1
@@ -349,8 +357,6 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider
349357
CodeWhispererConstants.uploadingCodeStepMessage,
350358
activeStepId === 0
351359
)
352-
// TO-DO: remove this step entirely since we do entirely client-side builds
353-
// TO-DO: do we still show the "Building in Java 17/21 environment" progress update?
354360
const buildMarkup =
355361
activeStepId >= 1 && transformByQState.getTransformationType() !== TransformationType.SQL_CONVERSION // for SQL conversions, don't show buildCode step
356362
? simpleStep(

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,8 @@ export class DiffModel {
180180

181181
const changedFiles = parsePatch(diffContents)
182182
getLogger().info('CodeTransformation: parsed patch file successfully')
183-
// path to the directory containing copy of the changed files in the transformed project
184-
// if doing intermediate client-side build, pathToWorkspace is the already-copied project directory
185-
// otherwise, we are at the very end of the transformation and need to copy the project to show the changes
183+
// if doing intermediate client-side build, pathToWorkspace is the path to the unzipped project's 'sources' directory (re-using upload ZIP)
184+
// otherwise, we are at the very end of the transformation and need to copy the changed files in the project to show the diff(s)
186185
const pathToTmpSrcDir = isIntermediateBuild ? pathToWorkspace : this.copyProject(pathToWorkspace, changedFiles)
187186
transformByQState.setProjectCopyFilePath(pathToTmpSrcDir)
188187

0 commit comments

Comments
 (0)