Skip to content

Commit 460321a

Browse files
authored
Merge #7065 from justinmk3/fixlspinstall
2 parents a264377 + 539ff4a commit 460321a

File tree

8 files changed

+86
-81
lines changed

8 files changed

+86
-81
lines changed

packages/amazonq/src/lsp/activation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import vscode from 'vscode'
77
import { startLanguageServer } from './client'
88
import { AmazonQLspInstaller } from './lspInstaller'
9-
import { lspSetupStage, ToolkitError } from 'aws-core-vscode/shared'
9+
import { lspSetupStage, ToolkitError, messages } from 'aws-core-vscode/shared'
1010

1111
export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
1212
try {
@@ -16,6 +16,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
1616
})
1717
} catch (err) {
1818
const e = err as ToolkitError
19-
void vscode.window.showInformationMessage(`Unable to launch amazonq language server: ${e.message}`)
19+
void messages.showViewLogsMessage(`Failed to launch Amazon Q language server: ${e.message}`)
2020
}
2121
}

packages/core/src/amazonq/lsp/lspClient.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import * as jose from 'jose'
1515

1616
import { Disposable, ExtensionContext } from 'vscode'
1717

18-
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient'
18+
import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient'
1919
import {
2020
BuildIndexRequestPayload,
2121
BuildIndexRequestType,
@@ -235,7 +235,7 @@ async function validateNodeExe(nodePath: string, lsp: string, args: string[]) {
235235
const r = await proc.run()
236236
const ok = r.exitCode === 0 && r.stdout.includes('ok')
237237
if (!ok) {
238-
const msg = `failed to run basic "node -e" test (exitcode=${r.exitCode}): ${proc}`
238+
const msg = `failed to run basic "node -e" test (exitcode=${r.exitCode}): ${proc.toString(false, true)}`
239239
logger.error(msg)
240240
throw new ToolkitError(`amazonqLsp: ${msg}`)
241241
}
@@ -244,7 +244,7 @@ async function validateNodeExe(nodePath: string, lsp: string, args: string[]) {
244244
const lspProc = new ChildProcess(nodePath, [lsp, ...args], { logging: 'no' })
245245
try {
246246
// Start asynchronously (it never stops; we need to stop it below).
247-
lspProc.run().catch((e) => logger.error('failed to run: %s', lspProc))
247+
lspProc.run().catch((e) => logger.error('failed to run: %s', lspProc.toString(false, true)))
248248

249249
const ok2 =
250250
!lspProc.stopped &&
@@ -264,7 +264,9 @@ async function validateNodeExe(nodePath: string, lsp: string, args: string[]) {
264264
truthy: true,
265265
})
266266
if (!ok2 || selfExit) {
267-
throw new ToolkitError(`amazonqLsp: failed to run (exitcode=${lspProc.exitCode()}): ${lspProc}`)
267+
throw new ToolkitError(
268+
`amazonqLsp: failed to run (exitcode=${lspProc.exitCode()}): ${lspProc.toString(false, true)}`
269+
)
268270
}
269271
} finally {
270272
lspProc.stop(true)
@@ -300,18 +302,11 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths
300302

301303
const serverModule = resourcePaths.lsp
302304

303-
// If the extension is launch in debug mode the debug server options are use
304-
// Otherwise the run options are used
305-
let serverOptions: ServerOptions = {
306-
run: { module: serverModule, transport: TransportKind.ipc },
307-
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions },
308-
}
309-
310-
// TODO(jmkeyes): this overwrites the above...?
311-
serverOptions = createServerOptions({
305+
const serverOptions = createServerOptions({
312306
encryptionKey: key,
313307
executable: resourcePaths.node,
314308
serverModule,
309+
// TODO(jmkeyes): we always use the debug options...?
315310
execArgv: debugOptions.execArgv,
316311
})
317312

packages/core/src/amazonq/lsp/lspController.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,11 @@ export class LspController {
9898
}
9999

100100
async buildIndex(buildIndexConfig: BuildIndexConfig) {
101-
this.logger.info(`LspController: Starting to build index of project`)
101+
this.logger.info(`Starting to build index of project`)
102102
const start = performance.now()
103103
const projPaths = (vscode.workspace.workspaceFolders ?? []).map((folder) => folder.uri.fsPath)
104104
if (projPaths.length === 0) {
105-
this.logger.info(`LspController: Skipping building index. No projects found in workspace`)
105+
this.logger.info(`Skipping building index. No projects found in workspace`)
106106
return
107107
}
108108
projPaths.sort()
@@ -119,12 +119,12 @@ export class LspController {
119119
(accumulator, currentFile) => accumulator + currentFile.fileSizeBytes,
120120
0
121121
)
122-
this.logger.info(`LspController: Found ${files.length} files in current project ${projPaths}`)
122+
this.logger.info(`Found ${files.length} files in current project ${projPaths}`)
123123
const config = buildIndexConfig.isVectorIndexEnabled ? 'all' : 'default'
124124
const r = files.map((f) => f.fileUri.fsPath)
125125
const resp = await LspClient.instance.buildIndex(r, projRoot, config)
126126
if (resp) {
127-
this.logger.debug(`LspController: Finish building index of project`)
127+
this.logger.debug(`Finish building index of project`)
128128
const usage = await LspClient.instance.getLspServerUsage()
129129
telemetry.amazonq_indexWorkspace.emit({
130130
duration: performance.now() - start,
@@ -137,7 +137,7 @@ export class LspController {
137137
credentialStartUrl: buildIndexConfig.startUrl,
138138
})
139139
} else {
140-
this.logger.error(`LspController: Failed to build index of project`)
140+
this.logger.error(`Failed to build index of project`)
141141
telemetry.amazonq_indexWorkspace.emit({
142142
duration: performance.now() - start,
143143
result: 'Failed',
@@ -149,7 +149,7 @@ export class LspController {
149149
}
150150
} catch (error) {
151151
// TODO: use telemetry.run()
152-
this.logger.error(`LspController: Failed to build index of project`)
152+
this.logger.error(`Failed to build index of project`)
153153
telemetry.amazonq_indexWorkspace.emit({
154154
duration: performance.now() - start,
155155
result: 'Failed',
@@ -166,7 +166,7 @@ export class LspController {
166166

167167
async trySetupLsp(context: vscode.ExtensionContext, buildIndexConfig: BuildIndexConfig) {
168168
if (isCloud9() || isWeb() || isAmazonInternalOs()) {
169-
this.logger.warn('LspController: Skipping LSP setup. LSP is not compatible with the current environment. ')
169+
this.logger.warn('Skipping LSP setup. LSP is not compatible with the current environment. ')
170170
// do not do anything if in Cloud9 or Web mode or in AL2 (AL2 does not support node v18+)
171171
return
172172
}
@@ -181,7 +181,7 @@ export class LspController {
181181
const usage = await LspClient.instance.getLspServerUsage()
182182
if (usage) {
183183
this.logger.info(
184-
`LspController: LSP server CPU ${usage.cpuUsage}%, LSP server Memory ${
184+
`LSP server CPU ${usage.cpuUsage}%, LSP server Memory ${
185185
usage.memoryUsage / (1024 * 1024)
186186
}MB `
187187
)
@@ -190,7 +190,7 @@ export class LspController {
190190
30 * 60 * 1000
191191
)
192192
} catch (e) {
193-
this.logger.error(`LspController: LSP failed to activate ${e}`)
193+
this.logger.error(`LSP failed to activate ${e}`)
194194
}
195195
})
196196
}
@@ -207,7 +207,7 @@ export class LspController {
207207
return
208208
}
209209
this._contextCommandSymbolsUpdated = true
210-
getLogger().debug(`LspController: Start adding symbols to context picker menu`)
210+
getLogger().debug(`Start adding symbols to context picker menu`)
211211
try {
212212
const indexSeqNum = await LspClient.instance.getIndexSequenceNumber()
213213
await LspClient.instance.updateIndex([], 'context_command_symbol_update')
@@ -223,15 +223,15 @@ export class LspController {
223223
{ interval: 1000, timeout: 60_000, truthy: true }
224224
)
225225
} catch (err) {
226-
getLogger().error(`LspController: Failed to find symbols`)
226+
this.logger.error(`Failed to find symbols`)
227227
}
228228
}
229229

230230
private async setupLsp(context: vscode.ExtensionContext) {
231231
await lspSetupStage('all', async () => {
232232
const installResult = await new WorkspaceLspInstaller().resolve()
233233
await lspSetupStage('launch', async () => activateLsp(context, installResult.resourcePaths))
234-
this.logger.info('LspController: LSP activated')
234+
this.logger.info('LSP activated')
235235
})
236236
}
237237
}

packages/core/src/shared/lsp/baseLspInstaller.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ export abstract class BaseLspInstaller<T extends ResourcePaths = ResourcePaths,
5252
await this.postInstall(assetDirectory)
5353

5454
const deletedVersions = await cleanLspDownloads(manifest.versions, nodePath.dirname(assetDirectory))
55-
this.logger.debug(`cleaning old LSP versions deleted ${deletedVersions.length} versions`)
55+
if (deletedVersions.length > 0) {
56+
this.logger.debug(`cleaning old LSP versions: deleted ${deletedVersions.length} versions`)
57+
}
5658

5759
const r = {
5860
...installationResult,

packages/core/src/shared/lsp/lspResolver.ts

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -40,44 +40,41 @@ export class LanguageServerResolver {
4040
languageServerVersion: result.version,
4141
}
4242
}
43-
try {
44-
const latestVersion = this.latestCompatibleLspVersion()
45-
const targetContents = this.getLSPTargetContents(latestVersion)
46-
const cacheDirectory = this.getDownloadDirectory(latestVersion.serverVersion)
47-
48-
const serverResolvers: StageResolver<LspResult>[] = [
49-
{
50-
// 1: Use the current local ("cached") LSP server bundle, if any.
51-
resolve: async () => await this.getLocalServer(cacheDirectory, latestVersion, targetContents),
52-
telemetryMetadata: { id: this.lsName, languageServerLocation: 'cache' },
53-
},
54-
{
55-
// 2: Download the latest LSP server bundle.
56-
resolve: async () => await this.fetchRemoteServer(cacheDirectory, latestVersion, targetContents),
57-
telemetryMetadata: { id: this.lsName, languageServerLocation: 'remote' },
58-
},
59-
{
60-
// 3: If the download fails, try an older, cached version.
61-
resolve: async () => await this.getFallbackServer(latestVersion),
62-
telemetryMetadata: { id: this.lsName, languageServerLocation: 'fallback' },
63-
},
64-
]
65-
66-
/**
67-
* Example:
68-
* ```
69-
* LspResult {
70-
* assetDirectory = "<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0"
71-
* location = 'cache'
72-
* version = '3.3.0'
73-
* }
74-
* ```
75-
*/
76-
const resolved = await tryStageResolvers('getServer', serverResolvers, getServerVersion)
77-
return resolved
78-
} finally {
79-
logger.info(`Finished setting up LSP server`)
80-
}
43+
const latestVersion = this.latestCompatibleLspVersion()
44+
const targetContents = this.getLSPTargetContents(latestVersion)
45+
const cacheDirectory = this.getDownloadDirectory(latestVersion.serverVersion)
46+
47+
const serverResolvers: StageResolver<LspResult>[] = [
48+
{
49+
// 1: Use the current local ("cached") LSP server bundle, if any.
50+
resolve: async () => await this.getLocalServer(cacheDirectory, latestVersion, targetContents),
51+
telemetryMetadata: { id: this.lsName, languageServerLocation: 'cache' },
52+
},
53+
{
54+
// 2: Download the latest LSP server bundle.
55+
resolve: async () => await this.fetchRemoteServer(cacheDirectory, latestVersion, targetContents),
56+
telemetryMetadata: { id: this.lsName, languageServerLocation: 'remote' },
57+
},
58+
{
59+
// 3: If the download fails, try an older, cached version.
60+
resolve: async () => await this.getFallbackServer(latestVersion),
61+
telemetryMetadata: { id: this.lsName, languageServerLocation: 'fallback' },
62+
},
63+
]
64+
65+
/**
66+
* Example:
67+
* ```
68+
* LspResult {
69+
* assetDirectory = "<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0"
70+
* location = 'cache'
71+
* version = '3.3.0'
72+
* }
73+
* ```
74+
*/
75+
const resolved = await tryStageResolvers('getServer', serverResolvers, getServerVersion)
76+
logger.info('Finished preparing "%s" LSP server: %O', this.lsName, resolved.assetDirectory)
77+
return resolved
8178
}
8279

8380
/** Finds an older, cached version of the LSP server bundle. */
@@ -147,11 +144,7 @@ export class LanguageServerResolver {
147144
}
148145
} else {
149146
// Delete the cached directory since it's invalid
150-
if (await fs.existsDir(cacheDirectory)) {
151-
await fs.delete(cacheDirectory, {
152-
recursive: true,
153-
})
154-
}
147+
await fs.delete(cacheDirectory, { force: true, recursive: true })
155148
throw new ToolkitError('Failed to retrieve server from cache', { code: 'InvalidCache' })
156149
}
157150
}

packages/core/src/shared/lsp/manifestResolver.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,25 +64,38 @@ export class ManifestResolver {
6464
}).getNewETagContent(this.getEtag())
6565

6666
if (!resp.content) {
67-
throw new ToolkitError(
68-
`New content was not downloaded; fallback to the locally stored "${this.lsName}" manifest`
69-
)
67+
const msg = `"${this.lsName}" manifest unchanged, skipping download: ${this.manifestURL}`
68+
logger.debug(msg)
69+
70+
const localManifest = await this.getLocalManifest(true).catch(() => undefined)
71+
if (localManifest) {
72+
localManifest.location = 'remote'
73+
return localManifest
74+
} else {
75+
// Will emit a `languageServer_setup` result=failed metric...
76+
throw new ToolkitError(msg)
77+
}
7078
}
7179

80+
logger.debug(`fetched "${this.lsName}" manifest: ${this.manifestURL}`)
7281
const manifest = this.parseManifest(resp.content)
7382
await this.saveManifest(resp.eTag, resp.content)
7483
await this.checkDeprecation(manifest)
7584
manifest.location = 'remote'
7685
return manifest
7786
}
7887

79-
private async getLocalManifest(): Promise<Manifest> {
80-
logger.info(`Failed to download latest "${this.lsName}" manifest. Falling back to local manifest.`)
88+
private async getLocalManifest(silent: boolean = false): Promise<Manifest> {
89+
if (!silent) {
90+
logger.info(`trying local "${this.lsName}" manifest...`)
91+
}
8192
const storage = this.getStorage()
8293
const manifestData = storage[this.lsName]
8394

8495
if (!manifestData?.content) {
85-
throw new ToolkitError(`Failed to download "${this.lsName}" manifest and no local manifest found.`)
96+
const msg = `local "${this.lsName}" manifest not found`
97+
logger.warn(msg)
98+
throw new ToolkitError(msg)
8699
}
87100

88101
const manifest = this.parseManifest(manifestData.content)
@@ -145,6 +158,7 @@ export class ManifestResolver {
145158
...storage,
146159
[this.lsName]: {
147160
etag,
161+
// XXX: this stores the entire manifest. vscode warns about our globalStorage size...
148162
content,
149163
},
150164
})

packages/core/src/shared/resourcefetcher/httpResourceFetcher.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ export class HttpResourceFetcher implements ResourceFetcher<Response> {
6868
if (response.status === 304) {
6969
// Explanation: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match
7070
contents = undefined
71-
this.logger.verbose(`E-Tag, ${eTagResponse}, matched. No content downloaded from: ${this.url}`)
71+
this.logger.verbose(`E-Tag matched (${eTagResponse}). Download skipped: ${this.url}`)
7272
} else {
73-
this.logger.verbose(`No E-Tag match. Downloaded content from: ${this.logText()}`)
73+
this.logger.verbose(`E-Tag not matched. Downloaded: ${this.logText()}`)
7474
}
7575

7676
return { content: contents, eTag: eTagResponse }

packages/core/src/shared/utilities/processUtils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,9 +513,10 @@ export class ChildProcess {
513513
* Gets a string representation of the process invocation.
514514
*
515515
* @param noparams Omit parameters in the result (to protect sensitive info).
516+
* @param nostatus Omit "(not started)" note.
516517
*/
517-
public toString(noparams = false): string {
518-
const pid = this.pid() > 0 ? `PID ${this.pid()}:` : '(not started)'
518+
public toString(noparams = false, nostatus = false): string {
519+
const pid = this.pid() > 0 ? `PID ${this.pid()}:` : nostatus ? '' : '(not started)'
519520
return `${pid} [${this.#command} ${noparams ? '...' : this.#args.join(' ')}]`
520521
}
521522
}

0 commit comments

Comments
 (0)